This is not yet another Meta-Language

0.1

Contents

Introduction
Build and Install
Usage
Quick tests
Virtual Machine internals
Meta Assembly Language
         Assembly language
         Meta-language
         Declaring new compile methods
         Defining new AST Walkers
Binding new libraries
Core library
Bundled extensions
         Real-Time Clock (GNU/Linux)
         Message Queues

Introduction

Tinyaml is a Virtual Machine, a Compiler, and a Compiler-Compiler, all in one, powered by the abstract parser tinyap.

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.

Build and Install

First, get the source code.
Note:
You need to build and install tinyap beforehand.

tinyap 1.2-1 or later is required to build tinyaml 0.3 !

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
You might need root privileges to run make install.

Usage

In both tinyaml and tinyaml_dbg, commands are executed on the fly, left-to-right.

Quick tests

Jump into the /tests/ directory.

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 
Oops ! Time to discover the (wannabe) debugger.
 $ tinyaml_dbg -l script.compiler -f -c test.script -d 
If you don't like pressing repetedly 's', just press 'r' and watch it run until it fails.

Now using an extension :

 $ tinyaml -c ../extension/RTC/RTC.lib -f -c rtc.asm -f 

Virtual Machine internals

Processing inside the VM is based on 32bit words. The VM processes exactly one instruction per execution cycle. An instruction is a two-word compound containing an opcode and a 32bit argument. An opcode is defined by its mnemonic, its argument type, and the corresponding C function.

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.

Meta Assembly Language

You should be familiar with tinyap's language description grammar and features such as grammar plugins before you read further.

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.

Assembly language

An instruction may be preceded by a label declaration.
 my_label : 
An instruction is the opcode mnemonic optionally followed by its argument.

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 
will result in the following data segment :
 Offset |    0    |    1    |    2    |    3     |
 Data   |    0    |  23.42  |  23.42  | "Wibble" |

Meta-language

The grammar definitions to append to the current grammar are defined in sections enclosed by the keywords 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.

Note:
Since compilation is done after parsing, the rule will be plugged after the whole file is parsed, so you can't use a rule in the same file it is defined in.
For details, you can have a look at the whole grammar in file ml/ml_core.gram.

Declaring new compile methods

See also:
Compiler
Compile methods are defined with 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

Defining new AST Walkers

Static analysis is not possible with compile methods, or would be seriously awkward, but tinyaml allows the definition of other AST walkers. A walker is invoked to evaluate a given node, and returns the result of its evaluation. It must provide an init method, a termination method, and a default method to visit unknown nodes. Other methods in the walker are declared by the 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

Binding new libraries

Two files are necessary to define a Tinyaml extension, a binary shared object containing the C opcode routines, and a tinyaml source file starting with a 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 :

Core library

Tinyaml comes with a small set of opcodes to handle the basic data and containers it defines, and to perform arithmetic boolean and bitwise operations. All the opcodes are listed in the module Core Opcodes.

See also:
The corresponding library file is ml/ml_core.lib

Bundled extensions

Real-Time Clock (GNU/Linux)

Note:
RTC_getRes should be used after RTC_setRes to get the actual resolution.

Message Queues


Generated on Wed Feb 6 14:46:04 2008 for TinyaML by  doxygen 1.5.3