Created
May 22, 2023 07:13
-
-
Save meshula/48a62b20b3db57fdd7e70b78567fa316 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define LABTEXT_ODR | |
#include "include/LabText/LabText.h" | |
#include <stdio.h> | |
#include <map> | |
using std::map; | |
#include <string> | |
using std::string; | |
#include <vector> | |
using std::vector; | |
char const* pingpongSrc = R"( | |
(require time) | |
(require io) | |
(io.print "hello world!") | |
(machine main | |
(state ping | |
(io.print "ping!") | |
(on time.after 0.5 | |
(goto pong) | |
) | |
) | |
(state pong | |
(io.print "pong!") | |
(on time.after 0.5 | |
(goto ping) | |
) | |
) | |
(state main | |
(goto ping) | |
) | |
) | |
(launch main) | |
)"; | |
char const* pingpongSrc2 = R"( | |
(require time) | |
(require io) | |
(machine main | |
(state ping | |
(io.print "ping!") | |
(on time.after 0.5 | |
(goto pong) | |
) | |
) | |
(state pong | |
() | |
) | |
(state main | |
() | |
) | |
) | |
(launch main) | |
)"; | |
//---------------------------------------------------------------------- | |
// forward declarations | |
struct Landru; | |
struct MachineExemplar; | |
struct MachineInstance; | |
//---------------------------------------------------------------------- | |
// function pointer for library functions whose signature is | |
// void func(Landru* l, tsParsedSexpr_t* curr, MachineInstance* inst); | |
typedef void (*LandruLibFunc)(Landru*, tsParsedSexpr_t*, MachineInstance*); | |
//---------------------------------------------------------------------- | |
typedef enum { | |
moLaunchMachine, | |
moGotoState, | |
moSetVariable, | |
moOn, | |
moFunctionCall, | |
} MachineOperation; | |
typedef struct { | |
MachineOperation op; | |
tsParsedSexpr_t* arg; // for launch, goto, args for call | |
LandruLibFunc func; // for call function | |
tsStrView_t name; // for debugging purposes | |
MachineExemplar* me; // for when the operation needs to recurse | |
} MachineInstruction; | |
typedef enum { | |
mtRoot, | |
mtMachine, | |
mtState, | |
mtOn, | |
mtFunction, | |
mtMonad, | |
mtList, | |
} MachineType; | |
typedef struct MachineExemplar { | |
string name; | |
MachineType isState; | |
MachineExemplar* parent; | |
tsParsedSexpr_t* arg; | |
vector<MachineInstruction> instructions; | |
map<string, MachineExemplar*> machines; | |
map<string, MachineExemplar*> functions; | |
vector<MachineExemplar*> ons; | |
} MachineExemplar; | |
//---------------------------------------------------------------------- | |
// A machine instance contains the local runtime scope for a machine. | |
// THe local scope context contains the machine exemplar, the local variables, | |
// and the callbacks and functions defined in the machine. | |
// The lcoal context also contains all the states that can be dispatched | |
// from the current context. | |
// The instance contains not only the local runtime scope, but also all the | |
// child scopes that have been launched from this scope. | |
typedef struct { | |
tsParsedSexpr_t* arguments; | |
map<string, tsParsedSexpr_t*> vars; | |
map<string, tsParsedSexpr_t*> callbacks; | |
map<string, tsParsedSexpr_t*> functions; | |
map<string, tsParsedSexpr_t*> states; | |
} MachineLocalContext; | |
typedef struct MachineInstance { | |
MachineExemplar* me; | |
vector<MachineLocalContext> localStates; | |
} MachineInstance; | |
//---------------------------------------------------------------------- | |
// The Landru context contains the all of the library functions | |
// imported by the program. It contains the root machine instance | |
// which comprises the global scope. | |
// | |
// It contains a list of the imported libraries, for debugging purposes, | |
// and a list of the machines to be launched the next time the engine | |
// is updated. That list is cleared after the machines are launched. | |
typedef struct { | |
MachineExemplar* me; // machine to launch | |
MachineInstance* parent; // the scope to launch the machine into | |
} PendingLaunch; | |
typedef struct Landru { | |
MachineInstance root; | |
vector<string> req; | |
map<string, LandruLibFunc> libraryFunctions; | |
vector<PendingLaunch> launch; | |
} Landru; | |
//---------------------------------------------------------------------- | |
tsParsedSexpr_t* compileMachine( | |
tsParsedSexpr_t* curr, | |
Landru* l, | |
MachineType mt, | |
MachineExemplar** exemplar); | |
//---------------------------------------------------------------------- | |
void print_spaces(int indent) { | |
static char spaces[256]; | |
static int spaces_sz = 0; | |
if (spaces_sz == 0) { | |
for (int i = 0; i < 255; ++i) { | |
spaces[i] = ' '; | |
} | |
spaces[255] = 0; | |
spaces_sz = 255; | |
} | |
if (indent < 0 || indent > 255) | |
return; | |
printf("%s", spaces + (255 - indent)); | |
} | |
typedef enum { | |
markerAtom, markerClose, markerReturn | |
} printMarker; | |
void dump_parsed(tsParsedSexpr_t* curr) { | |
int indent = 0; | |
printMarker marker = markerAtom; | |
while (curr) { | |
switch (curr->token) { | |
case tsSexprAtom: { | |
std::string s = std::string(curr->str.curr, curr->str.sz); | |
printf("%s ", s.c_str()); | |
marker = markerAtom; | |
break; | |
} | |
case tsSexprPushList: | |
printf("\n"); print_spaces(indent); printf("("); | |
indent += 3; | |
marker = markerAtom; | |
break; | |
case tsSexprPopList: | |
indent -= 3; | |
if (marker == markerReturn) | |
print_spaces(indent); | |
printf(")\n"); | |
marker = markerReturn; | |
break; | |
case tsSexprInteger: | |
printf(" %lld ", curr->i); | |
marker = markerAtom; | |
break; | |
case tsSexprFloat: | |
printf(" %f ", curr->f); | |
marker = markerAtom; | |
break; | |
case tsSexprString: { | |
std::string s = std::string(curr->str.curr, curr->str.sz); | |
printf(" \"%s\" ", s.c_str()); | |
marker = markerAtom; | |
break; | |
} | |
} | |
curr = curr->next; | |
} // while | |
printf("\n"); | |
} | |
void dumpExemplar(MachineExemplar* me, int indent = 0) { | |
if (!me) | |
return; | |
print_spaces(indent); | |
printf("MachineExemplar: %s", me->name.c_str()); | |
switch (me->isState) { | |
case mtRoot: printf(" type: root\n"); break; | |
case mtMachine: printf(" type: machine\n"); break; | |
case mtState: printf(" type: state\n"); break; | |
case mtOn: printf(" type: on\n"); break; | |
case mtFunction: printf(" type: function\n"); break; | |
case mtMonad: printf(" type: monad\n"); break; | |
case mtList: printf(" type: list\n"); break; | |
} | |
for (auto& instr : me->instructions) { | |
print_spaces(indent); | |
switch (instr.op) { | |
case moLaunchMachine: printf(" op: launch machine"); break; | |
case moGotoState: printf(" op: goto state"); break; | |
case moSetVariable: printf(" op: set variable"); break; | |
case moOn: printf(" op: on"); break; | |
case moFunctionCall: printf(" op: function call"); break; | |
} | |
printf(" [%.*s]\n", (int) instr.name.sz, instr.name.curr); | |
} | |
for (auto& on : me->ons) { | |
print_spaces(indent); | |
printf(" on: %s\n", on->name.c_str()); | |
dumpExemplar(on, indent + 2); | |
} | |
for (auto& func : me->functions) { | |
print_spaces(indent); | |
printf(" function: %s\n", func.second->name.c_str()); | |
dumpExemplar(func.second, indent + 2); | |
} | |
for (auto& machine : me->machines) { | |
dumpExemplar(machine.second, indent + 2); | |
} | |
} | |
void dumpMachineInstance(MachineInstance* inst) { | |
if (!inst) | |
return; | |
printf("MachineInstance: %s\n", inst->me->name.c_str()); | |
printf(" states:\n"); | |
for (auto i = inst->localStates.back().states.begin(); i != inst->localStates.back().states.end(); ++i) { | |
printf(" %s\n", i->first.c_str()); | |
} | |
} | |
void dumpLandru(Landru* l) { | |
printf("require:\n"); | |
for (auto& s : l->req) { | |
printf(" %s\n", s.c_str()); | |
} | |
} | |
void dumpParsedExpr(const char* prefix, tsParsedSexpr_t* curr) { | |
if (curr) { | |
switch(curr->token) { | |
case tsSexprAtom: | |
printf("%s[atom] %.*s\n", prefix, (int) curr->str.sz, curr->str.curr); | |
break; | |
case tsSexprFloat: | |
printf("%s%f\n", prefix, curr->f); | |
break; | |
case tsSexprInteger: | |
printf("%s%lld\n", prefix, curr->i); | |
break; | |
case tsSexprString: | |
printf("%s[string] %.*s\n", prefix, (int) curr->str.sz, curr->str.curr); | |
break; | |
case tsSexprPushList: | |
printf("%spush list\n", prefix); | |
break; | |
case tsSexprPopList: | |
printf("%spop list\n", prefix); | |
break; | |
} | |
} | |
} | |
[[noreturn]] void diagnoseAndThrow(tsParsedSexpr_t* curr, const char* msg) { | |
printf("Error: %s\n Current token and following are:\n", msg); | |
for (int i = 0; i < 7; ++i) { | |
if (curr) { | |
dumpParsedExpr(i == 0? "--> " : " ", curr); | |
curr = curr->next; | |
} | |
} | |
throw msg; | |
} | |
//---------------------------------------------------------------------- | |
// lib io | |
// | |
void io_register(Landru* l) { | |
} | |
void registerLibrary(Landru* l, const char* name) { | |
if (strcmp(name, "io") == 0) { | |
io_register(l); | |
} | |
else if (strcmp(name, "time") == 0) { | |
// | |
} | |
else if (strcmp(name, "test") == 0) { | |
// | |
} | |
else | |
throw "unknown library"; | |
} | |
//---------------------------------------------------------------------- | |
bool atomIsKeyword(const tsParsedSexpr_t* curr) { | |
if (!curr || curr->token != tsSexprAtom) | |
return false; | |
static const char* keywords[] = {"require", "machine", "state", "on", "goto", "launch"}; | |
static const int numKeywords = sizeof(keywords) / sizeof(keywords[0]); | |
for (int i = 0; i < numKeywords; ++i) { | |
if (sizeof(keywords[i]) != curr->str.sz) | |
continue; | |
if (strncmp(curr->str.curr, keywords[i], curr->str.sz) == 0) | |
return true; | |
} | |
return false; | |
} | |
void landru_add_require(Landru* l, tsParsedSexpr_t* curr) { | |
if (!curr || curr->token != tsSexprAtom || !curr->str.sz) | |
return; | |
l->req.push_back(std::string(curr->str.curr, curr->str.sz)); | |
} | |
LandruLibFunc findLibraryFunction(Landru* l, const char* name) { // search root | |
auto f = l->libraryFunctions.find(name); | |
if (f != l->libraryFunctions.end()) | |
return f->second; | |
return NULL; | |
} | |
tsParsedSexpr_t* findFunction(Landru* l, MachineInstance* inst, const char* name) { | |
// search local states | |
for (auto it = inst->localStates.rbegin(); it != inst->localStates.rend(); ++it) { | |
auto& ctx = *it; | |
auto f = ctx.functions.find(name); | |
if (f != ctx.functions.end()) { | |
return f->second; | |
} | |
} | |
// search root | |
auto f = l->root.localStates[0].functions.find(name); | |
if (f != l->root.localStates[0].functions.end()) { | |
return f->second; | |
} | |
return NULL; | |
} | |
tsParsedSexpr_t* findClosingParen(tsParsedSexpr_t* openParen) { | |
int level = 0; | |
tsParsedSexpr_t* curr = openParen; | |
while (curr && level >= 0) { | |
switch (curr->token) { | |
case tsSexprPushList: | |
level++; | |
break; | |
case tsSexprPopList: | |
level--; | |
if (level == 0) | |
return curr; | |
break; | |
case tsSexprAtom: | |
case tsSexprString: | |
case tsSexprFloat: | |
case tsSexprInteger: | |
break; | |
} | |
curr = curr->next; | |
} | |
return NULL; | |
} | |
void signalStateExit(MachineInstance* inst) { | |
// if we have a current state, then call the state exit function | |
// and clear the state's local context. The current context becomes | |
// the state's context's parent context. | |
if (!inst || !inst->localStates.size()) | |
throw "no current state"; | |
inst->localStates.pop_back(); | |
} | |
void signalStateEnter(MachineInstance* inst) { | |
// if we have a current state, then call the state enter function | |
// and create a new local context. Set the state's local context's parent | |
// to the current context, and set the current context to the new | |
// local context. | |
if (!inst) | |
throw "no current state"; | |
inst->localStates.push_back(MachineLocalContext{}); | |
} | |
void signalCallbackCancelled(MachineInstance* inst) { | |
// if we have a current state, then call the callback cancelled function | |
// and clear the state's local context. The current context becomes | |
// the state's context's parent context. | |
if (!inst || !inst->localStates.size()) | |
throw "no current state"; | |
} | |
void signalCallbackSet(MachineInstance* inst) { | |
// if we have a current state, then call the callback set function | |
// and create a new local context. Set the state's local context's parent | |
// to the current context, and set the current context to the new | |
// local context. | |
if (!inst) | |
throw "no current state"; | |
} | |
void diagnoseStateNotFound(MachineInstance* inst, string stateName) { | |
printf("state not found: %s\n", stateName.c_str()); | |
printf("available states:\n"); | |
for (auto& c : inst->localStates) { | |
for (auto& s : c.states) { | |
printf(" %s\n", s.first.c_str()); | |
} | |
} | |
} | |
tsParsedSexpr_t* compileLaunch(tsParsedSexpr_t* curr, MachineExemplar* me) { | |
// peek at next to get the name of the machine to launch | |
tsParsedSexpr_t* name = curr->next; | |
if (name && name->token == tsSexprAtom) { | |
std::string nameStr = std::string(name->str.curr, name->str.sz); | |
// the next should be a close paren | |
tsParsedSexpr_t* body = name->next; | |
if (!body || body->token != tsSexprPopList) | |
diagnoseAndThrow(curr, "expected end of list"); | |
MachineInstruction instr; | |
instr.op = moLaunchMachine; | |
instr.name = name->str; | |
me->instructions.push_back(instr); | |
return body; // return closing paren | |
} | |
else { | |
diagnoseAndThrow(curr, "expected machine name"); | |
} | |
} | |
tsParsedSexpr_t* compileGoto(tsParsedSexpr_t* curr, MachineExemplar* me) { | |
// peek at next to get the name of the state to launch | |
tsParsedSexpr_t* name = curr->next; | |
if (name && name->token == tsSexprAtom) { | |
std::string nameStr = std::string(name->str.curr, name->str.sz); | |
// the next should be a close paren | |
tsParsedSexpr_t* body = name->next; | |
if (!body || body->token != tsSexprPopList) | |
diagnoseAndThrow(curr, "expected end of list"); | |
MachineInstruction instr; | |
instr.op = moGotoState; | |
instr.name = name->str; | |
me->instructions.push_back(instr); | |
return body; // return closing paren | |
} | |
else { | |
diagnoseAndThrow(curr, "expected state name"); | |
} | |
} | |
tsParsedSexpr_t* compileSet(tsParsedSexpr_t* curr, Landru* l, MachineExemplar* me) { | |
// peek at next to get the name of variable to set | |
tsParsedSexpr_t* name = curr->next; | |
if (name && name->token == tsSexprAtom) { | |
std::string nameStr = std::string(name->str.curr, name->str.sz); | |
// the next token should either be a constant or a list | |
tsParsedSexpr_t* body = name->next; | |
switch (body->token) { | |
case tsSexprPushList: { | |
// the next token should be a close paren | |
tsParsedSexpr_t* body = name->next; | |
if (!body || body->token != tsSexprPopList) | |
throw "expected end of list"; | |
MachineExemplar* monad = NULL; | |
compileMachine(body, l, mtMonad, &monad); | |
if (!monad) | |
throw "expected monad"; | |
MachineInstruction instr; | |
instr.op = moSetVariable; | |
instr.name = name->str; | |
me->instructions.push_back(instr); | |
instr.arg = NULL; | |
instr.me = monad; | |
return body->next; | |
} | |
break; | |
case tsSexprAtom: | |
case tsSexprString: | |
case tsSexprFloat: | |
case tsSexprInteger: { | |
MachineInstruction instr; | |
instr.op = moSetVariable; | |
instr.name = name->str; | |
me->instructions.push_back(instr); | |
instr.me = NULL; | |
instr.arg = body; | |
return body->next; | |
} | |
break; | |
case tsSexprPopList: | |
throw "unexpected end of list"; | |
break; | |
} | |
if (!body || body->token != tsSexprPopList) | |
throw "expected end of list"; | |
return body->next; | |
} | |
else { | |
throw "expected state name"; | |
} | |
} | |
void expectConstantOrList(tsParsedSexpr_t* curr) { | |
if (!curr) | |
throw "expected a constant or list"; | |
switch (curr->token) { | |
case tsSexprAtom: | |
case tsSexprString: | |
case tsSexprFloat: | |
case tsSexprInteger: | |
case tsSexprPushList: | |
return; | |
case tsSexprPopList: | |
throw "expected a constant or list"; | |
} | |
} | |
tsParsedSexpr_t* compileFunctionCall(tsParsedSexpr_t* curr, Landru* l, MachineExemplar* me) { | |
// compile a function call. The existance or non-existance | |
// of the function will be discovered at runtime. | |
tsParsedSexpr_t* name = curr; | |
curr = curr->next; | |
expectConstantOrList(curr); | |
MachineInstruction instr; | |
instr.op = moFunctionCall; | |
instr.name = name->str; | |
me->instructions.push_back(instr); | |
instr.me = NULL; | |
instr.arg = NULL; | |
MachineExemplar* monad = NULL; | |
if (curr->token == tsSexprPushList) { | |
curr = compileMachine(curr, l, mtList, &monad); | |
if (!monad) | |
diagnoseAndThrow(curr, "expected monad"); | |
instr.me = monad; | |
} | |
else { | |
instr.arg = curr; | |
curr = curr->next; // consume value | |
if (!curr || curr->token != tsSexprPopList) | |
diagnoseAndThrow(curr, "expected closing paren"); | |
} | |
return curr; | |
} | |
tsParsedSexpr_t* compileRequire(tsParsedSexpr_t* curr, Landru* l) { | |
// require statements are immediately executed because they have global | |
// scope. they aren't compiled into the current machine. | |
// peek at next to get the name of the required library | |
tsParsedSexpr_t* name = curr->next; | |
if (name && name->token == tsSexprAtom) { | |
std::string nameStr = std::string(name->str.curr, name->str.sz); | |
registerLibrary(l, nameStr.c_str()); | |
name = name->next; // consume the name | |
if (!name || name->token != tsSexprPopList) | |
diagnoseAndThrow(curr, "expected paren at end of scope"); | |
return name; // consume the closing paren | |
} | |
else | |
diagnoseAndThrow(curr, "expected library name"); | |
} | |
// parse given an open parens token. Return the corresponding close paren. | |
// | |
tsParsedSexpr_t* compileMachine( | |
tsParsedSexpr_t* curr, | |
Landru* l, | |
MachineType mt, | |
MachineExemplar** exemplar) { | |
if (!curr || !l || (curr->token != tsSexprPushList)) | |
diagnoseAndThrow(curr, "expected an open paren\n"); | |
curr = curr->next; | |
if (!curr) | |
diagnoseAndThrow(curr, "unexpected end of program\n"); | |
// the top level machine is implicit and unnamed. | |
std::string name = "top"; | |
// create new substate | |
MachineExemplar* me = new MachineExemplar(); | |
me->isState = mt; | |
*exemplar = me; | |
if (mt == mtList) { | |
} | |
else if (mt == mtRoot) { | |
} | |
else { | |
// machine, state, defun, and defum expect to be followed by a name | |
if (curr->token != tsSexprAtom) | |
diagnoseAndThrow(curr, "expected machine atom"); | |
std::string token = std::string(curr->str.curr, curr->str.sz); | |
switch (mt) { | |
case mtMachine: | |
if (token != "machine") | |
diagnoseAndThrow(curr, "expected machine atom"); | |
break; | |
case mtState: | |
if (token != "state") | |
diagnoseAndThrow(curr, "expected state atom"); | |
break; | |
case mtFunction: | |
if (token != "defun") | |
diagnoseAndThrow(curr, "expected defun atom"); | |
break; | |
case mtMonad: | |
if (token != "defum") | |
diagnoseAndThrow(curr, "expected defum atom"); | |
break; | |
case mtOn: | |
if (token != "on") | |
diagnoseAndThrow(curr, "expected defum atom"); | |
break; | |
case mtRoot: | |
diagnoseAndThrow(curr, "root must be at root position"); | |
case mtList: | |
// anything is valid in the next position | |
break; | |
} | |
curr = curr->next; | |
if (!curr || curr->token != tsSexprAtom) | |
diagnoseAndThrow(curr, "expected state name"); | |
name = std::string(curr->str.curr, curr->str.sz); | |
curr = curr->next; | |
} | |
me->name = name; | |
if (mt == mtOn) { | |
// the name of the event is already in name. | |
// The next token is a constant value. | |
switch (curr->token) { | |
case tsSexprAtom: | |
case tsSexprString: | |
case tsSexprFloat: | |
case tsSexprInteger: | |
break; | |
default: | |
diagnoseAndThrow(curr, "expected constant value to parameterize condition"); | |
} | |
me->arg = curr; | |
curr = curr->next; | |
if (!curr || curr->token != tsSexprPushList) | |
diagnoseAndThrow(curr, "expected body to follow on condition\n"); | |
} | |
// at this level in the parsing, there are things that are compile time; | |
// require statements are evaluated immediately, as are function calls | |
// outside of states. | |
// | |
// other things are compiled for use at runtime. These include function | |
// declarations, and nested machine definitions. | |
// | |
// states are compiled into the state table | |
// | |
// goto to statements are added to the local state as a pending goto | |
// and are evaluated by the runtime the next time the runtime runs. | |
// | |
// launch statements are appended into the launch list, and are evaluated | |
// by the runtime the next time the run time runs. | |
int level = 0; | |
tsParsedSexpr_t* openParen = NULL; | |
while (curr && level >= 0) { | |
switch (curr->token) { | |
case tsSexprPushList: | |
openParen = curr; | |
curr = curr->next; | |
level++; | |
continue; | |
case tsSexprPopList: | |
if (!level) | |
return curr; | |
else if (level == 1) | |
return curr->next; | |
openParen = NULL; | |
curr = curr->next; | |
level--; | |
continue; | |
case tsSexprAtom: { | |
std::string s = std::string(curr->str.curr, curr->str.sz); | |
if (s == "machine") { | |
// parse a local machine | |
MachineExemplar* sub_me = NULL; | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileMachine(openParen, l, mtMachine, &sub_me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "machine not properly closed"); | |
if (sub_me) { | |
// and make it available to the local scope | |
sub_me->parent = me; | |
sub_me->isState = mtMachine; | |
me->machines[sub_me->name] = sub_me; | |
} | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "state") { | |
// parse a local state; the only difference between a state and | |
// a machine is that machines are launched into the same | |
// scope as the launching machine, and states are launched | |
// into the local scope of the machine. | |
// Additionally, switching into a state terminates the | |
// present state, and launching a machine does not affect the | |
// machine doing the launching. | |
MachineExemplar* sub_me = NULL; | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileMachine(openParen, l, mtState, &sub_me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "state not properly closed"); | |
if (sub_me) { | |
// and make it available to the local scope | |
sub_me->parent = me; | |
sub_me->isState = mtState; | |
me->machines[sub_me->name] = sub_me; | |
} | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "on") { | |
MachineExemplar* sub_me = NULL; | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileMachine(openParen, l, mtOn, &sub_me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "on not properly closed"); | |
if (sub_me) { | |
// and make it available to the local scope | |
sub_me->parent = me; | |
sub_me->isState = mtState; | |
me->ons.emplace_back(sub_me); | |
} | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "require") { | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileRequire(curr, l); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "require not properly closed"); | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "defun") { | |
// a function is just like a state, except invoking it | |
// does not terminate the present state or launch a new one. | |
// a function is a pure function, and does not have access to | |
// the local scope of the machine that invoked it. | |
MachineExemplar* sub_me = NULL; | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileMachine(openParen, l, mtFunction, &sub_me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "defun not properly closed"); | |
if (sub_me) { | |
// and make it available to the local scope | |
sub_me->parent = me; | |
sub_me->isState = mtFunction; | |
me->functions[sub_me->name] = sub_me; | |
} | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "defum") { | |
// a monadic function is just like a function, except that | |
// it may have side effects. | |
MachineExemplar* sub_me = NULL; | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileMachine(openParen, l, mtMonad, &sub_me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "defum not properly closed"); | |
if (sub_me) { | |
// and make it available to the local scope | |
sub_me->parent = me; | |
sub_me->isState = mtMonad; | |
me->functions[sub_me->name] = sub_me; | |
} | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "launch") { | |
// compile a launch operation | |
// note that at the moment a launch occurs into the local scope | |
// and not a higher scope. The next iteration of the compiler | |
// will include sytnax to allow a launch to occur into a specific | |
// scope. Perhaps there will be named scopes, or perhaps a scheme | |
// whereby a sequence of carets might indicate the number of | |
// parent scopes up to launch into. Maybe both. Needs a bit more | |
// thought. Perhaps an operation might be defineScope, which | |
// would name a scope. An example use case might be | |
// (scope playfield) | |
// and a player machine might launch bubble machines into the | |
// playfield scope. That's probably the best idea. | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileLaunch(curr, me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "launch not properly closed"); | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "goto") { | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileGoto(curr, me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "goto not properly closed"); | |
curr = curr->next; | |
--level; | |
} | |
else if (s == "set") { | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileSet(curr, l, me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "set not properly closed"); | |
curr = curr->next; | |
--level; | |
} | |
else { | |
tsParsedSexpr_t* verify = findClosingParen(openParen); | |
curr = compileFunctionCall(curr, l, me); | |
if (verify != curr) | |
diagnoseAndThrow(curr, "function is not properly closed"); | |
curr = curr->next; | |
--level; | |
} | |
break; | |
} | |
case tsSexprString: | |
case tsSexprFloat: | |
case tsSexprInteger: | |
break; | |
} | |
} | |
*exemplar = me; | |
return curr; | |
} | |
bool testLandru(const char* src, bool dump_exemplar = false) { | |
tsParsedSexpr_t* parsed = tsParsedSexpr_New(); | |
parsed->token = tsSexprAtom; | |
tsStrView_t str = (tsStrView_t){src, strlen(src)}; | |
/*tsStrView_t end_of_str =*/ tsStrViewParseSexpr(&str, parsed, 0); | |
dump_parsed(parsed); | |
Landru l; | |
l.root.me = NULL; | |
l.root.localStates.push_back({}); | |
MachineExemplar* me = NULL; | |
// @TODO get rid of the empty token in the parser itself | |
if (parsed->token == tsSexprAtom && parsed->str.sz == 0) | |
parsed = parsed->next; | |
// empty file is valid | |
if (!parsed) | |
return true; | |
tsParsedSexpr_t open = {tsSexprPushList, }; | |
tsParsedSexpr_t close = {tsSexprPopList, }; | |
open.next = parsed; | |
tsParsedSexpr_t* last = parsed; | |
while (last) { | |
if (!last->next) { | |
last->next = &close; | |
break; | |
} | |
last = last->next; | |
} | |
tsParsedSexpr_t* verifyClose = findClosingParen(&open); | |
if (verifyClose != &close) { | |
diagnoseAndThrow(verifyClose, "unmatched open paren"); | |
} | |
tsParsedSexpr_t* curr = compileMachine(&open, &l, mtRoot, &me); | |
if (curr != verifyClose) { | |
diagnoseAndThrow(curr, "found tokens beyond end of script"); | |
} | |
if (dump_exemplar) { | |
dumpExemplar(me); | |
} | |
return 0; | |
} | |
int main() { | |
printf("test empty program\n"); | |
testLandru(""); | |
printf("test empty list\n"); | |
testLandru("()"); | |
printf("test print\n"); | |
testLandru("(io.print \"(\")"); | |
testLandru("(io.print \")\")"); | |
testLandru("(io.print \"()\")"); | |
testLandru("(io.print \"(\") (io.print \")\")"); | |
printf("test require parsing\n"); | |
testLandru("(require time) (require io)"); | |
testLandru("(goto foo) (goto bar)"); | |
printf("test machine\n"); | |
testLandru("(require test) (machine main (state ping ()) (state pong())) (launch main)"); | |
printf("test ping pong\n"); | |
testLandru(pingpongSrc2); | |
testLandru(pingpongSrc, true); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment