» Sentinel Language Specification

This is the specification for the Sentinel policy language.

The Sentinel language is designed with policy enforcement in mind. It is dynamically typed and garbage collected and has explicit support for rule construction representing boolean logic.

The language is designed to be easy to learn and use by non-programmers. It is expected to be embedded within applications.

» Table of Contents

» Source code representation

Source code is Unicode text encoded in UTF-8. A "character" referenced in this document refers to a Unicode code point. Each code point is distinct: upper and lower case letters are different characters.

The underscore character _ (U+005F) is considered a "letter".

letter        = unicode_letter | "_" .
decimal_digit = "0" … "9" .
octal_digit   = "0" … "7" .
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" .

» Declarations and Scope

A declaration binds an identifier to a value. An identifier is declared at the point it is first assigned. An assignment is considered a first assignment if the identifier hasn't already been previously declared in the current scope or any parent scopes.

The scope of an identifier is the extent of source text in which the identifier denotes the specified value. Sentinel is lexically scoped using blocks.

An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the value assigned in the inner declaration.

» Blocks

A block is a possibly empty sequence of statements within matching brace brackets.

Block = "{" StatementList "}" .
StatementList = { Statement ";" } .

Blocks nest and affect scoping.

In addition to explicit blocks in the source code, there are implicit blocks:

1. The universe block encompasses all source text.
2. Each program has a program block containing all source text for that program.
3. Each "any", "all", and "for" statements is considered to be in its own implicit block. "if" does not create an implicit block.

» Lexical Elements

» Comments

Comments are sections of source text used for documentation.

# Single line comment
// Single line comment
/* multi
line
    comment */

Three forms of comments are supported: two single-line forms and one multi-line form. A single-line comment begins with the token // or #. Everything between the starting token and the end of the line is ignored.

A multi-line comment begins with the token /* and ends with the token */. Everything between /* and */ is ignored.

A comment may not start inside a string literal or inside another comment.

A multi-line comment containing no newlines acts like a space.

» Identifiers

Identifiers name program entities such as rules, variables, and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.

identifier = letter { letter | unicode_digit } .
a
_a
_A
αβ

» Keywords

The following keywords are reserved and may not be used as identifiers:

all
any
as
for
func
if
import
else
null
return
rule
undefined
when

» Operators and Delimiters

The following character sequences represent operators, delimiters, and other special tokens:

+
-
*
/
%
+=
-=
*=
/=
%=
==
<
>
=
!
!=
<=
>=
( )
[ ]
{ }
,
.
:
;
and
or
contains
in
is
matches
not
xor
else

» Integer Literals

An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: 0 for octal, 0x or 0X for hexadecimal. In hexadecimal literals, letters a-f and A-F represents values 10 through 15.

Integers are signed 64-bit values (-9223372036854775808 to 9223372036854775807).

int_lit     = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1" … "9" ) { decimal_digit } .
octal_lit   = "0" { octal_digit } .
hex_lit     = "0" ( "x" | "X" ) hex_digit { hex_digit } .
42
0600
0xBadFace
170141183460469231731687303715884105727

» Floating-point Literals

A floating-point literal is a decimal representation of a floating-point constant. It has an integer part, a decimal point, a fractional part, and an exponent part. The integer and fractional part comprise decimal digits; the exponent part is an e or E followed by an optionally signed decimal exponent. One of the integer part or the fractional part may be elided; one of the decimal point or the exponent may be elided.

Floating-point numbers are IEEE-754 64-bit floating numbers.

float_lit = decimals "." [ decimals ] [ exponent ] |
            decimals exponent |
            "." decimals [ exponent ] .
decimals  = decimal_digit { decimal_digit } .
exponent  = ( "e" | "E" ) [ "+" | "-" ] decimals .
0.
72.40
072.40  // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5

» String Literals

String literals are character sequences between double quotes, as in "bar". Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal.

A multi-character sequence beginning with a backslash encode values in various formats.

Backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: \x followed by exactly two hexadecimal digits; \u followed by exactly four hexadecimal digits; \U followed by exactly eight hexadecimal digits, and a plain backslash \ followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base.

After a backslash, certain single-character escapes represent special values:

\a   U+0007 alert or bell
\b   U+0008 backspace
\f   U+000C form feed
\n   U+000A line feed or newline
\r   U+000D carriage return
\t   U+0009 horizontal tab
\v   U+000b vertical tab
\\   U+005c backslash
\"   U+0022 double quote

