Copied the code from stack overflow without thinking about it. The
first byte in little endian order should always be LSB so I construct
a more contrived example (0xFFFF0000) which should make it easier to
detect what the first byte is considered on the machine. If it's 0
then the LSB is the first byte hence little endian, otherwise it's big
endian.
On a greater note: Don't never copy no code from stack overflow, bro.
I went up there at 11 o'clock last night trynna get me some code.
Bro, I copied that shit, woke up, my motherfucking LITTLE_ENDIAN
detection don't work. Explain, bro.
While it helped with understanding to use unions as a safe way to
access the underlying bits, this shift based mechanism actually makes
more sense at a glance, particularly by utilising WORD_NTH_BYTE
The new general testing procedure is making a structure for tests that
include all the samples and expected results, then iterating over them
to run the test.
In particular, __LITTLE_ENDIAN__ was not a functioning macro.
Instead, I implemented a version by hand (copied from IBM) that
actually figures out if the machine is little endian or not.
Thank you unit testing!
Just runs a sample of suitably byte arrays with an expected set of
functions. The samples are in little endian format and the outputs
are what we expect them to be. This should run regardless of the
endian of your machine.
This defines useful macros, in particular to print information and to
define tests and test suites.
The idea is a suite will be a set of test functions, where a test
function will fail <=> an assert is called. This allows us to stop
testing immediately upon a test failing and produce viable output.
Folder per module in test/. Header only tests with one actual main
file to implement a program that runs them.
Makefile runs the test and provides some eye candy to show if the test
succeeded or failed. The tests, on the other hand, will show success
or failure for each of them.
No longer relying on darr_t or anything other than the C runtime and
aliases. This means it should be *even easier* to target this via FFI
from other languages without having to initialise my custom made
structures! Furthermore I've removed any form of allocation in the
library so FFI callers don't need to manage memory in any way.
Instead we rely on the caller allocating the correct amount of memory
for the functions to work, with basic error handling if that doesn't
happen.
In the case of inst_read_bytecode, error reporting occurs by making
the return of a function an integer. If the integer is positive it is
the number of bytes read from the buffer. If negative it flags a
possible error, which is a member of read_err_t.
prog_read_bytecode has been split into two functions: prog_read_header
and prog_read_instructions. prog_read_instructions works under the
assumption that the program's header has been filled, e.g. via
prog_read_header. prog_read_header returns 0 if there's not enough
space in the buffer or if the start_address is greater than the count.
prog_read_instructions returns a custom structure which contains an
byte position as well as an error enum, allowing for finer error
reporting.
In the case of inst_write_bytecode via the assumption that the caller
allocated the correct memory there is no need for error reporting.
For prog_write_bytecode if an error occurs due to
In the case of inst_read_bytecode we return the number
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.