struct.c is used for the general construction, inspection and deletion
of the virtual machine structure (vm_t). runtime defines the
execution routines, error enum, lookup tables, etc.
Firstly abuse OPCODE_DATA_TYPE along with integer arithmetic to do a
POP_ROUTINE table lookup (no more ugly conditionals). Then make a
format string table which we can lookup using the same data type.
Same method as when simplifying OP_POP's implementation: use the
lookup table along with OPCODE_DATA_TYPE abuse. In this case I made a
lookup table called OP_POP for this method to work, but it wasn't difficult.
OP_PUSH: Increment program pointer iff no error from PUSH_ROUTINE call
STACK_ROUTINES: Same as OP_PUSH
RET: Pop off the call stack iff the jump to the address was good.
Due to reordering I need to have two macros for checking if an opcode
is of a type. If the type is signed then the upper bound must be
OP_<type>_LONG whereas if it is unsigned then the upper bound must be
OP_<type>_WORD.
Moved all opcodes that use unsigned types before the signed types AND
ordered signed types into BYTE, CHAR, HWORD, INT, WORD, LONG. This is
not only logically consistent but also looks prettier.
This simple fix made the routine for OP_POP not require an additional
dispatch step on top of the conditional due to OPCODE_DATA_TYPE,
instead using data_type_t as a map from an opcode's base type to a
specific type.
This "header" is now embedded directly into the struct. The semantic
of a header never really matters in the actual runtime anyway, it's
only for bytecode (de)serialising.
I've decided to split the project into 2 repositories: the assembler
and the runtime. The runtime will contain both the executable and
lib/ while the assembler will have the runtime as a git submodule and
use it to build. I think this is a clean solution, a lot cleaner than
having them all in one project where the Makefile has to massively
expand.
Instead of %const(<name>) ... %end it will now be %const <name>
... %end i.e. the first symbol after %const will be considered the
name of the constant similar to %use.
The preprocess_* functions are now privately contained within the
implementation file to help the preprocesser outer function.
Furthermore I've simplified the API of the preprocess_* functions by
making them only return pp_err_t and store their results in a vector
parameter taken by reference.
We leave the parameter tokens alone, considering it constant, while
the parameter vec_out is used to hold the new stream of tokens. This
allows the caller to have a before and after view on the token stream
and reduces the worry of double frees.