The three-digit octal (\nnn) and two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal \377 and \xFF represent a single byte of value 0xFF=255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.

A string value is a (possibly empty) sequence of bytes. Strings are immutable: once created, it is impossible to change the contents of a string.

The length of a string s (its size in bytes) can be discovered using the built-in function length. An individual character (of type string) can be accessed by integer indices 0 through length(s)-1.

string_lit = `"` { unicode_value | byte_value } `"` .
unicode_value    = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value       = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value   = `\` "x" hex_digit hex_digit .
little_u_value   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value      = `\` "U" hex_digit hex_digit hex_digit hex_digit
                           hex_digit hex_digit hex_digit hex_digit .
escaped_char     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) .
`abc`                // same as "abc"
`\n
\n`                  // same as "\\n\n\\n"
"\n"
"\""                 // same as `"`
"Hello, world!\n"
"日本語"
"\u65e5本\U00008a9e"
"\xff\u00FF"
"\uD800"             // illegal: surrogate half
"\U00110000"         // illegal: invalid Unicode code point

» Implicit Line Joining

Expressions can be split over more than one line. For example:

a or
b or # This is a comment
c

Implicitly continued lines can have trailing comments. Blank continued lines are allowed.

» Whitespace

Whitespace is needed to separate tokens, but no distinction is made between the number and combination of whitespace characters. Whitespace characters can be space (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A).

a or b

a or      b

a or
    b

rule { a or b }

rule {
  a or b }

rule {
  a or
  b
}

» Semicolons

The formal grammar uses semicolons ";" as terminators in a number of productions. It is idiomatic Sentinel source to omit semicolons in most cases.

A semicolon is automatically inserted into the token stream immediately after a line's final token if that token is:

  • An identifier
  • An integer, float, or string literal
  • The keyword break, continue, or return
  • The delimiter ), ], or }

» Variables

A variable is a storage location for holding a value.

A variable has a dynamic type, which is the concrete type of the value assigned to the variable at run time. The dynamic type may vary during execution and change as a result of further assignments.

A variable's value is retrieved by referring to the variable in an expression; it is the most recent value assigned to the variable. If a variable has not yet been declared (initially assigned), it is an error.

x = 7          // x is type int
x = "foo"      // x is now type string

x = y          // error if y is not previously assigned

» Undefined

The value denoted by the keyword undefined represents undefined behavior or values. It can be created directly using the keyword undefined. It is also returned as a result of expressions in specified cases.

undefined is a valid operand for any operations. Only undefined or true will result in true. All other operations result in undefined. An exception is if undefined is not reached as a result of short-circuit operations.

undefined OR true       = true
undefined OR false      = undefined
undefined OR undefined  = undefined
undefined AND true      = undefined
undefined AND false     = undefined
undefined AND undefined = undefined
undefined XOR true      = undefined
undefined XOR false     = undefined
undefined XOR undefined = undefined

// Short-circuit examples
false OR true OR undefined   = true
false OR undefined OR true   = true
true AND false AND undefined = false
true AND undefined AND false = undefined

// Non-logical operators
undefined + 5 = undefined
-undefined    = undefined
!undefined    = undefined

If the result of the main rule is undefined, it is treated as false but is indicative of erroneous logic.

» Expressions

» Operand

Operands denote the elementary values in an expression. An operand may be a literal, an identifier denoting a variable, rule, or function, or a parenthesized expression.

Operand     = Literal | OperandName | MethodExpr | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit | RuleLit .
BasicLit    = int_lit | float_lit | string_lit .
OperandName = identifier | QualifiedIdent.

» Primary Expressions

Primary expressions are the operands for unary and binary expressions.

PrimaryExpr =
    Operand |
    PrimaryExpr Selector |
    PrimaryExpr Index |
    PrimaryExpr Slice |
    PrimaryExpr Arguments .

Selector       = "." identifier .
Index          = "[" Expression "]" .
Slice          = "[" [ Expression ] ":" [ Expression ] "]" |
                 "[" [ Expression ] ":" Expression ":" Expression "]" .
Arguments      = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()

» Null

The reserved word null denote the singleton value null. Null represents the explicit absense of a value. Behavior of null within expressions is specified explicitly for each expression.

» Booleans

» Boolean Literals

The reserved words true and false denote objects that represent the boolean values true and false, respectively. These are boolean literals.

BoolLit = "true" | "false" .

» Boolean Expressions

Boolean expressions are expressions that must result in a boolean value. Any other value type becomes the undefined value.

BoolExpr = Expression .

