To execute our compiled code we need a virtual machine as well. The big deal now is that the interpreter and compiler have to run inside the virtual machine. So we have to compile our first word (I name it shell) by hand.
ok> : *2 2 * ; ok> 4 *2 8 ok> drop ok> : say "so, say what?" type cr ; ok> say so, say what? ok>
1.1. read next word, finish if imput is empty 1.2. if compile mode => [show at 2.1 compiling] 1.3. if word is a string, push string literal 1.4. else if word is in dictionary, execute word 1.5. else if word is a number, push number on data stack 1.6. throw an error, unknown word 2.1. if word is a string, compile a string literal 2.2. else if word is a macro, execute word 2.3. else if word is in dictionary, compile it 2.4. else if word is a number, compile a number literal 2.5. throw an error, unknwon word continue at 1.1.
lets start with the vm. As you can see, the vm() is an endless loop
and does nothing but executing primitives one by one. You might recognize
the global variable current_xt which is needed if an execution token
hast to reference to itself (see f_docol()). Thus, all the magic relies in
the execution tokens.
static void vm(void) { for(;;) { current_xt=*ip++; current_xt->prim(); } }Now lets see the interpreter / compiler.
code is the current compilation pointer into the code_base segment. Execution tokens (xt_t) and literals get stored there. This is the area for the compiled code.
static void interpreting(char *w) { if(is_compile_mode) return compiling(w); if(*w=='"') { // +course02: string handling // ... code discarded, it is the same as in course02.c } static void compiling(char *w) { // course03 if(*w=='"') { // +course02: string handling literal((cell_t)strdup(w+1)); // compile a literal } else if((current_xt=find(macros, w))) { // if word is a macro current_xt->prim(); // execute it immediatly } else if((current_xt=find(dictionary, w))) { // if word is regular *code++=current_xt; // dictionary, compile it } else { // not found, may be a number char *end; int number=strtol(w, &end, 0); if(*end) terminate("word not found"); else literal(number); // compile a number literal } } static void f_lit(void) { sp_push((cell_t)*ip++); } static void literal(cell_t value) { *code++=xt_lit; // call f_lit when executed *code++=(xt_t*)value; }
And now see the functions for : (define a new word, switch to compile mode) and ; (end of definition, switch back to interpret mode)
typedef struct xt_t { // Execution Token struct xt_t *next; char *name; void (*prim)(void); struct xt_t **data; // address into high level code } xt_t; static xt_t *add_word(char *name, void (*prim)(void)) { xt_t *xt=calloc(1, sizeof(xt_t)); xt->next=*definitions; *definitions=xt; xt->name=strdup(name); xt->prim=prim; xt->data=code; // current high level code pointer, compilation target return xt; } static void register_primitives(void) { ... add_word(":", f_colon); // course03, define new word, enter compile mode definitions=¯os; // select macro dictionary add_word(";", f_semis); // course03, end of new word, leave compile mode } static void f_docol(void) { // course03, VM: enter function (word)new rp_push(ip); // at runtime push current ip on return stack ip=current_xt->data; // and continue at the high level code /* data will be set in add_word() and represent the current dictionary pointer */ } static void f_colon(void) { // course03, define a new word char *w=word(); // read next word which becomes the word name add_word(strdup(w), f_docol); is_compile_mode=1; // switch to compile mode } static void f_semis(void) { // course03, macro, end of definition *code++=xt_leave; // compile return from subroutine is_compile_mode=0; // switch back to interpret mode }
To get our vm up and running we have to add the shell manually
int main() { register_primitives(); /* we compile interpreting by hand */ add_word("shell",f_docol);// define a new high level word xt_t **begin=code; // save current code pointer for loop back *code++=xt_word; // get the next word on data stack *code++=xt_dup; *code++=xt_0branch; // jump to end if top of stack is null xt_t **here=code++; // forward jump reference *code++=xt_interpreting; // interpret/compile word on top of stack *code++=xt_branch; // loop back to begin of this word *code++=(void*)begin; // Loop back address *here=(void*)code; // resolve reference *code++=xt_drop; *code++=xt_bye; // leave VM ip=begin; // set instruction pointer vm(); // and run the vm return 0; }
And now lets see how to implement conditionals course04 ⇒