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
- Host: GitHub
- URL: https://github.com/meshula/landru
- Owner: meshula
- Created: 2013-06-24T05:50:27.000Z (almost 13 years ago)
- Default Branch: master
- Last Pushed: 2021-01-26T08:22:39.000Z (over 5 years ago)
- Last Synced: 2025-01-13T13:30:30.191Z (over 1 year ago)
- Language: C
- Size: 1.56 MB
- Stars: 3
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
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.