» List Literals

A list literal denotes a list, which is an integer indexed collection of values.

ListLit       = "[" [ ElementList [ "," ] ] "]" .
ElementList   = Element { "," Element } .
Element       = Expression | LiteralValue .

A list may contain zero or more values. The number of values in a list is its length.

A list has a set of indices. An empty list has an empty set of indices. A non-empty list has the index set {0...n - 1} where n is the length of the list. Attempting to access a list using an index that is not a member of its set of indices results in the undefined value.

» Map Literals

A map literal denotes a map, which is an unordered group of elements indexed by a set of unique keys.

MapLit           = "{" [ KeyedElementList [ "," ] ] "}" .
KeyedElementList = KeyedElement { "," KeyedElement } .
KeyedElement     = Element ":" Element .

Keys can only be a boolean, numeric, or string type.

The value of a non-existent key is the undefined value.

» Function Literals

A function literal represents a function.

FunctionLit    = "func" Function .
Function       = Parameters FunctionBody .
FunctionBody   = Block .
Parameters     = "(" [ IdentifierList [ "," ] ] ")" .
IdentifierList = identifier { "," identifier } .
func(a, b) { return b }

Function literals are only allowed in the file scope. They may refer to variables defined in surrounding blocks.

A function must terminate with a return statement. If a function has no meaningful return value, it should return undefined.

» Rule Expressions

A rule is a boolean expression that is evaluated lazily and the result is memoized.

If the optional "when" predicate is present, the rule is evaluated only when the "when" boolean expression results in true. If the predicate is false, the rule is not evaluated and returns true. The predicate is evaluated when the rule would be evaluated; it is also lazy and memoized in the same way.

RuleExpr = "rule" [ "when" BoolExpr ] "{" BoolExpr "}" .
rule { x is y }

rule {
    x == "value" or
    y == "other"
}

rule when x is y { y > 42 }

» Index Expressions

A primary expression of the form a[x] denotes the element of a list, map, or string indexed by x. The value x is called the index or key.

For a of type map:

  • If a contains a key x, a[x] is the map value with key x.
  • If a does not contain key x, a[x] is the undefined value.

For a of type list:

  • x must be an integer
  • x must be in the range [-1 * length(a), length(a)-1]
  • If x is contained in the set of indices of a, a[x] is the list element at index x. If x is negative, a[x] is equivalent to a[length(a)+x].
  • If x is not contained in the set of indices of a, a[x] is the undefined value.

For a of type string:

  • x must be an integer
  • x must be in the range [-1 * length(a), length(a)-1]
  • If x is in range, a[x] is the byte value at index x as a string. If x is negative, a[x] is equivalent to a[length(a)+x].
  • If x is out of range, a[x] is the undefined value.

For a of value null:

  • a[x] is the undefined value.

Otherwise a[x] is an error.

» Selectors

For a primary expression x, the selector expression x.f denotes the field f of the value x. The identifier f is called the selector. The type of the selector expression is the type of the selector.

Selectors are used to access data from imports. The first primary expression x in x.f denotes the import name. The field f is the selector to access data from the import.

Selectors may also be used to access map data with static keys. They are syntactic sugar over index expressions. math.pi is equivalent to math["pi"] and exhibit the same limitations and behavior as an index expression.

Selectors on imports are memoized per policy execution by the runtime. For example, time.pst.hour would be retrieved from an import once, memoized, and reused for all subsequent time.pst.hour selector expressions. Selectors on other data not sourced from an import will not be memoized. To avoid confusion, selector expressions should only be used for externally sourced data.

Selectors on undefined result in undefined.

math.pi
time.pst.hour

» Slice Expressions

Slice expressions construct a substring or list from a string or list.

The primary expression

a[low : high]

constructs a substring or list. The indices low and high select which elements of operand a appear in the result. The result has indices starting at 0 and length equal to high - low.

a = [1, 2, 3, 4, 5]
b = a[1:4]           // [2, 3, 4]

For convenience, any of the indices may be omitted. A missing low index defaults to zero; a missing high index defaults to the length of the sliced operand:

a[2:]  // same as a[2 : length(a)]
a[:3]  // same as a[0 : 3]
a[:]   // same as a[0 : length(a)]

The indices are in range if 0 <= low <= high <= length(a), otherwise they are out of range. If the indices are out of range at run time, the result is the undefined value.

If a is the value null, the result is the undefined value.

If a is any other value type, it is an error.

» Calls

Given an expression f where f is a function value:

f(a1, a2, … an)

