sai-language
Version:
An object-oriented language designed to afford code comprehension and maintenance. Transpiles in-place to Javascript.
1,017 lines (572 loc) • 26 kB
Markdown
#
This file is automatically generated. _Do not edit._
Unknown Heading
### % - _modulus_ - operators - ^%
Calculates the mathematical **modulus**; returns the remainder of the left expression divided by the right.
.. [expr] % [expr]
Examples:
debug 5 % 2
debug 6.2 % 1
> 1
> 0.2
### * - _multiplication_ - operators - ^*
Multiplies two expressions.
.. [expr] * [expr]
As in:
debug 2 * 3
> 6
### ** - _exponentiation_ - operators - ^**
Raises one value to the power of another.
.. [lexpr] ** [rexpr]
Calculates an exponent; equivalent to `Math.pow([lexpr],[rexpr])`.
debug 2 ** 3
> 8
### + - _addition / string concatenation_ - operators - ^+
summary
.. [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
### - - _subtraction/negation_ - operators - ^-
summary
.. [lexpr] - [rexpr]
.. - [expr]
Used as a binary operator, subtracts the right expression from the left.
debug 3 - 5
> -2
Used as a unary operator, negates the expression (subtracts it from 0).
set b to 5
debug 3 + -b
> -2
SAI will not let you write the following;
set a - 3
Because that could be parsed one of two ways:
- place the value `-3` in the variable `a`
- subtract `3` from the variable `a`
If you try to write code like this, a compiler error will result. Choose one of these
formats instead:
set a to -3 // store -3 in a
set a self - 3 // subtract 3 from a
Just a little gotcha.
### / - _mathematical division_ - operators - ^/
Divides the expression on the left by the expression on the right.
.. [expr1] / [expr2]
As in:
debug 22 / 7
debug 355 / 113
> 3.142857142857143
> 3.1415929203539825
### < - _less comparison_ - operators - ^<
Evaluates **true** if lexpr is numerically or lexically lower than rexpr, **false** otherwise.
.. [lexpr] < [rexpr]
For example:
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.
### << - _bitwise left shift_ - operators - ^<<
Convert the expression to a 32 bit integer, then perform a binary left shift bitcount times.
.. [expr] << [bitcount]
.. [expr] lsh [bitcount] // synonym
See its synonym __lsh__.
### <= - _less or equal comparison_ - operators - ^<=
Evaluates to **true** if lexpr is numerically or lexically lower or equal to rexpr, **false** otherwise.
.. [lexpr] <= [rexpr]
You know how this works.
### <=> - _comparison_ - operators - ^<=>
Evaluates to **-1** if lexpr is lower than rexpr, **1** if it is greater, and **0** if they are equal.
.. [lexpr] <=> [rexpr]
For example:
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
Useful in sorting operations!
### <> - _inequality comparison_ - operators - ^<>
Tests two values for value inequality.
.. [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**.
### = - _equality comparison_ - operators - ^=
Evaluates to **true** if the two expressions are equivalent.
.. [expr] = [expr]
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 comparison_ - operators - ^>
Evaluates **true** if lexpr is numerically or lexically greater than rexpr, **false** otherwise.
.. [lexpr] > [rexpr]
For example:
debug 1 > 1 // true
debug 1 > 2 // false
debug 'a' > 'b' // false
debug 'a' > 'B' // true, case matters
If you want a case insensitive comparison, you must ensure both expressions are of the same case.
### >= - _greater or equal comparison_ - operators - ^>=
Evaluates **true** if lexpr is numerically or lexically greater than or equal to rexpr, **false** otherwise.
.. [lexpr] >= [rexpr]
You know the drill.
### >> - _signed bitwise right shift_ - operators - ^>>
Convert the expression to a 32 bit signed integer, then shift it right bitcount times,
maintaining the sign of the expression.
.. [expr] rsh [bitcount]
.. [expr] >> [bitcount] // synonym
See __rsh__.
### >>> - _unsigned bitwise right shift_ - operators - ^>>>
Convert the expression to a 32 bit unsigned integer, then shift it right bitcount times,
Zero bits are added on the left side (msb), which does not preserve negative sign.
.. [expr] ursh [bitcount]
.. [expr] >>> [bitcount] // synonym
See __ursh__.
### ?< - _minimum_ - operators - ^?<
Evaluates to whichever expression is numerically or lexically lower.
.. [lexpr] ?< [rexpr]
Kind of like Math.min except as an operator.
debug 1 ?< 2 // returns 1
debug 2 ?< 1 // returns 1
Handy when you need it.
### ?> - _maximum_ - operators - ^?>
Evaluates to whichever expression is numerically or lexically higher.
.. [lexpr] ?> [rexpr]
Kind of like Math.max except as an operator.
debug 1 ?> 2 // returns 2
debug 2 ?> 1 // returns 2
Of course it only operates on two values at a time. But that's cool.
### ?? :: - _trinary conditional_ - operators - ^?? ::
Evaluates to expr1 if the left expression is _truthy_, otherwise evaluates to expr2.
.. [expr] ?? [expr1] :: [expr2]
I get the grar about the ?: operator, it can make for unclear logic. But I've found
with this _doubled up_ version that it's much easier to see when it's used, especially
if you keep spaces on either side of the operators.
debug true ?? 'True' :: 'False' // prints 'True'
debug false ?? 'True' :: 'False' // prints 'False'
It's still kind of not great, but on the other hand there are times when five lines of code
could be reduced to one without losing much clarity. And those are the times for ??::.
### AND - _logical and_ - operators - ^and
Checks two or more values; if they're all _truthy_, return a _truthy_ value.
Otherwise returns a _falsy_ value.
.. [expr] and [expr]
.. and [list of expressions]
As a binary operator, if the left expression evaluates to **falsy**, returns it,
otherwise returns the right expression.
Binay operator examples:
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
As a unary operator given a list, returns the right-most element of the list if all the
elements in the list are _truthy_, otherwise returns the first _falsy_ element.
Unary operator examples:
debug and 'frog', 2, true
debug and 'toad', false, 'mario'
> true
> false
The list can be split on more than one line, indented:
debug and:
'hello', true
'this', 4, 88
lookup('thing')
> [ result of call to lookup ]
You can nest logical operators on multiple lines:
debug and:
or test1(2), test2(3)
or fallback(1), fallback(2)
It can get very clunky to try and use multi-line logical expressions with IF statements.
Instead, assign the results of your test to a clearly named variable that describes your
test and then use that in your IF statement:
set buildOk to and:
fileExists(fn)
fileDate(fn) > lastBuild\fn
not alreadyBuilt\fn
extensionhandler[PATH.extension(fn)]
if buildOk as hdlr
hdlr.build fn // rule about AND: last element in the list if all are true.
### ANDB - _bitwise and_ - operators - ^andb
Bitwise and operator. Converts, if possible, both left and right expressions into 32 bit signed integers, and performs a bitwise AND operation on them.
.. [expr] andb [expr]
Examples:
debug 5 andb 11
> 1
// 5 in binary: 00000000000000000000000000000101
// 11 in binary: 00000000000000000000000000001011
// 1 in binary: 00000000000000000000000000000001
### BIND - _function context binder_ - operators - ^bind
The __bind__ operator _binds_ a function reference to the current object context.
.. bind [function reference]
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:
... !anObject.anyFunction.bind anObject
SAI compiled objects do a runtime check on any member method invocation to verify that the
current execution context is, if not correct, at least not totally absent.
### COPY - _shallow copy_ - operators - ^copy
Use the **copy** unary operator to create a _shallow copy_ of the expression it precedes.
.. copy [expr]
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 attributes. There's a rabbit hole for ya.
### CREATE - _object creation_ - operators - ^create
Creates a SAI object by name.
.. create [expr] [parameters]
If we are running in dynamic mode (that is, with source that is being dynamically compiled from
.SAI files), __create__ 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` i
n the provided paths.
In runtime mode (that is, when running from plain Javascript .js files that have been pre-compiled),
__create__ will call `require`, but do see the source in the `sai-library` that discusses this.
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.
### DEFAULT - _variable initialization_ - operators - ^default
Examines the expression on the left; if it has a value, returns it. Otherwise returns the value on the right.
... [lexpr] default [rexpr]
set [lexpr] default [rexpr]
#### default in expressions
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 present.
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
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.
### INITIALIZED - _default with assignment_ - operators - ^initialized
Examines the variable on the left; if it has a value, returns it. Otherwise calculates the value
on the right, stores it in the variable on the left, and returns it.
... [lexpr] initialized [rexpr]
The __initialized__ operator works very much like the __set/default__ combination, but allows
you to use it in the middle of an expression. You should only use variables on the left,
as trying to assign into a function or calculated expression will not work.
The only real use for this is in scenarios where you would like to cache the result of an
expression and want the code to reflect this simple idea.
Here is a trivial example:
GetCachedThing task with identifier
return thingCacheidentifier initialized from thingComputation identifier
### IS - _equivalence test_ - operators - ^is
Returns **true** if the two expressions are completely indistinguishable.
.. [expr] is [expr]
More rigorous than **=** when objects differ in type.
debug 0 = false
debug 0 is false
> true
> false
_Note: Do not use **is** or **isnt** to test values against **NaN**. You must use the
specific **isNaN** operator for this._
### isNaN - _checks for NaN_ - operators - ^isNaN
Returns true if the expression is the **NaN** flag.
.. isNaN [expr]
Note there is no other way to test for NaN than by using **isNaN**.
Note also that capitalization matters.
set a !parseInt 'This is not a number.'
debug a // NaN
debug a = 0 // false
debug a = false // false
debug a = NaN // false
debug isNaN a // true
Right? Seriously:
debug NaN = NaN // false
debug NaN is NaN // false
So that's why we need this keyword for this one little function.
### ISNT - _equivalence test_ - operators - ^isnt
Returns **true** if the two expressions are in any way distinguishable.
.. [expr] isnt [expr]
More rigorous than **<>** when objects are of differing types.
debug 1 <> true
debug 1 isnt true
> false
> true
_Note: Do not use **is** or **isnt** to test values against **NaN**. You must use the specific **isNaN** operator for this._
### ISOF - _object information_ - operators - ^isof
All prototyped SAI objects have an **isof** trait that is a plain object that encapsulates detailed
information about the SAI object and its heritage.
.. [object].isof
- or -
.. [object] isof [class name expr]
Let's just see how this works:
object Fruit 1.0.0
Instantiate task
debug 'I am a ${isa}'
set a to create 'Fruit'
> I am a Fruit
object Pear 1.0.0
inherit: Fruit
set b to create 'Pear'
> I am a Pear
debug a.isof
> { Fruit:
> { version: '1.0.0',
> isa: 'Fruit',
> context: './sample/Keywords/Fruit.sai' } }
debug b.isof
> { Fruit:
> { version: '1.0.0',
> isa: 'Fruit',
> context: './sai/sample/Keywords/Fruit.sai' },
> Pear:
> { version: '1.0.0',
> isa: 'Pear',
> context: './sai/sample/Keywords/Pear.sai',
> inherit: [ 'Fruit' ] } }
You can trace an object’s creation in order recursively by starting at the **isof** entry for the
present object — `isof[isa]` — and stepping through the **inherit** array.
#### isof operator
.. [object] isof [class name expr]
Returns true if the object is an instance of a class that is, or inherits from, the given class name.
class Parent 1.0.0
class Child 1.0.0
inherits: Parent
set a to create 'Child'
debug a isof 'Child' // true
debug a isof 'Parent' // true
### LSH - _unsigned bitwise left shift_ - operators - ^lsh
Convert the expression to a 32 bit integer, then shift it left bitcount times.
.. [expr] lsh [bitcount]
.. [expr] << [bitcount] // synonym
For example:
debug 4 lsh 1 // 8
debug -8 lsh 2 // -32
debug 2.8 lsh 0 // 2
The synonym __<<__ does exactly the same thing.
### NAND - _not-and logical_ - operators - ^nand
Checks two or more values; returns _false_ if all of the values are _truthy_. Otherwise
returns _true_.
.. [expr] nand [expr]
.. nand [list of expressions]
A semantic simplification of `not ([expr] and [expr])`.
debug 0 nand 0 // prints 'true'
debug 1 nand 0 // prints 'true'
debug 0 nand 1 // prints 'true'
debug 1 nand 1 // prints 'false'
These are logically the same
debug a nand b
debug not ( a and b )
For syntax of the list-based version of __nand__ please review keyword __and__.
### NEW - _object instantiation_ - operators - ^new
Instantiates a new object with the given prototype.
.. new [prototype] [parameters]
If parameters are supplied, passes them to the object’s constructor.
**New** is used for traditional Javascript objects; objects where you have in-hand the prototype
used to fabricate an instance that object. **New** trusts that the code to create that object
has already been located and compiled.
**Create** is used for SAI objects; objects where you have in-hand an object name (as a string).
**Create** doesn't assume pre-compiled prototypes; if the prototype isn't cached, it will attempt
to load it via the `SAI.config.Loader` function.
reference:
express require('express')
object HelloHTTP 1.0.0
Instantiate task
set @app express()
@app.get '/', task given req, res
res.send 'Hello HTTP!'
set @server !@app.listen 3000, 'localhost', task
with @server.address()
debug 'Example app listening at http://${.address}:${.port}'
You ordinarily need to use the **~** global scope prefix when creating Javascript built-in objects
however `Error` has been promoted to SAI global so you don't have to:
if crocodiles
throw new Error "Why do we even have that lever?"
Notice here that **new** is not expecting any parameters to be wrapped in parentheses; in this way it
works like **from**.
See also __create__ which is used to instantiate SAI objects by name, and __singleton__ which is
used to create singleton SAI objects by name.
### NOR - _not-or logical_ - operators - ^nor
Checks two or more values; returns true only if _all_ of them are _falsy_. Otherwise
returns true.
.. [expr] nor [expr]
.. nor [list of expressions]
Returns **true** only if left or right expression are both _falsy_. Otherwise returns **false**.
A semantic simplification of `not ([expr] or [expr])`.
debug 0 nor 0 // prints 'true'
debug 1 nor 0 // prints 'false'
debug 0 nor 1 // prints 'false'
debug 1 nor 1 // prints 'false'
These are the same:
if not ( a or b )
if a nor b
unless a or b
For the syntax related to the list-based version of 'nor' review keyworcd __and__.
### NOT - _logical not_ - operators - ^not
Performs the logical not operation.
.. not [expr]
Returns **false** if the right expression is _truthy_, otherwise returns **true**.
debug not false // prints 'true'
debug not 0 // prints 'true'
debug not 'Sam' // prints 'false'
debug not empty // prints 'false'
These are the same:
if not a
unless a
### NOTB - _binary not_ - operators - ^notb
Performs a bitwise logical not on a 32-bit signed integer.
.. notb [expr]
For example:
debug notb -8 // prints '7'
-8 in binary: 11111111111111111111111111111000
+7 in binary: 00000000000000000000000000000111
### NUMBER - _numeric coercion_ - operators - ^number
Attempts to convert the following expression into a numeric value.
.. number [expr]
If it cannot, the result is 0.
debug number '12'
debug number 'ralph'
> 12
> 0
It is always safe to use __number__ in that it will never return a non-number, and it will never
throw an error. It's sole failure mode is to return 0.
### OR - _logical or_ - operators - ^or
Checks two or more values; returns the first one examined that is _truthy_. Otherwise
returns the last value checked.
.. [expr] or [expr]
.. and [list of expressions]
Examples:
debug 0 or 0 // prints '0'
debug 1 or 0 // prints '1'
debug 0 or 2 // prints '2'
debug 1 or 2 // prints '1'
__Or__ also works with a list. Please review the __and__ keyword for details on
syntax.
### ORB - _bitwise-or_ - operators - ^orb
Performs a bitwise or on two 32-bit integers.
[expr] orb [expr]
Example:
debug 5 orb 11 // prints '15'
5 in binary: 00000000000000000000000000000101
11 in binary: 00000000000000000000000000001011
15 in binary: 00000000000000000000000000001111
### RSH - _signed bitwise right shift_ - operators - ^rsh
Convert the expression to a 32 bit signed integer, then shift it right bitcount times,
maintaining the sign of the expression.
.. [expr] rsh [bitcount]
.. [expr] >> [bitcount] (synonym)
As in:
debug 8 >> 1 // 4
debug -32 rsh 2 // -8
There's also an unsigned variant __>>>__/__ursh__.
### SINGLETON - _singleton object access_ - operators - ^singleton
Creates one instance of SAI object by name.
.. singleton [expr] [parameters]
Creates one instance of SAI object by name. If the object has already been created, returns a
reference to it. The scope is that maintained by the runtime library.
Like __create__, __singleton__ 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 singleton Tally
(The example follows best practices of placing object names in a **reference** section, aliasing
versioned names into literals.)
Singleton objects are useful for library functions, APIs to external resources, tallies, caches,
message managers and so on.
Since the reference to the singleton object is maintained by the library, singleton objects are
never garbage collected (and cannot be destroyed) once created. If you might need to release resources
held by a singleton before the end of the application, you must make allowances for that in your
object's API; e.g. by crafting a `.Cleanup` task.
See also __new__, which is a direct link to Javascript's `new`, and __create__, which will make a new
object each time.
### TYPEOF - _javascript type disclosure_ - operators - ^typeof
Returns the Javascript-native type of an expression.
.. typeof [expr]
See e.g. [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof][2] for details.
### URSH - _unsigned bitwise right shift_ - operators - ^ursh
Convert the expression to a 32 bit unsigned integer, then shift it right bitcount times,
Zero bits are added on the left side (msb), which does not preserve negative sign.
.. [expr] ursh [bitcount]
.. [expr] >>> [bitcount] (synonym)
This is the difference:
debug -32 rsh 2 // -8
debug -32 ursh 2 // 1073741816
You want this one if you're working with bitmap data, for example.
Compare with __rsh__.
The synonym __>>>__ does the same thing as __ursh__.
### XOR - _logical exclusive-or_ - operators - ^xor
If both expressions are _truthy_, or both expressions are _falsy_, return `false`. Otherwise return the expression that is _truthy_.
.. [expr] xor [expr]
Example:
debug 'Fred' xor 'Daphne'
debug 'Shaggy' xor 0
debug 0 xor 'Scooby'
debug 0 xor 0
> false
> Shaggy
> Scooby
> false
Maybe not too often used, but compared to:
if (a and b) or not (a and b)
### XORB - _binary exclusive-or_ - operators - ^xorb
Performs a bitwise XOR operation on two 32-bit integers.
.. [expr] xorb [expr]
Example:
debug 3 xorb 6 // prints '5'
3 binary: 00000000000000000000000000000011
6 binary: 00000000000000000000000000000110
5 binary: 00000000000000000000000000000101
Favourite of cryptographers everywhere. And data storage architects.