sai-language
Version:
An object-oriented language designed to afford code comprehension and maintenance. Transpiles in-place to Javascript.
1,827 lines (1,097 loc) • 106 kB
Markdown
## Keyword Reference
SAI keyword reference.
#### Legend
.. - elided code
... - repeating pattern
A/B/C - alternatives
( .. ) - optional syntax or component
[block] - an indented block of code
[expr] - any expression/value
[function] - a function value
[identifier] - a variable or method name
lexpr / rexpr - an expression on the left/right side of an operator
#### Keyword Types
**Clause** - a component of a larger construct
**Comprehension** - a special class of operator intended for use with collections.
**Declaration** - describes or defines behaviour
**Literal** - declares a value or collection of values
**Pronoun** - an identifier that holds context-specific data
**Operator** - a process that produces a new, likely modified value while leaving the original value(s) unchanged
**Scoping Prefix** - symbol used at the beginning of a variable to specifiy context.
**Statement** - a unit of code, occuping one or more lines, that performs some useful action
**Verb** - a function reference that may be used as a statement or in an expression as a computation returning a value
#### Sample Data
Examples in this document use the following data:
set fruit to list Apple, Banana, Citron
set friends to:
:name 'Sara', age 23, #cat, province 'ON'
:name 'John', age 19, #cat, #dog, province 'ON'
:name 'Ellie', age 22, province 'QC'
:name 'Marshal', age 21, #dog, province 'ON'
:name 'Doug', age 18, province 'ON'
:name 'Ann', age 23, #cat, province 'QC'
:name 'Harry', age 31, province 'QC'
:name 'Jenna', age 28, #dog, province 'ON'
set
width to 10
height to 5
angle to 45o
### ! _operator_
.. ! [identifier] ([parameters]) (;)
**!** (bang) invokes the following identifier as a function call that returns a value, passing in any parameters that follow.
The following are synonymous:
set record !cursor.FetchRow
set record from cursor.FetchRow
set record to cursor.FetchRow()
set cursor !db.Query 'select * from names'
set cursor to db.Query('select * from names')
Neither **!** nor parenthesis are needed when the verb begins the line as in the `cursor.Close` statement below.
set cursor from db.Query 'select * from names'
set records from cursor.FetchAll
cursor.Close
If you wish to use **!** in a continuing expression, close the parameter clause with a semicolon (like you'd close a structural literal). The following two examples are equivalent.
set @x ~Math.sin($angle) * $magnitude
set @x !~Math.sin $angle; * $magnitude
set @x from ~Math.sin $angle; * $magnitude
At some point __from__ may be deprecated.
### != (not equal) _operator_
.. [expr] != [expr]
Compares two values for value equality; returns **true** if the values appear to be the same, **false** otherwise. This is implemented with Javascript’s != operator and the behaviour is identical.
Contrast with **isnt**.
### ' and " (string) _literal_
.. 'Type your string literal here.'
.. 'Use a backslash to escape \' and \" and \\ in your strings.'
.. "Double quote is semantically identical to single quote."
Declare a single line delimited literal string.
String literals support embedded expressions (a.k.a. string compositions) using the EcmaScript 6 syntax: `${expr}`. Any normal expression can be evaluated and will be concatenated in-place.
Use backslash as an escape for string delimiters `\'` and `\"`, dollar signs before braces `\${`, and the standard control characters `\n \r \t \b \f`.
debug 'This here\'s ${'a'} test using \${} composition.'
> This here's is a test using ${} composition.
### ''' (multi-line string) _literal_
.. '''
Now you can type as much text as you want,
remembering that leading and trailing whitespace
will be stripped, though newlines will be preserved.
So it's perfect for markup.
The string goes on as long as the indenting does.
And you can include ${'compo'+'sitions'} as well.
That’s three single quotes in a row, then an indented block.
Declare a multi-line string ideal for embedding longer passages of markup.
Blank lines are preserved, but indenting is not (this is a bug).
### 'd _syntax_
.. [verb]'d [value] (parameters)
An alternate way of writing:
.. from [value].[verb] (parameters)
This encourages a more conversational style of coding.
### 's _syntax_
.. [value]'s [attribute]
[object]'s verb
Equivalent to the use of `.` when accessing named attributes.
### \# (hashtag) _literal_ / _comprehension_
Declares or searches for a _hashtag_.
#### \#hashtag literal
.. traits #[tagname]
.. fields #[tagname]
When defining fields or traits, using a hash in front of a trait _name_ will automatically assign a value of **true** to that trait.
The following two definitions are identical:
set row1 to: name 'John', age 19, #cat, #dog
set row2 to: name 'John', age 19, cat true, dog true
#### \#hashtag comprehension
.. [collection] #[tagname] // require field to be truthy
.. [collection] !#[tagname] // require field to be falsy
When used as a comprehension, the hashtag creates a simple filter that returns only records that have a _truthy_ value for the named field.
The following two comprehensions are identical:
set results1 to friends #cat
set results2 to friends has .cat
A negated hashtag creates a filter that returns only records that have a _falsy_ value for the named field. Not having that field at all is also a match.
These comprehensions are identical:
set results1 to friends !#dog
set results2 to friends has not .dog
Hashtag comprehensions can be chained inline, and the filtering is done efficiently.
set results3 to friends #cat !#dog
### $ (parameter) _argument scoping prefix_
.. $
.. $[attribute]
When alone, **$** returns the first parameter a function was called with.
set ShowParameter to task
debug $
ShowParameter 'Bianca'
> Bianca
When followed immediately by an attribute name, that attribute of the first parameter in a function. (It is not necessary to include the dot.)
set ShowAttribute to task
debug $name
ShowAttribute friends[0]
> Sara
Even if you use **as** to name your parameters, **$** continues to refer to the first parameter.
set ShowParameters to task given p1, p2
debug $
debug p1
debug p2
ShowParameters 'First', 'Second'
> First
> First
> Second
### $$ (arguments) _pronoun_
.. $$
Equivalent to Javascript’s `arguments` and can be used the same way.
### $\{ } (string compose) _modifier_
.. 'String ${ [expr] } continues.'
.. "String ${ [expr] } continues."
.. `String ${ [expr] } continues.
.. '''
String ${ [expr] } continues.
Allows the insertion of any arbitrary expression in the middle of a string literal. As shown above, this is supported in all four string literal formats.
set FriendSummary to task
return 'Name: ${$name}, age: ${$age}'
ply friends limit 3 as friend
debug 'Subject ${key+1}: ${FriendSummary(friend)}'
> Subject 1: Sara, age 23
> Subject 2: John, age 19
> Subject 3: Ellie, age 22
Use `\${` to represent `${` in a string literal.
debug 'But I really need to include \${} in my output!'
> But I really need to include ${} in my output!
### % (modulo) _operator_
.. [expr] % [expr]
Calculates the mathematical **modulus**; returns the remainder of the left expression divided by the right.
debug 5 % 2
debug 6.2 % 1
> 1
> 0.2
### \* (multiply) _operator_
.. [expr] * [expr]
Multiplies the two expressions.
debug 2 * 3
> 6
### \*\* (exponent) _operator_
.. [lexpr] ** [rexpr]
Calculates an exponent; equivalent to `Math.pow([lexpr],[rexpr])`.
debug 2 ** 3
> 8
### + (plus) _operator_
.. [expr] + [expr]
Adds two numbers OR concatenates two strings. If both expressions are numbers, adds them. Otherwise converts them both to strings and concatenates.
debug 1+2
debug '1'+2
debug 1+'2'
debug '1'+'2'
> 3
> 12
> 12
> 12
### - (minus) _operator_
.. [lexpr] - [rexpr]
.. - [expr]
Used as a binary operator, subtracts the right expression from the left. Used as a unary operator, negates the expression (subtracts it from 0).
debug 3 - 5
debug 3 + -5
> -2
> -2
### . (attribute) _syntax_
.. [expression].[attribute]
.. .[attribute]
.. .[numeric array index]
[value].[attribute]
.[attribute]
.[numeric array index]
Looks up the trait named _attribute_ in the expression. When used without a leading value, references the named attribute in the **it** pronoun.
set friend to friends.0
debug friend.name
with friend
debug it.name
debug .name
// prints 'Sara' three times
Named attributes can also be referenced with `'s` because why not.
### .. (caboose) _style_
You can use the two-dot caboose to stitch a *single line* child code block onto the end of a statement that requires it. Don't use the caboose unless it will help make your code clearer.
The following are functionally identical:
if error
return -1
if error .. return -1
if error then return -1
See also __then__.
### ... (continue) _style_
You can use the three-dot continue syntax to insert line breaks, or multiple child code blocks, into a single statement.
This is not considered good practice (period), as most overly complex statements should be broken down into multiple statements (period), just like a run-on sentence should be clarified by a good editor.
But should you really need it, it is there.
The three dot continuation must start at the same indent as the line it continues.
debug traits
name Sally
age 32
province QC
... update traits
#cat
province ON
> { name: 'Sally', age: 32, province: 'ON', cat: true }
Or even:
debug 1
... +
... 2
... +
... 3
> 6
Syntactically, the continue is equivalent to a whitespace character with a bonus ability to reset level of indent.
### / (divide) _operator_
.. [lexpr] / [rexpr]
Divides the left expression by the right expression.
debug 22 / 7
debug 355 / 113
> 3.142857142857143
> 3.1415929203539825
### // (comment) _syntax_
// any text goes here, is ignored through the end of line.
Format of comments in SAI source files.
### : (declare structure) _literal_ / _clause_
To define an array, single line:
.. : [expr] (, [expr], [expr], ...) (;)
Multiple line variant:
.. :
[expr] ( , [expr], ... )
( [expr], [expr], ...
[expr]
... )
To define fields:
.. : [name] [expr], [name] [expr], [name] [expr], ... (;)
Multiple line variation:
.. :
[name] [expr] (, [name] [expr], ...)
( [name] [expr], [name] [expr], ...
[name] [expr]
... )
The **:** structure definition parser determines whether to create an array or fields by whether or not a field name is included before the first expression.
debug :1,2,3
> [ 1, 2, 3 ]
debug :a 1, b 2, c 3
> { a: 1, b: 2, c: 3 }
### ; (end structure) _syntax_
.. [structure definition] (;)
Optional, closes the current structure definition when needed for clarity. Only needed when there is additional code on the same line that might mistakenly bind too tightly to the final value in the structure.
debug list 3, 2, 1 by it
debug array 3, 2, 1 by it
debug array 3, 2, 1; by it
> [ 1, 2, 3 ]
> [ 3, 2, 1 ] // undesired result
> [ 1, 2, 3 ]
In the example above, the **list** literal sorts properly because **list** elements are not allowed to be mathematical expressions, so the parser can correctly bind `by it` to the entire list.
However, the first **array** literal _doesn’t_ sort correctly because array elements can be expressions, and `by it` is binding to the final term in the literal. Thus, the definition is parsed as `array (3), (2), (1 by it)`.
One can force **list** to parse incorrectly by using the **=** list element expression evaluation flag, but that’s ridiculous.
debug list =3, =2, =1 by it // don't write code like this
debug list =3, =2, =1; by it // just don't
> [ 3, 2, 1 ]
> [ 1, 2, 3 ]
The semicolon can also close parameter lists if using the **from** form of function invocation. The following examples are identical:
set x to ~Math.sin(angle) * magnitude
set x from ~Math.sin angle; * magnitude
### \< (less than) _operator_
.. [lexpr] < [rexpr]
Evaluates **true** if lexpr is numerically or lexically lower than rexpr, **false** otherwise.
debug 1 < 1 // false
debug 1 < 2 // true
debug 'a' < 'b' // true
debug 'a' < 'B' // false, case matters
If you want a case insensitive comparison, you must ensure both expressions are of the same case.
### \<\< (left shift) _operator_
.. [expr] << [bitcount]
Convert the expression into a 32 bit signed integer and perform a binary left shift *bitcount* times.
debug 4 << 1 // 8
debug -8 << 2 // -32
debug 2.8 << 0 // 2
### \<= (less or equal) _operator_
.. [lexpr] <= [rexpr]
Evaluates to **true** if lexpr is numerically or lexically lower or equal to rexpr, **false** otherwise.
### \<=\> (compare) _operator_
.. [lexpr] <=> [rexpr]
Evaluates to **-1** if lexpr is lower than rexpr, **1** if it is greater, and **0** if they are equal.
debug 1 <=> 0 // returns 1
debug 1 <=> 1 // returns 0
debug 1 <=> 2 // returns -1
debug 'b' <=> 'a' // returns 1
debug 'b' <=> 'b' // returns 0
debug 'b' <=> 'c' // returns -1
### = (equal) _operator_
.. [expr] = [expr]
Evaluates to **true** if the two expressions are equivalent. This compiles directly to Javascript’s `==` operator and has the same occasionally bizarre side effects. Basically, don’t use = except for comparing numeric, boolean or string values.
Use **is** or **isnt** to compare objects or object types, except when dealing with **NaN** in which case the only reliable way to test for its existence is to use **isNaN**. That’s Javascript for you.
### \> (greater than) _operator_
.. [lexpr] > [rexpr]
Evaluates to **true** if lexpr is numerically or lexically greater than rexpr, **false** otherwise.
### \>= (greater or equal) _operator_
.. [lexpr] <= [rexpr]
Evaluates to **true** if lexpr is numerically or lexically greater or equal to rexpr, **false** otherwise.
### \>\> (signed right shift) _operator_
.. [expr] >> [bitcount]
Convert the expression to a 32 bit signed integer, then shift it right bitcount times, maintaining the sign of the expression.
debug 8 >> 1 // 4
debug -32 >> 2 // -8
### \>\>\> (unsigned right shift) _operator_
.. [expr] >> [bitcount]
Convert the expression to a 32 bit *unsigned* integer, then shift it right bitcount times. Zeros are added from the left.
debug 8 >>> 1 // 4
debug -32 >>> 2 // 1073741816
### ? (safe fetch) _operator_
.. ? [expr]
Adds extra type-checking to an expression ensuring that it will not throw an exception if roots are undefined; instead just returning **undefined**.
set a to undefined
debug a.a
// throws an exception
debug ?a.a
> undefined
### ?\< (minimum) _operator_
.. [lexpr] ?< [rexpr]
Evaluates to whichever expression is numerically or lexically lower.
debug 1 ?< 2 // returns 1
debug 2 ?< 1 // returns 1
### ?\> (maximum) _operator_
.. [lexpr] ?> [rexpr]
Evaluates to whichever expression is numerically or lexically higher.
debug 1 ?> 2 // returns 2
debug 2 ?> 1 // returns 2
### ?? :: (if/this/that) _operator_
.. [expr] ?? [expr1] :: [expr2]
Evaluates to expr1 if the left expression is _truthy_, otherwise evaluates to expr2.
debug true ?? 'True' :: 'False' // prints 'True'
debug false ?? 'True' :: 'False' // prints 'False'
### [ ] (lookup) _syntax_
.. [value] '[' [expr] ']'
Performs an indirect lookup of a trait/element in the given value.
The following debug statements print identical results.
set field to 'name'
set friend to friends[0]
debug friend.name
debug friend['name']
debug friend['na'+'me']
debug friend[field]
// prints 'Sara' four times.
### @ (object) _this scoping prefix_
Ugly. Synonym for `me` and `my`, which see.
### @@ _scoping prefix_
Used inside called functions to refer to the "this" object in the scope of the call, rather than the scope of the function definition.
### ~ _global scoping prefix_
.. ~GlobalIdentifier
Use `~` in front of an identifier to access an object defined globally, e.g. by the runtime environment.
You must use `~` in each of the following examples:
set validated to ~parseInt(raw)
set rounded to ~Math.round(float)
~process.exit 1
set attendees to new ~Set
set func to new ~Function(arg1, src)
You do not need to use `~` on file-level pseudo-globals defined with `reference`; it is only required for functions/values/objects that originate outside SAI. This leads to the following shortcut which I don't recommend because it's unclear where Math comes from. Not that I can stop you.
reference:
Math ~Math
set rounded to Math.floor(float)
### \\ _lookup syntax_
.. [var]\[value]
A cleaner way of doing indirect lookups when only using a single literal or variable.
The following debug statements print identical results.
set field to 'name'
with friends.0
debug it.name
debug .name
debug it['name']
debug it\'name'
debug it[field]
debug it\field
// prints 'Sara' six times.
The `\` lookup syntax can only be used with simple values; that is, single variable names or literals. If you need to use an expression, you should use the `[expr]` form of lookup.
### \` (full line string) _literal_
.. `text
Signifies the start of a string literal that will occupy the rest of the source code line.
debug `It's nice not to have to worry about "delimiters."
> It's nice not to have to worry about "delimiters."
### and _operator_
.. [expr] and [expr]
Logical and operator. If the left expression evaluates to **falsy**, returns it, otherwise returns the right expression.
debug 1 and 0 // prints '0'
debug 1 and false // prints 'false'
debug 0 and 1 // prints '0'
debug false and 1 // prints 'false'
debug 1 and 1 // prints '1'
debug true and 'Fred'
> Fred
### andb _operator_
.. [expr] andb [expr]
Bitwise and operator. Converts, if possible, both left and right expressions into 32 bit signed integers, and performs a bitwise AND operation on them.
debug 5 andb 11
> 1
// 5 in binary: 00000000000000000000000000000101
// 11 in binary: 00000000000000000000000000001011
// 1 in binary: 00000000000000000000000000000001
### array _literal_
.. array [expr], [expr], ... (;)
.. array
[expr], [expr], ...
[expr]
...
Used to specify the creation of a plain array of mathematical values; e.g. the result of a series of expressions. As opposed to **list**, which is a plain array of bare literals.
In general, the **colon** structure constructor will figure out what you want, but when you want to be specific about creating an array of expressions, use **array**. Compare with **list**, **fields** and **traits**.
Arrays may be specified on one line:
debug array 1+1, 2*3, 'Fred'
> [ 2, 6, ‘Fred’ ]
Or multiple lines in the form of an indented block:
debug array
width * ~Math.cos(angle)
height * ~Math.sin(angle)
> [ 7.0710678118654755, 3.5355339059327373 ]
Or a combination of both:
debug array
1, 1, 2, 5
14, 42, 132, 429
1430, 4862
When using an array literal in an expression that might make the end of the array a matter of question, use a **semicolon** to close the array literal:
debug array 5, 3, 2, 7, 4; has it%2
> [ 5, 3, 7 ]
Or enclose the array in parenthesis:
debug (array 4, 3, 2, 1) by it
> [ 1, 2, 3, 4 ]
Arrays can be nested by use of either parenthesis or semicolons, or by using multiple levels of indent. Note that commas separate expressions on one line but are not included at the end of a line.
debug array
array 1, 2, 3;, array 4, 5, 6
array
7, 8, 9
> [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]
### as _clause_
As declares named locally scoped variables within a code block based on values passed into the block. (In general, **as** is optional because one can usually access such passed-in values through pronouns.)
**As** is used in conjunction with the following SAI keywords: set, task, process, promise, switch, catch, if, exists, with, iterate, each, ply, count, by, thru, audit, gather, has, set, observe, and within parenthesis as the “parenthetic as”.
#### as with parameters
Note that using __as__ to describe function parameters is now deprecated, please use __given__ instead.
set [ident] (as [$ var]) // dynamic trait
.. task (as [parameter1], [parameter2], ...)
.. process (as [parameter1], [parameter2], ...)
.. promise (as [parameter1], [parameter2], ...)
An example of parameter naming:
set tally to blank
set AddRow to task given item, quantity
set tally[item] to (self default 0) + quantity
AddRow 'socks', 3
AddRow 'pants', 6
debug tally
> { socks: 3, pants: 6 }
#### as value reclaimation
switch [expr] (as [trial var])
catch (as [error var])
if [expression] (as [trial var])
exists [expression] (as [it var])
with [expression] (as [it var])
An example of **if** value reclamation with **as**:
if @keydown as charcode
switch charcode
case 27
@Escape
case 32
@Continue
#### as with iterators
iterate [expr] (as [it var])
each [expr] (as [it var] (, [key var] ) )
ply [expr] (as [it ident] (, [key var] ) )
count [expr] (as [key var])
An example, renaming **it** and **key** in an each iterator:
each friends first as value, field
debug `${field}: ${value}
> name: Sara
> age: 23
> cat: true
> province: ON
#### as with comprehensions
[expr] by (as [a var], [b var] )
[expr] thru (as [it var] (, [key var] ) )
[expr] audit (as [it ident] (, [key var] ) )
[expr] into [expr] (as [sum var] (, [it var] (, [key var] ) ) )
[expr] filter (as [it var] (, [key var] ) )
[expr] set (as [it var])
[expr] observe (as [it var])
An example, renaming **it** and **key** in the block handler of a **thru** comprehension:
debug friends thru as friend, index
return `${index}) ${friend.name} lives in ${friend.province}
> [ '0) Sara lives in ON',
'1) Jon lives in QC',
'2) Ellie lives in QC',
'3) Marshal lives in ON',
'4) Doug lives in ON',
'5) Ann lives in QC',
'6) Harry lives in QC',
'7) Jenna lives in ON' ]
#### as parenthetic
.. ( [expr] as [var] )
The parenthetic **as** assigns the value of the parenthesised expression to a named identifier. The assignment happens as soon as the parethesis is evaluated, so you can use the identifier in the same expression as the parenthetical, as long as the parenthetical is evaluated first. _This is not a good style of coding_.
set six to (1+2 as three)+three
debug array three, six
> [ 3, 6 ]
### assert _verb_
assert [expr], [expr]
If the first expression is _falsy_, throw an exception with the second expression as a message.
assert everythingIsAwesome, 'I have some bad news...'
Error: SAI: failed assertion: I have some bad news...
### bind _operator_
.. bind [function reference]
The __bind__ operator _binds_ a function reference to the current object context. This is useful when you need to pass in a member function to a callback, and need to ensure the execution context (specifically, the _this_ object) isn't lost.
Why is this important?
Object car
instance: plate '876 ACET'
CheckPlate task
DB.lookupPlate plate, PlateResults
PlateResults task given status
debug 'Status for ${plate}: ${status}'
In the above function definition of `CheckPlate` the `DB.lookupPlate` function is an asynchronous call with a plate number and a callback function. As written, when the callback is invoked, `PlateResults` will have been unbound from its object; it is a function reference with no context.
The code will produce this result:
Looking up 876 ACET
Status for undefined: Valid
Although the test was successful, we have _lost context_ on the callback because `PlateResults` is passed in as an unbound function. Referring to the member trait `plate` within an unbound context produces an undefined result. In order to make this function reference work, we need to bind it to the current object with the __bind__ operator, as follows:
DB.lookupPlate plate, bind PlateResults
Now when we run the asynchronous function, it works as expected, because the callback has its context; e.g. it knows where it is going (or, more accurately, where it is returning to).
Looking up 876 ACET
Status for 876 ACET: Valid
Note that __bind__ is only necessary when passing references to object member functions. Functions you define in-place won't have this issue, as they create their own context. This version of __bind__ only binds functions to the current object. If you want to bind a function on some other object, you'd write code something like this, making use of the Function object's bind ability:
... from anObject.anyFunction.bind anObject
### blank _literal_
.. blank
The keyword **blank** creates a plain object with no traits. It is the SAI equivalent of Javascript’s `{}`.
set player to blank
set player.age to 21
debug player
> { age: 21 }
### break _statement_
[loop/iterator]
[code]
break
switch
case [expr]
break
default
break
To exit a loop, iterator or switch case before its natural end, use the **break** keyword.
count 5
debug it
if it=2
debug 'Nevermind...'
break
> 1
> 2
> Nevermind…
### case _clause_
switch [trial expr] ( as [trial ident] )
case [match expr] (, [match expr] (, ...) )
[code]
case [match expr]
[code]
default
[code]
...
Use one or more **case** clauses within a **switch** statement to specify one or more matching expressions to be tested against with the trial expression.
switch keystroke
case 'n', 'N'
@move: x 0, y -1
case 'e', 'E'
@move: x 1, y 0
case 'w', 'W'
@move: x -1, y 0
case 's', 'S'
@move: x 0, y 1
case ' '
@skipturn
default
@display 'Use n/e/s/w to move, space to pause.'
Unlike Javascript, execution within a **case** statement does not fall through to the next case, so you don’t need to add **break** at the end of a code block.
### catch _clause_
try
[code]
catch ( as [error ident] )
[code]
finally
[code]
The **catch** clause heads a section of code that executes if and only if any code within the **try** clause throws an exception.
The exception thrown is caught and placed in the **error** pronoun (or the variable named in the **as** clause), as follows:
try
throw new ~Error 'This is an error message!'
catch
return error.message
Alternately:
try
throw new ~Error 'This is another error message!'
catch as e
return e.message
For details on how try/catch/finally exceptions work, see the Jasacript exception documentation.
### chain _operator / verb_
#### chain as an operator
.. chain [expr]
[comprehension/method]
[comprehension/method]
...
The **chain** clause allows you to compose (string together) a series of operations that will each be applied in turn to a value, object, collection or iterator.
**Chain** is another way of writing `value.method().method().method().method()` that offers cleaner code and more possibilities. You start with an object, then apply a sequence of verbs to it. Each verb is a new link in the chain.
set mirror to task
return chain $
split ''
reverse
join ''
debug mirror('A man, a plan, a canal, Panama!')
> !amanaP ,lanac a ,nalp a ,nam A
In the previous case, each verb was a trait (a method) of the string itself.
You can also chain comprehensions:
debug chain friends
#cat
by .name
thru 'My friend ${.name} likes cats.'
> [ 'My friend Ann likes cats.',
> 'My friend Jon likes cats.',
> 'My friend Sara likes cats.' ]
And because one of the comprehensions is **set**, you can actually chain any function at all.
set double to task
return chain empty
concat $
concat $
debug chain fruit
set double(it)
> [ 'Apple',
'Banana',
'Citron',
'Apple',
'Banana',
'Citron' ]
Functions you use in **chain** typically return a value; this is used as the object to pass to the next link in the chain. However, some methods and functions don't return a value, instead modifying their context in-place. If a function returns **undefined**, **chain** will reuse the previous object for the next call.
_Be careful: some built-in methods return unexpected values that can foil **chain**. For example, **Array.push** is very irritating for this; you cannot chain **.push** because it returns the new array length instead of either the array or **undefined**._
#### chain as a verb
You can also use `chain` as a verb, that is, at the beginning of a line. This is useful when the end result of the chain isn't useful but the intermediate results are, which can often be the case when parsing data.
set population empty
chain friends
audit
inc population[.province] to (self default 0) + 1
debug population
> [ ON: 4, QC: 4 ]
Arguably it might be better to code this particular example with a discrete iterator statement like `ply`, but not every use case is as trivial as this example.
_Note: If you use chain as a verb in this way, and the final result of the chain is a generator, it will be drained automatically._ Otherwise, the code within the chain may never be executed, because without a drain, generators do nothing.
### continue _statement_
[iterator/loop]
[code]
continue
In a loop or otherwise iterating block of code, **continue** short-cuts the remaining code in the block, causing the loop to continue its next iteration immediately.
count 10
unless key % 2
continue
debug key
> 1
> 3
> 5
> 7
> 9
### contract _declaration_
contract:
[task/trait name]
[task/trait name]
...
When defining an object, **contract** is used to specify tasks or traits that _children_ inheriting from this object are intended to implement.
Inheriting from an object that has contracts, and then not providing implementations for those contracts, will result in a SAI exception.
object fruit
contract:
Consume
object apple
inherit:
fruit
Consume task
debug 'You ate an apple.'
object pear
inherit:
fruit
> exception thrown: “SAI: Contractually required task ‘Consume’ does not exist in object ‘pear’.”
### copy _operator_
.. copy [expr]
Use the **copy** unary operator to create a _shallow copy_ of the expression it precedes. You sometimes need this because ordinarily SAI and Javascript assign objects, rather than copies of object values.
set plate to fruit // i want some fruit
plate.push 'Ice cream' // and also dessert
debug fruit // but now fruit has changed
> [ 'Apple', 'Banana', 'Citron', 'Ice Cream' ]
fruit.pop // get rid of the ice cream
debug fruit
> [ 'Apple', 'Banana', 'Citron' ]
set plate to copy fruit
plate.push 'Ice cream'
debug fruit
> [ 'Apple', 'Banana', 'Citron' ]
debug plate // there we go
> [ 'Apple', 'Banana', 'Citron', 'Ice Cream' ]
Note that **copy** only copies enumerable values.
### count _statement_ / _comprehension_
#### count as a looping control statement
count [high expr] ( as [counter ident] )
[block]
( else
[none block] )
// variants
count [low expr], [high expr] ( as [counter ident] )
count [low expr], [high expr], [step amount expr] ( as [counter ident] )
countdown [high expr] ( as [counter ident] )
countdown [high expr], [low expr] ( as [counter ident])
countdown [high expr], [low expr], [step expr] ( as [counter ident])
Specifically designed as an integer iterator for list elements, **count** always counts 1 at a time (unless a **step** clause is provided), and its highest value is always the step value less than the high value provided (both when counting up and down). To reiterate, **count** _never_ outputs the high value.
**Count** uses the pronoun **counter** for the value.
count 10
debug counter
// prints numbers from 0 to 9
count 5, 10
debug counter
// prints numbers 5 to 9
countdown 10 as i
debug i
// prints numbers from 9 to 0
countdown 25, 15
// prints numbers 24 to 15
count 0, 50, 10
// prints 0, 10, 20, 30, 40
countdown 45, 0, -10
// prints 35, 25, 15, 5
When using a step with **count down**, be sure that the step expression is negative, otherwise an infinite loop will result.
count aList.length
debug 'List element ${counter} is ${aList[counter]}'
else
debug 'The list is empty.'
The optional **else** clause is executed instead of the main block if the **count** range length computes to 0 (or less).
### counter _pronoun_
.. counter
A partner to **count**, **counter** is a _pronoun_, populated by the **count** iterator, if no other variable is specified in an **as** clause. Like all pronouns, **counter** is only visible in attached expressions or code blocks, not in any functions that may be called within those expressions.
A complete list of **counter** enabled events:
count [expr] // key: the number being counted
An example:
ply friends
debug key
// prints: 0 1 2 3 4 5 6 7
each friends[0]
debug key
// prints: name age cat province
Similar to **it** in nested contexts, you are unable to access “outer” values of **counter** within inner contexts unless you assign them to a name other than **counter** using the **as** clause.
count 3
count 3
debug counter
// only the inner loop's 'counter' is visible here.
// prints 0 1 2 0 1 2 0 1 2
count 3 as i
count 3
debug i+','+counter
// outer loop's 'counter' now available here as 'i'
// prints 0,0 0,1 0,2 1,0 1,1 1,2 2,0 2,1 2,2
count 3
count 3 as j
debug counter+','+j
// no, this doesn't work, you still can't access the outer 'counter'
// 'counter' always has the value of the innermost context
// prints 0,0 1,1 2,2 0,0 1,1 2,2 0,0 1,1 2,2
### create _operator_
.. create [expr] [parameters]
Creates a SAI object by name.
Will attempt to find the object’s source by using the `SAI.config.Loader` function, which defaults to `SAI.GetSourceFromPaths`, which tries to find a file named `[expr].sai` in the provided paths.
reference:
Tally 'Tally ^1.0.0'
..
set inventory to create Tally
The example follows best practices of placing object names in a **reference** section, aliasing versioned names into literals.
See also __new__, which is a direct link to Javascript's `new`, and __singleton__, which works like __create__ except instantiates the object only once in the scope of the runtime library; subsequent calls will return that same object.
### debug _adhoc verb_
debug [expr]
Prints the value of the expression to the console.
Compiles directly to `console.log([expr])`.
### dec _statement_
dec [var]
Decrements (reduces by 1) the value in the given variable.
set a to 2
dec a
debug a
> 1
### default _operator_
#### default in expressions
... [lexpr] default [rexpr]
If and only if the left expression evaluates to `undefined`, returns the right expression, otherwise returns the left expression.
Like **or** except instead of checking for _truthy_ tests to see if a value is available. Most useful in expressions where you’re not sure if a variable is initialized.
set
magnitude to undefined
angle to 60o
debug (magnitude default 1) * ~Math.cos(angle)
> 0.5
#### default in **set** statements
set [lexpr] default [rexpr]
If the left expression is `undefined` then evaluate the right expression and assign it to the left expression. For example:
set calculationCache[key] default ExpensiveCalculation(key)
This checks to see if the calculation cache has a value for the given key, and if not, calculates the value and assigns it to the cache.
### delete _statement_ / _operator_
delete [variable]
set [variable] delete
set [variable] delete [attribute(s)]
... [expr] delete [attribute(s)]
When called on a variable or traits, undefines the named variable or attribute(s) of a variable.
This just calls the underlying Javascript version of **delete**, with all the same rules and caveats. The only clever addition is the ability to delete multiple attributes at once by supplying a collection:
set obj to: a 1, b 2, c 3, d 4, e 5, f 6
set obj delete 'a'
debug obj
> { b: 2, c: 3, d: 4, e: 5, f: 6 }
set obj delete: 'c', 'f'
debug obj
> { b: 2, d: 4, e: 5 }
set obj delete: b 'B is for Bonobo.', d 'D is for Dixie cups.'
debug obj
> { e: 5 }
When **delete** operates on an array, it returns a copy of the array with the elements specified removed.
debug (list 1,2,3,4,5) delete 1
> 1, 3, 4, 5
debug (list 1,2,3,4,5) delete (list 1,2)
> 1, 3, 5
Elements are removed in first->last order, so sequence does matter.
debug (list 1,2,3,4,5) delete (list 2,1)
> 1, 4, 5
### do _modifier_
dowhile [expr]
[code]
dountil [expr]
[code]
Alters a **while** or **until** statement so that the test is performed after the code block, not before. This guarantees the code block will be executed at least once.
Check out these wacky examples.
dountil true
debug 'Hello, World!'
dowhile false
debug 'Hello, World!'
Note there's no space in there. It's __dowhile__ and __dountil__. Makes parsing easier when it's one word.
### down _modifier_
count down [start]
count down [start] to [stop]
Used only with **count**; signifies the count is to proceed by _decrementing_ the key. The first key value is the start value minus one, and the last key value is the stop value, or zero.
count down 5
debug key
> 4
> 3
> 2
> 1
> 0
**Down** can seem freaky because you never get the value you begin with, and it seems to be even freakier when you're using a step value other than 1.
count down 15 step -4
debug key
> 11
> 7
> 3
Here's an important safety tip, illustrated above: if you're using **down** with **step**, be sure the step value is negative. Otherwise the loop will never terminate.
See also: **count** and **step**.
### each _statement_
each [collection] ( as [it var] ( , [key var] )
[code block]
( else
[code] )
each [collection] using [function]
**Each** uses Javascript's `for-in` loop construct to iterate through an object's enumerable properties. It steps through each element of the collection, in arbitrary order, setting **it** and **key** variables, and executing the code block for each one. If the collection is empty, executes the (optional) **else** block instead.
set friend to friends first
each friend
debug `${key}: ${it}
else
debug `You have no friends.
> name: Jon
> age: 19
> cat: true
> dog: true
> province: QC
Recall that you can rename **it** and **key** with the **as** clause:
each friend as value, field
debug `${field}: ${value}
#### each ... using
**Each** can also call out to a function with the **each using** variation.
set handler to task given value, field, collection
debug `${field}: ${value}
each friend using handler
The function is called for each attribute of the collection with three parameters:
- attribute value (e.g. __it__)
- attribute name (e.g. __key__)
- reference to the collection itself
### else _clause_
if [expr]
/ unless [expr]
/ exists [expr]
/ each [expr]
/ ply [expr]
/ iterate [expr]
/ count [expr]
[block]
else
[block]
Specifies a block of code that will be executed if the condition leading to the previous block fails. Can be used with conditional statements **if/unless/exists** as well as iterating loops **each/ply/iterate/count**.
if b
debug 'b is truthy'
else
debug 'b is falsy'
each obj
debug '${key}: ${it}'
else
debug 'No traits.'
### elsexists _clause_
if ...
elsexists [expr] (as [identifier])
[block]
Combines __else__ and __exists__.
### elsif _clause_
if ...
elsif [expr] (as [identifier])
[block]
Combines __else__ and __if__.
### elsunless _clause_
if ...
elsunless [expr]
[block]
Combines __else__ and __unless__.
### empty _literal_
.. empty
A literal value indicating an empty list/array. The equivalent of Javascript’s `[]`, **empty** creates an Array with no elements.
### enum _modifier_
fields [identifier] enum, ( [identifier] enum, ... )
When declaring a set of fields, specifies a value 1 higher than the value of the previous definition. If there is no previous definition, 1.
debug fields a enum, b enum, c 10, d enum
> { a: 1, b: 2, c:10, d: 11}
### Error _global object_
Refers directly to the native Javascript error object, as in:
throw new Error "message"
This has been added strictly as a convenience.
### error _pronoun_
.. error
Valid only within the **catch** exception handler clause of a **try/catch/finally** construct. Receives the exception thrown.
try
noFunction
catch
debug error
> [TypeError: undefined is not a function]
You can override this behaviour by using an **as** clause with the **catch** statement:
try
noFunction
catch as e
debug e
> [TypeError: undefined is not a function]
### every _statement_
every [collection] ( as [it var] ( , [key var] )
[code block]
( else
[code] )
every [collection] using [function]
**Every** is a general-purpose iterator that can handle any collection -- array, object or iterable -- or a even single value (which case it executes the block once).
It steps through each element of the collection, in order if possible (array, iterable), in random order otherwise (objects), setting **it** and **key** pronouns, and executing the code block.
The **key** pronoun will be the index number for arrays, the attribute name for objects, or a row counter for iterables. It will be `0` for the single pass granted a single value.
If the collection is empty, executes the (optional) **else** block instead.
set friend to friends first
every friend
debug `${key}: ${it}
else
debug `You have no friends.
> name: Jon
> age: 19
> cat: true
> dog: true
> province: QC
Recall that you can rename **it** and **key** with the **as** clause:
every friend as value, fieldname
debug `${fieldname}: ${value}
_Note:_ there is a slight performance penalty associated with using __every__ instead of a more specific iterator, as SAI must wrap the collection in a thin iteration in order to generically handle all cases. It is, however, very useful when you are not sure what kind of collection you'll be working with.
#### every ... using
**Every** can also call out to a function with the **every ... using** variation.
set handler to task given value, field
debug `${field}: ${value}
every friend using handler
The function is called for each attribute of the collection with two parameters:
- attribute value (e.g. __it__)
- attribute name (e.g. __key__)
### exists
exists [expr] ( as [it ident] )
[code]
else
[code]
Executes the indented code if the expression is a defined value, e.g. if it is not **undefined**. The tested value will be available as **it** within the scope of the code block. (This is different from the _truthy_ testing **if**, which uses **trial**.)
**Exists** is quite handy when working with caches and lookups.
exists @cache[$key]
return it
else
set @cache[$key] from @ExpensiveCalculation $key
return @cache[$key]
Though an even slicker way of dealing with that particular idiom is to use **default**:
set @cache[$key] default from @ExpensiveCalculation $key
return @cache[$key]
### expects _clause_ / _operator_
... task/promise/process expects [rules definition]
.. [expr] expects [rules object]
Used to check whether a function’s named parameters (or any arbitrary object) has certain traits, and optionally if those traits are of a particular type.
#### expects clause
When used as the clause in a **task**, **promise** or **process** function definition. **expects** adds run-time parameter checking to the function, passing the first argument (the one that holds named parameters) through an *expects check* based on the rules that follow. If any expectations are not met, a runtime error is thrown.
Names appearing in **expects** clauses, and thus being used to check parameters, _must_ be preceeded by **$**. This is intended as a reminder that *named parameters are always referenced with the $ scoping operator*.
#### expects operator
Expects compares an object againt a set of rules and returns a list of rules that are **violated**. Thus, a successful test of expectations is an empty array. An array with one or more elements contains a list of the rules that were broken.
#### expects rules
Rules for **expects** take the form of a set of traits. Their names correspond to names of traits that must be found in the expression under test. The value, if not _true_, is interpreted as an object type; the trait must be of that type (or a child of that type).
Any violations to the rules are returned in a list; that list being made up of plain objects with the following fields:
.trait - name expected
.expects - type expected
.found - type found
Here are some examples, finally:
AddStudent task expects $name string, $age number
@students.push copy $
..
@AddStudent name 'Sally', age '12'
> Error: SAI: parameter exception in AddStudent
> age should be number, but it's string
Performing this same test in an ad-hoc fashion using the **expects** operator would look like this:
set newStudent to: name 'Sally', age '12'
set studentRules to: name 'string', age 'number'
debug newStudent expects studentRules
> [ { trait: 'age', expects: 'number', found: 'string' } ]
N.B. the **expects** clause rule format is identical to that of a single-line **traits** definition, with the additional requirement of a **$** before every trait name.
### false _literal_
.. false
The boolean value **false**.
### finally
try
[block]
catch ( as [error ident])
[block]
finally
[block]
An exception handling clause identifying a block of code that is executed whether or not an exception occurs. (Even executes if you re-**throw** an exception within the **catch** clause.)
For details, see the Jasacript exception documentation.
### fields _literal_
.. fields [key] [expr], [key] [expr], ... (;)
.. fields
[key] [expr], [key] [expr], ...
[key] [expr]
...
Used to specify the creation of a plain object with a set of key/value pairs. (Contrast with **traits**.) In general, the **colon** structure constructor will figure out what you want, but when you want to be specific about creating a plain object with of keyed values & expressions, use **fields**. Compare with **list**, **array** and **traits**.
The **key** is an identifying word or other string, specified without quotes (although quotes may be used if desired/necessary). If a **\\\\\\\\\\\\#** preceeds the key (a hashtag), the key will be assigned a value of **true**. The **expr** is any valid literal, variable, object or expression.
Fields may be specified on one line:
fields name 'Sera', class 'Rogue', level 21, #focus
> { class: 'Rogue', level: 21, name: 'Sera', focus: true }
Or multiple lines in the form of an indented block:
fields
x width * ~Math.cos(angle)
y height * ~Math.sin(angle)
> { x: 0.5, y: 0.866 }
Or a combination of both:
fields
str 16, dex 34, mag 14
con 42, wil 21, cun 45
> { str: 16, dex: 34, mag: 14, con: 42, wil: 21, cun: 45 }
When using a fields literal in an expression that might make the end of the literal a matter of question, use a **semicolon** to close the literal, or enclose it in parenthesis:
fields x 5, y 4, z 3; thru it*2
(fields x 5, y 4, z 3) thru it *2
> { x: 10, y: 8, z: 6 }
Fields initializers can be nested by use of either parenthesis or semicolons, or by using multiple levels of indent.
### from _operator_
.. from [identifier] ([parameters]) (;)
**From** invokes the identifier as a function call that returns a value, passing in any parameters that follow. It is the preferred syntax for function calls that include indents, as it is difficult to close a parenthesis.
The following pairs are synonymous:
set record from cursor.FetchRow
set record to cursor.FetchRow()
set cursor from db.Query 'select * from names'
set cursor to db.Query('select * from names')
**From** is the encouraged form for **set** statements because it allows a more natural reading of source code. __From__ indicates that the identifier that follows will be used as a verb and returning a value.
Neither **From** nor parenthesis are needed when the verb begins the line as in the `cursor.Close` statement below.
set cursor from db.Query 'select * from names'
set records from cursor.FetchAll
cursor.Close
If you wish to use **from** in a continuing expression, close the parameter clause with a semicolon (like you'd close a structural literal). The following two examples are equivalent.
set @x to ~Math.sin($angle) * $magnitude
set @x from ~Math.sin $angle; * $magnitude
### get _declaration_
object [name] [version]
[ident] get
[code]
return [value]
( set ( given [ident] )
[code]
Declares a _getter_ for a dynamic object trait; that is, a trait that has a value that is only calculated upon request.
object Vector2 1.0.0
magnitude get // dynamic getter for 'magnitude' trait
return from ~Math.sqrt (@x * @x) + (@y *