Tinyaml defines a Meta Assembly Language for tinyap to parse. The ASTs that result from successful parses can be compiled (i.e. code/data is output to a new program) and walked by user programs.
One goal with tinyaml is to define some kind of high-level scripting language to define and handle a GUI, and more globally, the whole application behaviour. The two current extensions are a first step to define the message bus. Bindings of 2D/3D and event APIs will come later. The purpose of tinyaml is to remain generic and open to all uses, so such extensions will probably come apart.
tinyap 1.2-1 or later is required to build tinyaml 0.3 !
$ wget http://tinyaml.googlecode.com/files/tinyaml-0.3.tar.gz
$ tar xzf tinyaml-0.3.tar.gz
$ svn checkout http://tinyaml.googlecode.com/svn/trunk/ tinyaml-read-only
Now you have your tinyaml source distribution at hands, build it.
$ cd tinyaml
$ CFLAGS=-O3 ./configure -C --prefix=/my/install/prefix/if/not/slash/usr/local
$ make all
$ make install
tinyaml
and tinyaml_dbg
, commands are executed on the fly, left-to-right.
--compile,-c [filename]
--save,-s [filename]
--load,-l [filename]
--run-foreground,-f
--run-background,-b
--trace,-t
--no-trace,-nt
--version,-v
--help,-h
--compile,-c [filename]
--load,-l [filename]
--run,-f
--debug,-d
--version,-v
--help,-h
Start by compiling the bundled script compiler.
$ tinyaml -c script_typeless.melang -s script.compiler
You can now compile quickly and execute any of the files in the directory (except Makefile*).
$ tinyaml -l script.compiler -f -c test.script -f
$ tinyaml_dbg -l script.compiler -f -c test.script -d
Now using an extension :
$ tinyaml -c ../extension/RTC/RTC.lib -f -c rtc.asm -f
Argument types are :
Instructions are assembled in one code segment per program. At runtime, the code segment contains direct pointers to the C routines associated to the opcodes, so the processing overhead is minimized.
Data in the VM is also represented by a two-word compound, containing the data type and the actual data word.
Now, how do programs fit in the VM ?
Threads define the execution context for a program. They provide an instruction pointer, a call stack, a data stack, and a little more. Threads are prioritized using an integer value. The default priority value is 50, the lowest is 0, and the highest is is 99. Actually these values are not bounded, so 2^31-1 is the highest, and 2^31 is the lowest. The Virtual Machine schedules threads based on priority and a configurable timeslice that defaults to 100 instructions.
The grammar consists mainly of the base assembly language itself, sections to define new grammars, plug them, and compile them.
Comments start with a #
and stop at the next end-of-line character. They are treated like whitespace.
The parser accepts whitespace anywhere between tokens.
my_label :
Syntax : decimal integer
Syntax : decimal dotted floating point
Syntax : "standard string"
"\ this strings starts with blanks."
Syntax : @my_label +2 -42
Syntax : &some_symbol
For instance, this code will output "Hello, World." to screen, and demonstrates the use of Int, String and Label arguments.
asm push "Hello, world.\n" print 1 # the parser doesn't care about indentation and instructions per line jmp @skippy # we don't want execute the nops.. nop nop nop skippy: # a ret instruction is always appended to the end of a code segment ret 0 # after compilation, but the label declaration must be followed by end # an instruction.
Data sections are enclosed between data
and end
keywords. A data declaration is an initializer optionally followed by the keyword rep
and the number of repetitions of this initializer. Data types String
, Int
, and Float
can be used as initializers. For instance, the following declaration :
data
0
23.42 rep 2
"Wibble" rep 1
end
Offset | 0 | 1 | 2 | 3 |
Data | 0 | 23.42 | 23.42 | "Wibble" |
language
and end
. Tinyaml uses tinyap's language description grammar.
The following grammar defines a rule foobar
that recognizes "Hello, world".
language Hello ::= "hello". World = /\<world\>/. # don't do this at home ! foobar ::= <Hello> "," <World>. end
This rule must be plugged into the grammar to be effective. This is done via a plug
into
statement. Several plugs are defined :
Since this is a quick and dirty example, we won't bother and plug foobar
at the top-most level, right into _start
.
plug foobar into _start
Once the new rule is plugged, the parser is ready to produce it.
ml/ml_core.gram
.compile
statements. A compile
statement associates a bloc of code (plug rule p_Code
) with an AST node label. This code will be executed each time the compiler walks on a node with this label (to know more about AST node labels, refer to tinyap's AST creation). Actually, when it walks on node labeled "foobar", the compiler will look for compile method name “ _internal_prefix_ foobar ”. The following code will "pretty-print" the foobar node and write instructions to display "hi there". compile foobar asm pp_curNode # prettyprint current node push "hi there." write_oc_String "push" # write opcode << push "hi there." >> to output program compileStateNext # all done, OK, go on. end
on
keyword, and will behave like compile methods, i.e. the method Twist
will be invoked to process the AST node named Twist
.Short example :
walker Toto init asm push "Toto::init\n" print 1 end term asm push "Toto::term\n" print 1 end default asm push "Unknown node or generic behaviour : " astGetOp push "\n" print 3 compileStateNext # we are not in the compiler, but the walking end # mechanism remains the same. on foobar asm pp_curNode push "We are on node foobar which has " astGetChildrenCount push "\ children.\n" print 3 compileStateDone # our foobar node is top-level node, so we have finished after processing it. end end
lib
section.
The lib
section contains file
statements (generally one) and opcode
statements. file
followed by a string opens the corresponding shared object. Currently on *NIX it is $pkglibdir/libtinyaml_ string.so. The opcode
keyword is followed by the opcode mnemonic without quotes. If the opcode has an argument, the mnemonic is followed by a colon ":" and the argument type Int
, Float
, String
, EnvSym
, or Label
.
The argument word for EnvSym
is the index of the symbol in the current environment, computed at compile/unserialize-time.
The argument word for Label
is the relative offset of the label, computed at compile/unserialize-time.
When the tinyaml library file is compiled, the opcodes are added to the dictionary and their C functions are resolved. Tinyaml searches for a function named vm_op_MNEMONIC_ARGTYPE or vm_op_MNEMONIC if the opcode has no argument. Such a function must be of type compatible with opcode_stub_t.
Here is a quick foobar example :
lib
file "foobar"
opcode foo
opcode bar:Int
end
$ gcc -shared -Wall foobar.c -ltinyaml -o libtinyaml_foobar.so $ sudo cp !$ /usr/local/lib/tinyaml/
asm foo bar 42 end
$ tinyaml -q -c foobar.lib -c foobar.asm -f foo pressure : 42 mbar
RTC_init
: initialize the Real-Time Clock RTC_term
: terminate the RTC RTC_start
: rock around the clock RTC_stop
: stand stillRTC_sched
: pop float (timestamp), pop function object (task), schedule task at timestampRTC_wait
: pop float (timestamp), wait for RTC date to reach timestampRTC_getDate
: get current date in secondsRTC_getBeat
: get current date in beatsRTC_setBeat:Float
: set current date (beats) to argumentRTC_setBeat
: pop float, set current date (beats)RTC_getTempo
: get current tempoRTC_setTempo:Float
: set current tempo to argumentRTC_setTempo
: pop float, set current tempoRTC_getRes
: get the actual RTC resolutionRTC_setRes:Float
: try and set the RTC resolution to the argumentRTC_setRes
: pop float, try and set the RTC resolution_RTC_nextTask
: wait for next task in task queue. rtc_init
: initialize and start the RTC subsystem. RTC_sched and RTC_wait can be used directly after calling rtc_init
.rtc_term
: terminate the RTC subsystem.
RTC_getRes
should be used after RTC_setRes
to get the actual resolution.msgQueueNew
: create a new managed message queuemsgQueueWrite
: pop data to be written, pop message queue, write data into queuemsgQueueReaderNew
: create a new managed message queue readermsgQueueRead
: pop msgQueueReader, block until data is available, read and push data.