calls f with arguments a1, a2, ... an. The type of the expression is the result of f. The arguments are evaluated left to right. Arguments are passed by value.

» Operators

Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr  = PrimaryExpr | unary_op UnaryExpr .

binary_op  = logical_op | set_op | rel_op | add_op | mul_op | else_op .
logical_op = "and" | "or" | "xor" .
set_op     = ["not"] ( "contains" | "in" ).
rel_op     = "==" | "!=" | "<" | "<=" | ">" | ">=" | "is" | "is not" | "matches" | "not matches" .
add_op     = "+" | "-" .
mul_op     = "*" | "/" | "%" .
else_op    = "else" .
unary_op   = "+" | "-" | "!" | "not" .

» Operator Precedence

Unary operators have the highest precedence.

Precedence    Operator
    6             *  /  %
    5             +  -
    4             else
    3             ==  !=  <  <=  >  >= "is" "is not" "matches" "contains" "in"
    2             and
    1             or  xor

Binary operators of the same precedence associate from left to right. For instance, x / y * z is the same as (x / y) * z.

» Arithmetic Operators

Arithmetic operators apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to integer, and floating-point types; + also applies to strings.

+    sum                    integers, floats, strings, lists
-    difference             integers, floats
*    product                integers, floats
/    quotient               integers, floats
%    remainder              integers

» Integer operators

For two integer values x and y, the integer quotient q = x / y and remainder r = x % y satisfy the following relationships:

x = q*y + r  and  |r| < |y|

with x / y truncated towards zero.

x     y     x / y     x % y
 5     3       1         2
-5     3      -1        -2
 5    -3      -1         2
-5    -3       1        -2

As an exception to this rule, if the dividend x is the most negative value for the int type of x (-9223372036854775808), the quotient q = x / -1 is equal to x (and r = 0).

If the divisor is a constant, it must not be zero. If the divisor is zero at run time, an error occurs.

» Integer overflow

For signed integers, the operations +, -, and * may legally overflow and the resulting value exists and is deterministically defined by the signed integer representation, the operation, and its operands. No exception is raised as a result of overflow.

» Floating-point operators

For floating-point numbers, +x is the same as x, while -x is the negation of x. The result of a floating-point division by zero is not specified beyond the IEEE-754 standard.

» String Concatenation

Strings can be concatenated using the + operator or the += assignment operator:

x = "hi"
y = "hello"
x = x + ", " + y     // "hi, hello"
x += " and good bye" // "hi, hello and good bye"

String addition creates a new string by concatenating the operands.

» List Concatenation

Lists can be concatenated using the + operator or the += assignment operator:

x = [1, 2]
x = x + [2, 3]    // [1, 2, 2, 3]
x += [4]          // [1, 2, 2, 3, 4]

List addition creates a new list by concatenating the operands.

» Comparison Operators

Comparison operators compare two operands and yield a boolean value.

==       equal
!=       not equal
<        less
<=       less or equal
>        greater
>=       greater or equal
"is"     equal
"is not" not equal

In any comparison, the two operands must be equivalent types. The only exception are integers and floats, which are considered numeric and may be compared. Any comparison between other non-matching types results in the undefined value.

The equality operators ==, !=, is, and is not apply to operands that are comparable. The ordering operators <, <=, >, and >= apply to operands that are ordered. These terms and the result of the comparisons are defined as follows:

  • Boolean values are comparable. Two boolean values are equal if they are either both true or both false.
  • Integer values are comparable and ordered, in the usual way.
  • Floating point values are comparable and ordered, as defined by the IEEE-754 standard.
  • An integer compared with floating point value treats the integer as the converted floating point value.
  • String values are comparable and ordered, lexically byte-wise

Lists and maps are not comparable. As a special case, list and maps may be compared to the null value for equality or inequality.

If either operand is the undefined value, the result of the expression is the undefined value.

» Logical Operators

Logical operators apply to boolean values and yield a boolean result.

Logical operators are evaluated left-to-right and perform short-circuit logic. The right-hand side is not guaranteed to be evaluated if a short-circuit can be performed.

and    conditional AND    p and q  is  "if p then q else false"
or     conditional OR     p or q   is  "if p then true else q"
xor    conditional XOR    p xor q  is  "if p and not q or not p and q then true"
!      NOT                !p       is  "not p"
not    NOT                !p       is  "not p"

» Set Operators

The set operators contains and in test for set inclusion for lists and maps, and substring inclusion for strings.

