Expressions
Expressions can be categorized in many different ways: type of action, appearance, whether it also assigns the value somewhere or if its purpose has a special meaning.
There are 5 types of action for expressions in SGScript: arithmetic, bitwise, logical, comparison and special.
expression | appearance | type of action | assign | # in. |
---|---|---|---|---|
add | A + B | arithmetic | no | 2 |
subtract | A - B | arithmetic | no | 2 |
multiply | A * B | arithmetic | no | 2 |
divide | A / B | arithmetic | no | 2 |
modulo | A % B | arithmetic | no | 2 |
pre-increment | ++ A | arithmetic | self | 1 |
pre-decrement | -- A | arithmetic | self | 1 |
post-increment | A ++ | arithmetic | self | 1 |
post-decrement | A -- | arithmetic | self | 1 |
add-assign | A += B | arithmetic | yes | 2 |
subtract-assign | A -= B | arithmetic | yes | 2 |
multiply-assign | A *= B | arithmetic | yes | 2 |
divide-assign | A /= B | arithmetic | yes | 2 |
modulo-assign | A %= B | arithmetic | yes | 2 |
bitwise AND | A & B | bitwise | no | 2 |
bitwise OR | A | B | bitwise | no | 2 |
bitwise XOR | A ^ B | bitwise | no | 2 |
left shift | A << B | bitwise | no | 2 |
right shift | A >> B | bitwise | no | 2 |
bitwise AND-assign | A &= B | bitwise | yes | 2 |
bitwise OR-assign | A |= B | bitwise | yes | 2 |
bitwise XOR-assign | A ^= B | bitwise | yes | 2 |
left shift-assign | A <<= B | bitwise | yes | 2 |
right shift-assign | A >>= B | bitwise | yes | 2 |
bitwise invert | ~ A | bitwise | no | 1 |
logical AND | A && B | logical | no | 2 |
logical OR | A || B | logical | no | 2 |
first-not-null | A ?? B | logical | no | 2 |
logical AND-assign | A &&= B | logical | yes | 2 |
logical OR-assign | A ||= B | logical | yes | 2 |
first-not-null-assign | A ??= B | logical | yes | 2 |
logical invert | ! A | logical | no | 1 |
less than | A < B | comparison | no | 2 |
less than or equal | A <= B | comparison | no | 2 |
greater than | A > B | comparison | no | 2 |
greater than or equal | A >= B | comparison | no | 2 |
equal | A == B | comparison | no | 2 |
not equal | A != B | comparison | no | 2 |
strict equality | A === B | comparison | no | 2 |
strict inequality | A !== B | comparison | no | 2 |
raw comparison | A <=> B | comparison | no | 2 |
error suppression | @ A | special | no | 1 |
declare-local | var A | special | maybe | any |
declare-global | global A | special | maybe | any |
array literal | [ A, B, .. ] | special | no | any |
dict. literal | {A=1,B=2,..} | special | no | any |
map literal | map{[A]=B} | special | no | any |
assign | A = B | special | yes | 1 |
concatenate | A $ B | special | no | 2 |
concatenate-assign | A $= B | special | yes | 2 |
concatenate | A .. B | special | no | 2 |
concatenate-assign | A ..= B | special | yes | 2 |
property | A . B | special | maybe | 2 |
index | A [ B ] | special | maybe | 2 |
multi-index-assign | A[]<dict.lit> | special | yes | any |
multi-property-assign | A.<dict.lit> | special | yes | any |
function call | A ([B,..]) | special | no | <=256 |
comp. function call | O!A ([B,..]) | special | no | <=256 |
function definition | function A.. | special | maybe | 0 |
inline if | if(A,B,C) | special | no | 3(2) |
subexpression | ( A[, ..] ) | special | maybe | any |
thread-call | thread f() | special | maybe | <=256 |
subthread-call | subthread f() | special | maybe | <=256 |
new-call | new f() | special | maybe | <=256 |
Some notes on the special cases:
- [increment,decrement] pre-increment and pre-decrement operators return the modified value, post- operators - the original one
- [logical] all logical operators except 'logical invert' (which returns bool) set/return one of the two operands passed
- [equality] strict (in)equality operators are the same as their non-strict counterparts with one difference: they do type checking, for example
5 == 5.0
would return 'true' and5 === 5.0
would return 'false' - [declare] declarations only actually set the data if they include the assignment expression, otherwise only the type is internally noted for other references
- [declare] variables can be redeclared as long as they maintain their access level
- [property,index] whether property or index expressions set data depends if they're on the receiving (left) end of an assignment expression
- [inline if] 3 inputs are required, 2 are actually used (as if
if(A){return B;}else{return C;}
was used) - [subexpression] subexpressions can set data if they are set up like this: <subexpression> = <function-call> - this is the way to read more than one return value from a function
- [error suppression] the
@
operator disables warnings, errors and any other messages for as long as the subexpression is executed, this is mostly useful for things like reading a property or calling a function that may not exist, in cases where that isn't an error - [dict./map literal] 3 types of keys are supported: string, identifier (interpreted as string), variable ("[ <expression> ] = <value>")
- [function] the full function definition expression syntax:
function <name> ( <args> ) [ use ( <use-list> ) ]
, followed by either{ ... }
or= ... ;
. <name>, <args> and the 'use' block are all optional. - [multi-set] operator returns
A
, that is, the object itself - [thread]
thread
/subthread
commands support all kinds of calls (f()
,o.f()
,o!f()
) - [new]
new
supports only the basic calls (f()
,o.f()
), andthis
is not passed (as it would refer to the newly created instance)
compatible function call / inheritance call / global method call ( O!A ([B,..])
)
CFC is created to encourage users to reduce memory usage without sacrificing clarity of code. Object and its processing function can reside in different locations (like class instance / inherited class) and still be easily accessible.
Properties:
- uses the same symbol as logical inversion operator
- usage: simplified & optimized calling of non-integrated compatible functions
- instead of
comp_func.call( object, arg1 )
(removed since 1.4) writeobject!comp_func( arg1 )
- instead of putting functions with data / class interfaces, requiring additional memory usage, allows to easily keep them separate
- simplify inheritance-like code models to the extent permitted by a dynamic language
- instead of
Example WITHOUT:
function create_object() { object = { x = 0, y = 0 }; function object.move( x, y ){ this.x = x; this.y = y; @this.move_callback(); } function object.tick( delta ){ this.move( this.x + delta, this.y ); } return object; }
Example WITH:
function Object_Move( x, y ){ this.x = x; this.y = y; @this.move_callback(); } function create_object() { object = { x = 0, y = 0 }; function object.tick( delta ){ this!Object_Move( this.x + delta, this.y ); } return object; }
Now, by itself it may not mean much, however let's see what happens if the following code is used:
for( i = 0; i < 100; ++i ) objects.push( create_object() );
Without the usage of a global method, there's a key 'move' in each object that points to the function. In real software there may be no less than 10 such keys in many instances of many objects. And if there are no overrides planned for it, there's no need for it to be there but we can't really move it. Classes are an option that involves a few more allocations and it requires more memory to use them. Using .call/sys_call
or replacing this
with an argument would sacrifice readability and the option to relocate functions, as well as performance, this being a double call. This is where CFC comes in - syntax allows to clearly state the object and function used, and the required interface for the object to be compatible with said function.