Last updated:
0 purchases
flight script
A small scripting language to embed in your flutter projects.
Features #
Lightweight scripting. Define functions. create loops. Assign variables. All the usuals.
Call dart functions from flight, or call flight from dart.
Interpreter can dynamically adapt while running
VM is separate from the parser, for added extensibility
Getting started #
Initializing the interpreter and running a script:
import 'package:flight_script/flight_script.dart';
void main() {
final interpreter = Interpreter();
interpreter.eval('"The answer to the ultimate question is " << 42 print');
}
copied to clipboard
Result
The answer to the ultimate question is 42
copied to clipboard
Notice that returned values are returned in an array, as multiple returns are allowed
Examples #
/* "
The flight-script interpreter is similar to the Forth language's compiler
it reads left to right, using spaces (space, tab, newline) to identify when the
next item starts. Each item is evaluated as it is reached.
there are only three things it recognises:
numbers - anything that dart recognises with `double.tryParse`
strings - strings start with either double or single quotes, and end with the matching quotation mark. Standard escape characters (e.g. \n for newline) are recognised
identifiers - any sequecnce of characters that is not one of those
the example program would therefore be: [ String, Identifier, Number, Identifier ]
Numbers and strings are pushed onto a stack. Identifiers are looked up in the heap
and then evaluated - if they're an object (number, string, or another dart object)
they are pushed onto the stack. If they are a function (or a flight macro, or a DartFunction)
they are immediately called - due to the immediate execution and the stack-based call, function names go after their arguments (usually)
Even comments follow this pattern - /* is an identifier that is bound to a function that causes the interpretter to discard
everything it sees until it sees the end comment identifier (and for the parser's sake, we wrap the comment text in a string)
"
*/
"The answer to the ultimate question is " << 42 print
copied to clipboard
The answer to the ultimate question is 42.0
copied to clipboard
/* "
values can be assigned with `-> variable_name`, and retrieved with `variable_name`
anything left on the stack at the end of the run will be returned to dart
Multiple values can be returned, so the returned result is an array
Extra whitespace can be added and is ignored
Arithmetic operators are 'infix' they go between their parameters instead of after them
This example sets the a, b, c and x variables and evaluates the general
quadratic equation (ax^2 + bx + c) => 2*4*4 -3*4 + 1 => 21
Operators are evaluated left to right - not by normal operator precidence
here we've used the `y` variable as the running total - there are other methods
to acheive this (macros and functions would make it a one-line equation)
"
*/
4 -> x
1 -3 2 -> a -> b -> c /* " multiple assignment is allowed, but it's not easy to read " */
a * x * x -> y
b * x + y -> y
c + y -> y
y print
copied to clipboard
21.0
copied to clipboard
/* "
macros are defined starting with `#{` and ending with `}`
A macro can either be immediately called using (), or it can
be assigned to a variable, after which it will be called immediately when that
variable is loaded
Macros do not create new scopes, they are simply executed in whatever scope they
are called. They may retrieve values from the stack or push to the stack, but care
must be taken with variable assignment inside a macro
(the dup command duplicates the top of the stack, the _operators (_+, _*) are the postfix forms of the
arithmetic operators)
" */
1 -> c
-3 -> b
2 -> a
4 -> x
<{ dup _* }> -> square
<{ x square * a }> ()
<{ b * x }> ()
<{ c }> ()
_+ _+
print
copied to clipboard
21.0
copied to clipboard
/* "
Functions are 'heavyweight' macros. Functions create a new scope when called,
which has access to its parent scope at the point it was declared.
Like macros, functions can be called anonymously. Also like macros, they use the stack for parameters and
return values
" */
{ dup _+ } -> double
5 double print
copied to clipboard
10.0
copied to clipboard
/* "
Conditions are unusual. first a boolean expression should be pushed. Then the true function (or macro), then
false. finally the condition keyword evaluates the stack.
" */
3 -> x
x > 5 <{ "Larger" print }> <{ "Smaller" print }> if
true { "This is always true so we use the do-nothing function" } { } if print
copied to clipboard
Smaller
This is always true so we use the do-nothing function
copied to clipboard
/* "
Functions are 'heavyweight' macros. Functions create a new scope when called,
which has access to its parent scope at the point it was declared.
Functions look like macros, but they each get their own scope. they are defined with {
The standard assignment operator ( -> ) will set the variable in the current
scope, or will update it in an outer scope if it is found
the 'fat' assignment ( => ) is used to declare a new variable in the current scope
which shadows the outer scope
Like macros, functions can be called anonymously. Also like macros, they use the stack for parameters and
return values
" */
5 -> x
5 -> y
/* "If we use a macro, both x and y are incremented as no scopes change" */
<{
x + 1 -> x
y + 1 => y
}> ()
x print
y print
/* "Using a function, y is looked up from the outer scope, but the set shadows it. x is set" */
{
x + 1 -> x
y + 1 => y
} ()
x print
y print
copied to clipboard
6.0
6.0
7.0
6.0
copied to clipboard
/* "
Functions have access to the scopes that were available when they were defined
This gives them access to variables that may otherwise be inaccessible
We can use this to create private variables - accessing n from outside the
outer function here would be impossible if we do not call `getter`
notice we return the two inner functions and assign them both - multiple returns are allowed as
we just push to the stack
" */
{
5 => n
{ n }
{ n + 1 -> n }
} () -> add_one -> getter
add_one
add_one
getter print
copied to clipboard
7.0
copied to clipboard
/* "
Because variables can be read from outer scopes but assignments may shadow,
functions can be called recursively. This can also be used to create loops
Since ! is normally the not= operator we redefined it, because we can
" */
{
dup > 1
<{ dup - 1 ! _* }>
<{ }>
if
} -> !
5 ! print
copied to clipboard
120.0
copied to clipboard
/* "
min_n and max_n take a variable number of arguments. THe infix argument should say how many values
to test
" */
2 4 min_n 2 print
1 2 3 4 5 6 7 8 9 max_n 9 print
copied to clipboard
2.0
9.0
copied to clipboard
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.