Set operators may be negated by prefixing the operator with not: not contains and not in. This is equivalent to wrapping the binary expression in a unary not but results in a more readable form.

contains tests if the left-hand collection contains the right-hand value. For lists, this tests equality of any value. For maps, this tests equality of any key. For strings, this tests for substring existence.

in tests if the right-hand collection contains the left-hand value. The behavior is equivalent to contains.

The collection must be a list, map, or string. If it is the undefined value, the result is the undefined value. For any other value, the result is an error.

[1, 2, 3] contains 2            // true
[1, 2, 3] contains 5            // false
[1, 2, 3] contains "value"      // false
[1, 2, 3] not contains "value"  // true

{ "a": 1, "b": 2 } contains "a"     // true
{ "a": 1, "b": 2 } contains "c"     // false
{ "a": 1, "b": 2 } contains 2       // false
{ "a": 1, "b": 2 } not contains 2   // true

"test" contains "est"     // true
"test" contains "best"    // false
"test" in "testing"       // true
"best" in "testing"       // false

» Matches Operator

The matches operator tests if a string matches a regular expression.

The matches operators may be negated by prefixing the operator with not: not matches. This is equivalent to wrapping the binary expression in a unary not but results in a more readable form.

The left-hand value is the string to test. The right-hand value is a string value representing a regular expression.

The syntax of the regular expression is the same general syntax used by Python, Ruby, and other languages. More precisely, it is the syntax accepted by RE2. The regular expression is not anchored by default; any anchoring must be explicitly specified.

"test" matches "e"            // true
"test" matches "^e"           // false
"TEST" matches "test"         // false
"TEST" matches "(?i)test"     // true
"ABC123" matches "[A-Z]+\\d+" // true
"test" not matches "e"        // false

If either operand is undefined, the result is the undefined value. For any other non-string value, the result is an error.

» Else Operator

Else operators can be used to provide a default value for an expression that may evaluate to the undefined value. Else operators return their left-hand value unless it is undefined, otherwise they return their right-hand value.

foo() else 42
foo.bar else ""
config["bad-key"] else null

» Any, All Expressions

any and all expressions are existential and universal quantifiers, respectively. any expressions are equivalent to a chain of or and all expressions are equivalent to a chain of and. Both expressions implement short-circuiting equivalent to logical operators.

The body of any and all expressions is a boolean expression.

any returns the boolean value true if any value in the collection expression results in the body expression evaluating to true. If the body expression evalutes to false for all values, the any expression returns false.

all returns the boolean true if all values in the collection expression result in the body expression evaluating to true. If any value in the collection expression result in the body expression evaluating to false, the all expression returns false.

For empty collections, any returns false and all returns true.

When the collection is a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.

When the collection is a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.

QuantExpr = QuantOp Expression "as" [ identifier "," ] identifier "{" BoolExpr "}" .
QuantOp   = "all" | "any" .
all group.tasks as t { t.driver is "vmware" }
any group.tasks as t { t.driver is "vmware" }

» Statements

» Expression Statements

Function call expressions may exist in the statement context.

ExpressionStmt = Expression .

» Assignments

Assignments set the value of an expression to the value of another expression.

Assignment = AssignExpr assign_op Expression .
AssignExpr = identifier | IndexExpr .
assign_op  = [ add_op | mul_op ] "=" .

The assignment x op= y is equivalent to x = x op (y) for supported values of op.

a = 1
b[12] = 42
c["key"] = "value"

a *= 12
b[12] += 8

» If Statements

If statements specify the conditional execution of two branches according to the value of a boolean expression. If the expression evaluates to true, the "if" branch is executed, otherwise, if present, the "else" branch is executed.

IfStmt = "if" BoolExpr Block [ "else" ( IfStmt | Block ) ] .
if x < y {
    return x
} else if x > z {
    return z
} else {
    return y
}

» For Statements

A for statement specifies repeated execution of a block.

The expression must evaluate to a list or a map.

When iterating over a list, the first identifier is assigned the index and the second identifier is assigned the value. If only one identifier is specified, it is assigned the value.

When iterating over a map, the first identifier is assigned the key and the second identifier is assigned the value. If only one identifier is specified, it is assigned the key.

ForStmt = "for" Expressions "as" [ identifier "," ] identifier Block .
for [1, 2, 3] as v { count += v } // basic sum

for [1, 2, 3] as idx, v {
    if idx > 1 { count += v }
}

data = { "a": 12, "b": 32 }
for data as k    { count += data[k] } // sum map values
for data as k, v { count += v }

