SquareScript Standard Steps - radishengine/drowsy GitHub Wiki
Step Name: ''
['', 'this is a comment']
The Comment Step is unique in that it genuinely does not resolve to any value, so if a comment is added between two items in a value list, the final value list is exactly the same as if there had been no comment. This is unlike any other step that "does not resolve to a value", but would still add a null
to the value list in this example.
The parameter(s) of a Comment step are not evaluated or checked in any way, and should be ignored by automated processing, unless delving into comments is a deliberate part of it.
A Block can be thought of as a kind of Step, that:
- uniquely, has no name string
- only allows steps to be its parameters (not literal values like
true
or7
) - always evaluates to
null
...but then again, maybe it is better to think of Blocks and Steps as separate entities instead of one being a sub-type of the other. Go with whatever perspective works best for you.
A Block can be converted into an equivalent '...!'
Bottom Line Step by adding in the step name, and appending null
as a final value.
Step Name: '...!'
Evaluates to: whatever the final evaluated parameter was.
Similar to the comma operator in C/C++, the Bottom Line step evaluates each parameter, discarding each value until the last one, and then this final value is the overall result of the step.
This is mostly useful for things like macros, where you want to create something as a self-contained expression and you don't have access to the whole document structure. For example, this expression uses a self-contained import:
['...!', ['< >','imported.thing'], ['<^>','imported.thing'], ['imported.thing']]
Here scoping steps are being used as parameters instead of block steps, which is odd but not invalid. In any actual situation where you might want to use this, it seems like it would make more sense to bubble the import up to a containing block, but this may be a helpful starting point.
Step Name: '#?!'
Evaluates to: null
Normally, all steps must evaluate their parameters in order, from first to last. The Orderless step is unique in that it evaluates its parameters in undefined order, but must evaluate all of them, before the next step is run.
Step Name: '--8<--'
Evaluates to: null
The parameters of a detached step are run in their own isolated context, independent from the rest of the program. The next step in the calling code continues immediately.
The detached context includes its own scope, which by default will only contain values from the outer scope if they have been marked 'final'
. One exception to this is if the very first detached step is a '< >'
open-scope step, the '<^>'
import-to-scope steps immediately following it may pull in copies of values from the outer scope. Otherwise, it is a syntax error for detached steps to refer to non-final scoped values from the outer scope (including trying to export out to them, via a '<v>'
step).
Inside a detached scope, you can only use scoped variables for jump labels (not string literals or number literals). This means it is impossible to jump/call into a detached context, but not out of it.
The detached context also has its own stack, initially empty.
If the detached context exits with '<-'
, any return value(s) don't go anywhere.
Step Name: '< >'
Evaluates to: null
Parameters:
- one or more scoped value name strings
A scope defines one or more named values that exist until either the ]
that marks the end of this block, or an explicit ['</>']
end scope step within the same block.
Named scope values can be referred to by using their name as a step name, with no parameters. This reference usually evaluates to the named scope value's current value, but depending on context it can sometimes be used to refer to the variable itself, for example when it is the first parameter of the ['set', ...]
step, it is used as a destination to store a new value instead of fetching the current value.
An inner scope can hide the names in an outer block.
For example:
[
['< >', 'A'],
['set', ['A'], 10],
[
['< >', 'A', 'B', 'C'],
['set', ['A'], 3],
['set', ['B'], 4],
['set', ['C'], 5],
['output', ['A']] // output: 3
],
['output', ['A']] // output: 10
]
Step Name: '</>'
Evaluates to: null
Parameters: (none)
All scopes are usually removed at the end of the block that the < >
step was defined in. You can use this step instead to force the end of one or more scopes.
For example, a flattened equivalent to the example in "Open Scope":
[
['< >', 'A'],
['set', ['A'], 10],
['< >', 'A', 'B', 'C'],
['set', ['A'], 3],
['set', ['B'], 4],
['set', ['C'], 5],
['output', ['A']] // output: 3
['</>'],
['output', ['A']] // output: 10
['</>'],
]
Step Name: '<^>'
Parameters:
- string: scoped value name
- (optional) external import identifier, if it is different from the scoped value name
Import to Scope steps must only appear immediately after a Scope step, and the scoped value name must be present in that previous step.
The external import identifier is almost always a string, with one exception: if it is of the form ['outerName']
, then the value is imported from the outer scope (it must exist there, or it is a syntax error), not from a completely external source. This is useful in the event that you want to make a local copy of an outer scope variable, i.e. one with both the same name and the same value as the outer scope variable, but that you can also replace without also clobbering the one in the outer scope.
['< >', 'a'],
['set', 'a', 10],
['< >', 'a'],
['<^>', 'a', ['a']],
['output', ['a']], // output: 10
['set', ['a'], 5000],
['output', ['a']], // output: 5000
['</>'],
['output', ['a']], // output: 10
Step Name: '<^?>'
Evaluates to: the imported value, or null
if it wasn't found
Alternate version of Import to Scope that does not make the import an absolute requirement before continuing, but instead returns null
if the import was not possible.
Step Name: '<v>'
Evaluates to: null
Parameters:
- either:
- a string, for a "global" export
- a scoped value reference, but taken from the immediate outer scope, not the current one.
This is only useful in the case that the current scope has a value with the same name as one in the outer scope, otherwise you could just assign to it with'set'
.
- the value to export
Step Name: '<:>'
Parameters:
- a constraint specifier
- a constrainable value reference
See Standard Macros:
Constraints are most useful for ensuring that an imported object fulfills an expected contract (for example, a class definition includes the right methods that we are going to want to call).
- existing scoped value
- a member/element access on a constrainable value reference
If the indexer is0
, this stands in for "any integer". If the indexer is the empty string''
, this stands in for "any string".
At its simplest, this is a string like 'u8'
which specifies that the value is and must remain an unsigned 8-bit integer.
TODO: more complex
-
0.5
: a floating-point number (while not technically specified, can be assumed to be 64-bit double) -
0
: an integer (of no particular bit width or signedness) -
''
: a string (of no particular charset) -
true
: a boolean
'struct'
is a special constraint. It means the value must be a JavaScript constructor function that:
- takes three parameters:
buffer
(ArrayBuffer),byteOffset
(number),byteLength
(number) - has a
.byteLength
property (finite, positive integer) set on the constructor function object itself
The idea is to be able to create a struct-like object by doing something like:
var structInstance = new StructConstructor(
new ArrayBuffer(StructConstructor.byteLength),
0, StructConstructor.byteLength);
Step Name: '<this>'
Retrieves an object representing the current scope.
['@:', 'myLabel']
['@:', 12345]
['@:', ['scopedLabel']]
Defines an identifier for the following step position, so that execution can jump to this position from elsewhere.
The single parameter of the Position Label step can either be a literal string, a literal number, or a scoped value reference. If it is a string or number, it is a syntax error for there to be any other Position Label with the same value elsewhere in the same document (though it's OK to have the same value as a string and a number, like 1
and "1"
).
If it is a scoped value reference, it is given a value representing the step position immediately from the point that it is declared, and also is automatically marked final from the point that it is declared.
If the label is a number, the actual numerical value has no special significance.
A position label can be made into an execution entry point by using a scoped value, and exporting it.
Step Name: 'c():'
Evaluates to: callable object, to be exported and/or used in 'c()'
and 'push c()'
steps
Parameters:
- the function body as a block/step
['final', ['myfunc'], ['c()', [
['<-']
]]]
to
['->', '__END'],
['@:', ['myfunc']],
['<-'],
['@:', '__END'],
(where '__END'
represents some automatically-generated, non-conflicting jump label)
['->@', 'myLabel']
['->@', 12345]
['->@', ['scopedLabel']]
An unconditional jump to the given label, which must exist or it is a syntax error.
Step Name: '<-'
Evaluates to: (never evaluates)
Parameters:
- Return value(s). May be none (for a "void" type)
When returning values from the stack, particularly multiple values, it may be more convenient to use the Pop and Exit step, '<- pop'
.
If there is a step immediately after this one, and it is not a position label, it will be unreachable.
Step Name: '?'
Evaluates to: same as the successful clause, or null
if all failed
Parameters:
- primary "if" clause:
- non-block expression
- "then" step or block
- (optional, repeated) "else-if" clause(s):
- non-block expression
- "then" step or block
- (optional) final "else" clause:
- "else" step or block
An example of the simplest form:
['?', ['some-expression'], [
/* steps to run if (some-expression) evaluates to true */
]]
Step Name: '?*'
Evaluates to: null
Parameters:
- loop label:
- a string, to create a scope where this scoped value represents the loop itself
-
null
, to not create a scope - this also means you cannot'break'
or'continue'
this loop
- top condition, consulted before every iteration
- step/block to repeat
-
(optional): bottom condition, consulted after every iteration (default:
true
)
Example of a loop like "while (some-expression) {...}":
['?*', null, ['some-expression'], [
/* steps to be executed while (some-expression) evaluates to true */
]]
Example of a loop like "do {...} while (some-expression)":
['?*', null, true, [
/* steps to be executed until (some-expression) does not evaluate to true */
], ['some-expression']]
["break", ["loop"]]
Breaks an enclosing loop that must have been given a scoped name (i.e. the first parameter of '?*'
is not null
) and the sole parameter is a reference to this scoped value.
['?*', 'loop', ['some-expression'], [
['?', ['some-other-expression'], ['break', ['loop']]],
/*
steps to be executed while (some-expression) evaluates to true
and while (some-other-expression) also evaluates to true
*/
]]
["continue", ["loop"]]
Jump to the bottom condition of an enclosing loop that must have been given a scoped name (i.e. the first parameter of '?*'
is not null
) and the sole parameter is a reference to this scoped value.
['?*', 'loop', ['some-expression'], [
['?', ['some-other-expression'], ['continue', ['loop']]],
/*
steps to be executed while (some-expression) evaluates to true
unless (some-other-expression) also evaluates to true, in this particular iteration
*/
]]
Step Name: 'c()'
Evaluates to: the return value, or null
Parameters:
- the function to call
- the call argument(s), if any
If the function returns multiple values, all but the first will be lost. Use Call and Push to retain them all.
Some languages may require specialized variants. For example in Lua, a.b()
is different from a:b()
(the method call syntax), so these could (for example) translate to:
['c()', ['.[]', ['a'], 'b']]
...and:
[':c()', ['.[]', ['a'], 'b']]`
...respectively (where ':c()'
would be a non-standard step type, specifically for Lua method calls).
Having said that, even in languages where there are multiple kinds of "callable", it is appropriate to use c()
for any kind of call, so long as it can always be unambiguously deduced in practice.
A value is assignable if it is one of these:
- a scoped name value
- a member/element access (on any value, not necessarily an assignable value)
-
['.[]', ['stack'], 'length']
- must be an integer >= 0. values on the stack will be truncated or padded withnull
s
...and also, if it has not (yet) had the 'final'
step applied to it.
Step Name: 'set'
Parameters:
- assignable value
- the value to be assigned
Step Name: 'final'
Parameters:
- assignable value (except
['stack size']
) - (optional) value
Mark a value as constant, not to change again from its current value (or the new value, if one is specified via the second parameter). This means the value can no longer be used as an assignable value.
Step Name: 'i64'
Parameters:
- string literal containing an integer literal, preferably hexadecimal (with
0x
prefix)
Step Name: 'u64'
Parameters:
- string literal containing an integer literal, preferably hexadecimal (with
0x
prefix)
Step Name: 'new {}'
Parameters: (none)
Step Name: 'new []'
Parameters:
- (optional) initial length
Step Name:
'new i8[]'
'new u8[]'
'new i16[]'
'new u16[]'
'new i32[]'
'new u32[]'
'new i64[]'
'new u64[]'
'new f32[]'
'new f64[]'
Parameters:
- length (mandatory, fixed)
Step Name: 'new C()'
Evaluates to: the newly-constructed object
Parameters:
- the constructor function (must be an actual JavaScript function object, not a SquareScript callable)
- the constructor call argument(s)
Step Name: 'new struct'
Evaluates to: the new struct object
Parameters:
- the struct constructor function (must fulfill the constraint for a struct constructor)
Step Name: 'new struct[]'
Evaluates to: the new array
Parameters:
- the struct constructor function (must fulfill the constraint for a struct constructor)
- length (mandatory, fixed)
-
0+
: to number -
s+
: to string -
i8
,i16
,i32
,i64
: to signed integer -
u8
,u16
,u32
,u64
: to unsigned integer -
f32
,f64
: to floating-point -
!!
: to boolean
Step Name: '.[]'
Parameters:
- The object to access. Often a Scoped Value
- The name or index.
- (optional) A literal string representing the access type, if it is ambiguous
['.[]', ['a'], 'field']
represents something like a.field
in the original syntax.
Note that the access type is expected to only be specified if it is ambiguous. To give an example, let's look at plain C, and the various ways you can make an access on value a
:
-
a.field
(struct field access) -
a->field
(struct pointer field access) -
a[0]
(array access) -
*a
(pointer dereference)
It might be tempting, looking at this list, to assume it's going to be necessary to use a series of access-type strings (maybe '.'
, '->'
, '[]'
and '*'
). Well, first of all, if a
is a pointer then a.field
is never valid, and if a
is not a pointer, a->field
is never valid. Since we expect to always have the information about whether or not a
is a pointer-type, there's actually no ambiguity between these two cases, so they do not need separate access-type strings.
Secondly, in a C array-access expression, the indexer is always going to be an integer or an expression that resolves to an integer. If you try to do a["string"]
in C, the code won't compile[1]. So, we can automatically disambiguate between field access like a.field
and a->field
(where the indexer will be a string literal) and an array access (where the indexer will never be a string literal).
Finally, with a *a
pointer dereference there is no actual indexer. Since '*'
is not a valid C field name, we could use that as the indexer, or alternatively we might use true
since C has no true boolean type. Either way, we don't need an access-type for pointer dereference either. So all four kinds of access can be represented unambiguously without resorting to specifying an access-type.
[1] The closest you might see is something like a['c']
, but in that case what the compiler sees is actually a[99]
-- in C, a single-quoted char literal like 'c'
(as opposed to a double-quoted string literal) is essentially just another way to describe an integer, like 30
or 0xFF
.
Step Name:
'!'
'&&'
'||'
Same as the JavaScript operators, including the fact that &&
and ||
will stop evaluating once they hit a logically false or logically true operand, respectively.
Step Name: +
Evaluates to: number or string
Parameters:
- any number of values (usually 2)
Exactly the same semantics as the JavaScript +
operator, including use of .toString()
, .valueOf()
etc.
If there is only 1 value, cast the value to a number (or NaN
if impossible to do so).
Step Name: 0+
Evaluates to: number or NaN
Like the JavaScript +
operator except the result must be a number, so NaN
if any of the values are not numbers (or have a relevant .valueOf()
method).
Step Name: s+
Evaluates to: string
Like the JavaScript +
operator if both sides are coerced to strings so it must be a string concatenation, not a numerical addition.
If only one value, cast the value to a string.
These are identical to the JavaScript operators (except //
which is the same as using Math.floor()
on the result of a division). They should not be used for 64-bit integers as the result is likely to lose precision.
- subtract/unary minus:
'-'
- floating-point divide:
'/'
- integer divide:
'//'
- multiply:
'*'
Note: For add, subtract, modulus and divide you can use the generic operator and then cast the result. Multiply, however, will not get the correct "purist's" result in the edge-case of a massively overflowing (or underflowing) result, which is why it is included here.
- multiply:
'i32*'
,'u32*'
- bitwise and:
'i32&'
,'u32&'
- bitwise or:
'i32|'
,'u32|'
- bitwise xor:
'i32^'
,'u32^'
- bitwise not:
'i32~'
,'u32~'
- left shift:
'i32<<'
,'u32<<'
- arithmetic right shift:
'i32>>'
,'u32>>'
- logical right shift (unsigned):
'i32>>>'
,'u32>>>'
For 8-bit and 16-bit versions, you can just cast the result, including the generic multiply.
- add:
'i64+'
,'u64+'
- subtract/unary minus:
'i64-'
,'u64-'
- multiply:
'i64*'
,'u64*'
- divide:
'i64/'
,'u64/'
- modulus:
'i64%'
,'u64%'
- bitwise and:
'i64&'
,'u64&'
- bitwise or:
'i64|'
,'u64|'
- bitwise xor:
'i64^'
,'u64^'
- bitwise not:
'i64~'
,'u64~'
- left shift:
'i64<<'
,'u64<<'
- arithmetic right shift:
'i64>>'
,'u64>>'
- logical right shift:
'i64>>>'
,'u64>>>'
Step Name: 'push'
Evaluates to: null
Parameters:
- value(s) to push
Pushes a single value onto the stack.
Step Name: 'pop'
Evaluate to: (the popped value)
Parameters: (none)
Pops a single value from the stack.
['stack']
['.[]', ['stack'], 'length']
The total number of values currently on the stack
Step Name: 'push c()'
Evaluates to: the number of values pushed (may be zero)
Parameters:
- the function to call
- the call argument(s), if any
Unlike Call and Return, this step pushes the return value(s) from the function call onto the stack. This function is necessary for calling functions with multiple return values.
Step Name: '<- pop'
Evaluates to: (never evaluates)
Parameters:
- (optional) the number of return values to pop from the stack (default: 1)
-
(optional) multiple value ordering flag:
true
to return the stack top first,false
to return it last (default:false
)
A stack-based variant of the Exit Point step.
Step Name: 'pop []'
Evaluates to: a new generic array
Parameters:
- number of values to pop