An open API service indexing awesome lists of open source software.

https://github.com/meshula/landru

Landru compiler and VM
https://github.com/meshula/landru

Last synced: 8 months ago
JSON representation

Landru compiler and VM

Awesome Lists containing this project

README

          

**Landru** is a reactive state machine oriented **Forth** dialect.

**Forth** is executed line by line as source is interpreted. **Landru** is compiled.

Comments are **C++** style.

```
// This is a comment.
```

**Landru** allows the import of modules using the *require* keyword.

```
audio = require("audio")
time = require("time")
io = require("io")
```

**Landru** has a number of standard libraries, including
- io -- input and output
- time -- time related utilities
- int -- integer math
- real -- real number math

There are some optional libraries as well, such as
- audio

The fundamental execution unit is a *machine*. When machines are
encountered in a Landru file, they are compiled. There is one special
machine, named *main*. It's special because it is executed automatically as
soon as the whole file is compiled.

```
machine main:
// this machine is trivial
;
```

As in **Forth**, **Landru** language structures are declared, then surrounded
with a colon and semicolon. Here, *machine main* is pretty trivial.

Machines are made up of declared persistent state, and states. There is one
special state, named *main*. When a machine is started, the machine's state
variables are instantiated, and then *main* automatically runs.

```
machine main:
declare: audio.Context audioContext;

state main:
;
;
```

Whitespace is not significant.

```
// The same program.

machine main:
declare: audio.Context audioContext;
state main: ;;
```

**Forth** uses a prefix notation where parameters are loaded up on a stack, then
a function is invoked. **Landru** also has a data stack, loaded up between
parenthesis. Commas between arguments are optional. Functions are always
executed on an object. There are no free functions.

```
int.add(3 5)
int.add(3, 5)
```

A *machine* can launch another *machine*.

```
machine pong:
state main: io.print("pong") ;;

machine ping:
state main: io.print("ping")
launch("pong") ;;
```

A *machine* can launch another instance of its own kind.

```
machine recurse:
state main: launch("recurse") ;;
```

The *recurse* machine does not lock up the execution environment,
because *lauch* instructions do not execute immediately. Instead, they are
queued, and run when a state yields.

A *machine* retires when it has no pending state transitions. **Landru**
execution continues until all launched machines retire, or are otherwise
terminated.

A real *machine* will have more than one state. State transitions are invoked
by the *goto* statement.

```
machine pingpong:
state ping: io.print("ping") goto pong ;
state pong: io.print("pong") goto ping ;
state main: goto ping ;;
```

Notice that the statements executed by the state are bracketed by **Forth*'s
colon-semicolon scoping syntax.

The *pingpong machine* will print out ping and pong like crazy. (Not completely
crazy, gotos yield before they take affect.) Actor based
languages are event driven, and **Landru** is no exception. **Landru** uses
the *on* keyword to indicate events that will be responded to.

The *time* standard library has an after event. The *pingpong machine* can be
brought under control by involving a timer.

```
machine pingpong:
state ping: io.print("ping")
on time.after(0.5): goto pong ;;
state pong: io.print("pong")
on time.after(0.5): goto ping ;;
state main: goto ping ;;
```

This *pingpong machine* will change state every half a second. Notice that
the *on* statement takes the form of *on condition* followed by the things to do
enclosed in **Forth**'s typical colon semicolon scoping syntax.

variables
---------

Variables have a hierarchy of scopes.

Local variables are declared within a scope, and don't persist beyond it.

```
machine localVar:
state main:
float a = 3
io.print("a is ", a);;
```

The variable *a* won't be avalable outside of the state, and will be initialized
to 3 every time the state is initialized.

```
machine instanceVar:
declare: float b = 3;
state main:
io.print("b is ", b);;
```

When a machine of type *instanceVar* is launched, it will have its own copy of *b*,
initialized to 3. If a state modifies b, the new value is seen by any other states
within that instatiated machine, but not by other machines of the same time.

```
machine sharedInstanceVar:
declare: shared float c = 3;
state main:
io.print("c is ", c) ;;
```

When the first machine of type *sharedInstanceVar* is instantiated, *c* will be
have the value of 3. If *c* is modified, all other instances of *sharedInstanceVar*
will see that new value. When new instances are created, they will share the
already existing *c*. If all copies of *sharedInstanceVar* exit, *c* will also
disappear, meaning the next *sharedINstanceVar* object will reinitialize *c* with
the value 3.

```
require("test")
machine requireVar:
state main:
io.print("test.d is ", d) ;;
```

If the module test is known to have a value *d* in it, a machine can access it
using dotted scope syntax.

```
declare:
float e = 3 ;

machine globalVar:
state main:
io.print("global e is ", e) ;;
```

Variables declared in the global scope persist until all launched machines have
exited and the script stops running.

States can have local variables.

```
machine stateVar:
state main:
declare: int j = 5; ;
;
;
```

Scoping rules are that the most local scope has precedence. So a local variable
is preferred to machine variable (shared or instance), is preferred to a required
module's variable or a global variable. If for some reason namespacing caused a
global variable to have the same name as a module's variable, the module
variable hides the global variable.