» Return Statements

A return statement in a function F terminates the execution of F and provides the result value.

ReturnStmt = "return" Expression .

A function must terminate with a return statement.

The return statement can be invoked at any time during a function to terminate execution.

» Imports

An import adds an assignment to the global scope to external definitions.

The import declarations must only appear at the top of the source file, before any other statements. Comments may appear before imports.

ImportDecl = "import" Name ["as" identifier] .
Name       = string_lit .

An import name and identifier must be distinct. Two names may not repeated even if they're declared with different identifiers.

If an identifier is not specified, the identifier is the name of the import itself.

An import is not a valid value. The identifier representing the import may not be used as an expression, such as in assignment statements or call expressions.

The handling of import loading is specific to the runtime implementation.

Implicit imports may be added by the runtime, for example for standard library definitions. An explicit import of the same name should override a matching implicit import. For example, if "time" is an implicit import and the program specifies import "time" as cal, then only cal is defined.

import "time"
import "math" as m

» Built-in Functions

Built-in functions are predeclared. They are called like any other function.

» Length

The built-in function length returns the length of a collection of string.

The length of a string is the number of bytes in the string. It is not necessarilly the number of characters.

The length of a collection is the number of elements in that collection.

The length of undefined is undefined.

» Collections

» List Append

The built-in function append appends a value to the end of a list in-place.

Appending to a non-list value results in an immediate fail.

The return value of append is always the undefined value.

append([1,2], 3)      // [1, 2, 3]
append(1, 3)          // error()
append(undefined, 3)  // error()
append([], undefined) // [undefined] (a list containing `undefined`)

» Map Delete

The built-in function delete deletes elements from a map by key. The map is modified in-place.

Deleting a key that does not exist does nothing.

Calling delete for a non-map value results in an immediate fail.

The return value of delete is always the undefined value.

data = { "a": 2, "b": 3 }
delete(data, "a")            // data is now { "b": 3 }
delete(data, "c")            // data is unchanged
delete(1, "a")               // error()
delete(undefined, "b")       // error()

» Keys and Values

The built-in function keys and values return the keys and values of a map, respectively. The return value is a list. Both functions return values in an unspecified order.

The keys or values of undefined is undefined.

data = { "a": 2, "b": 3 }
keys(data)       // ["b", "a"]
values(data)     // [2, 3]

» Range

The built-in function range returns a list of numbers in a range.

There are three ways to call this function:

range(end)
range(start, end)
range(start, end, stride)

The start is inclusive, the end is exclusive.

If start is not provided, it defaults to 0. If stride is not provided, it defaults to 1.

range(5)           // [0,1,2,3,4]
range(1, 5)        // [1,2,3,4]
range(1, 5, 2)     // [1,3]

» Type Conversion

The built-in functions int, float, string convert a value to a value of that type according to the rules below.

For int:

  • Integer values are unchanged
  • String values are converted according to the syntax of integer literals
  • Float values are rounded down to their nearest integer value

For float:

  • Float values are unchanged
  • Integer values are converted to the nearest equivalent floating point value
  • String values are converted according to the syntax of float literals

For string:

  • String values are unchanged
  • Integer values are converted to the base 10 string representation
  • Float values are converted to a string formatted xxx.xxx with a precision of 6. This is equivalent to %f for C's sprintf.

For any other unspecified type, the result is the undefined value.

» Printing

The built-in function print can be used to output formatted debug information. The runtime may elide print function calls depending on its execution mode. The runtime may choose where the output of a print function goes.

The first argument is any value. If that value is a string, it is treated as a formatting string analogous to C's printf. Optionally, the following arguments are values for the formatting string.

The return value of print is always true to allow it to be used in boolean expressions.

print("hello")
print("number is %d", 42)  // "number is 42"
print("hello %s", "world") // "hello world"
print("%s: %d", "key", 12) // "key: 12"
print([1, 2, 3])           // "[1, 2, 3]"
print("value: %s", [1,2])  // "value: [1, 2]"

» Errors

The built-in function error is equivalent to print but immediately causes execution to halt and the main rule to evaluate to false. The program execution is considered to have failed.

» Program Execution

Program execution begins by evaluating the source top to bottom. If evaluation is successful, the main rule is evaluated and its result is returned. Execution can be interrupted at any time by an error, which can be called explicitly or implicitly through illegal or undefined behavior.


The content of this page is licensed under the CC BY 3.0.

Based on The Go Programming Language Specification licensed under CC BY 3.0.