Epoch now has a functioning virtual machine: Say hello to Fugue
I decided to take a rather different route for the implementation. Traditionally when I've done a toy language, I've started with a parser and gotten it generating a token stream, and then worked from there on acting on that stream, be it feeding into a compiler or dumping the output into an interpreter that executes it "live."
This time, I'm building an infrastructure that you could call something of an executable compiler pipeline. The same code is intended to work for both an interpreter/VM and a compiler.
For example, there is a class representing a lexical variable scope, which can be asked to dump a list of all its variables. This can be used by a compiler to statically assign stack space for those variables; it can be used at runtime by an interpreter for reflection purposes; and so on.
As another example, the built-in addition operator is represented by a class as well. It can "serialize" itself during the compile process to produce sensible output based on the target language. For example, serializing addition to C would produce "(a + b)", and serializing to assembler would produce "ADD b, a".
This will provide hooks for easy drop-in of compiler back ends, similar to how GCC works. It also allows deep introspection in the VM itself.
The interesting thing is that there is actually no parser or syntax for the language right now. Programs are "built" by writing code in the VM's unit test module, and instantiating the wrapper classes for each first-class language construct from C++. The VM class then executes those wrappers and produces results.
This is what the unit test for addition looks like:
VM::Operations::SumIntegrals(scope, L"baz", L"foo", L"bar").Execute();
VM::Operations::SumIntegrals(scope, L"quux", L"baz", 32).Execute();
UNITTEST_ASSUMPTION(scope.GetIntegralVariableValue(L"foo") == 3);
UNITTEST_ASSUMPTION(scope.GetIntegralVariableValue(L"bar") == 7);
UNITTEST_ASSUMPTION(scope.GetIntegralVariableValue(L"baz") == 10);
UNITTEST_ASSUMPTION(scope.GetIntegralVariableValue(L"quux") == 42);
Note the way that a SumIntegrals object is created, and then Execute() is called to actually "run" the operation. This decouples the execution of code from its representation, meaning that a SumIntegrals object can actually be passed around unevaluated as a first-class expression construct. The potential uses for this are myriad, obviously, but the primary value of it is that it allows an easy swap between imperative and functional styles based purely on how the VM chains its Execute() calls.
As of right now, the VM only supports integer variables and booleans, and only has the addition operation. However, the framework is pretty well defined and adding more features should move quickly. Look for consistent updates over the next several weeks. Finishing off the arithmetic operators, implementing basic control logic, and getting the IO library working are all on the list.
My goal is to eventually get the VM to the point where I can run a simple Sudoku solver in one of the unit tests. Once I've reached that point, I will switch to working on adding the syntactical front-end to the system.
So there you have it: Epoch is officially in production.