flight_script 0.1.2 copy "flight_script: ^0.1.2" to clipboard
flight_script: ^0.1.2 copied to clipboard

A small flutter compatible embeded scripting language. Because sometimes you just need some scripting..

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');
}

Result

The answer to the ultimate question is  42

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
The answer to the ultimate question is 42.0

/* "
  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

21.0

/* "
  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


21.0

/* "

  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

10.0

/* "
  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



Smaller
This is always true so we use the do-nothing function

/* "

  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

6.0
6.0
7.0
6.0

/* "
    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

7.0

/* "
  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


120.0

/* "

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
2.0
9.0

2
likes
120
pub points
0%
popularity

Publisher

unverified uploader

A small flutter compatible embeded scripting language. Because sometimes you just need some scripting..

Repository

Documentation

API reference

License

MIT (LICENSE)

More

Packages that depend on flight_script