├── .editorconfig ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── bvalue.c ├── bvalue.h ├── collect_garbage.c ├── compile.c ├── compile.js ├── dict.c ├── dict.h ├── examples ├── 99_bottles_of_beer.js └── y.js ├── main.c ├── object.c ├── object.h ├── opcode.def ├── toy.h ├── translate_to_c.node.js ├── util.c ├── util.h ├── util.node.js ├── value.c ├── value.h ├── vm.c └── vm.h /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | charset = utf-8 5 | 6 | [*.{c,h,js}] 7 | indent_style = space 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /compiler_code.c 2 | /toy 3 | *.o 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2017 Antoine Motet 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CC=cc 3 | CFLAGS=-W -Wall -Wextra 4 | OBJECTS=bvalue.o compile.o compiler_code.o dict.o collect_garbage.o main.o \ 5 | object.o util.o value.o vm.o 6 | 7 | all: release 8 | 9 | release: CFLAGS+=-Os 10 | release: LDFLAGS+=-s 11 | release: toy 12 | 13 | debug: CFLAGS+=-g 14 | debug: toy 15 | 16 | toy: $(OBJECTS) 17 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ 18 | 19 | compiler_code.c: *.js 20 | node translate_to_c.node.js > $@ 21 | 22 | clean: 23 | rm -rf $(OBJECTS) compiler_code.c toy 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # toy 2 | 3 | This is a tiny interpreter (with a bytecode compiler and a stack-based 4 | virtual machine) for a lilliputian subset of JavaScript, written in C 5 | and... JavaScript itself. It fits in less than 2500 lines of code. 6 | 7 | The parser and the bytecode generator (in `compile.js`) are written in 8 | Toy-compatible JavaScript. It was just too painful to write those in 9 | C. Since you can run them with Node.js or Toy itself, they 10 | self-compile (into `compiler_code.c`) and are magically bundled inside 11 | the `toy` executable. 12 | 13 | ## Interesting features 14 | 15 | - Closures 16 | - Compiles itself (see above) 17 | - Little mark-and-sweep garbage collector 18 | 19 | ## Unsupported JavaScript features 20 | 21 | - ES201{5,6,7} 22 | - Functions with many arguments (use lists, dictionnaries or 23 | curryfication instead) 24 | - Prototypes 25 | - Exceptions 26 | - `undefined` (seriously, who needs `undefined`? `null` is sufficient) 27 | - Optional semicolons 28 | - Booleans (use 0 and 1, or anything else truthy or falsy) 29 | - `for`, `switch`, `do` 30 | - `else` (just write `if` statements. Thousands of `if` statements.) 31 | - Regexps 32 | 33 | ## Compile and run 34 | 35 | You need Node.js, a C compiler and GNU make. Just run `make` and try 36 | the examples. 37 | 38 | ## Various observations 39 | 40 | First of all, because I hate naming things _à la_ JavaScript: 41 | 42 | - an _object_, a _hash map_ or a _hash table_ is _dictionnary_ 43 | - an _array_ is a _list_ 44 | 45 | For the sake of simplicity, the whole thing is really slow. 46 | 47 | The dictionnary implementation is a shame. 48 | 49 | Lists are implemented with those dictionnaries. This is really 50 | straightforward since we need dictionnaries and JavaScript lists 51 | behaves mostly the same. It’s obviously slow. 52 | 53 | Since the garbage collector does not visit the stack, each object has 54 | a reference counter which prevents it from being collected if that 55 | counter is nonzero. Moreover, the GC must not run at any time, but 56 | only between two instructions (see the call to 57 | `request_garbage_collection();` in `vm.c`). 58 | 59 | Variables are compiled as-is. The VM interprets scoping rules and 60 | catches variable definition errors at run-time. The compiler is really 61 | straightforward. 62 | 63 | I just hope you are not crazy enough to use this hack in production. 64 | 65 | ## Why? 66 | 67 | It was fun. Enjoy :wink: 68 | -------------------------------------------------------------------------------- /bvalue.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | #include "bvalue.h" 3 | 4 | static value_t bvalue_to_v(bvalue_t b) { 5 | switch (b.type) { 6 | case bvalue_type_string: return v_string(b.string); 7 | case bvalue_type_number: return v_number(b.number); 8 | } 9 | abort(); 10 | } 11 | 12 | void bvalue_array_to_v(value_t *v, const bvalue_t *b, size_t length) { 13 | for (size_t i = 0; i < length; i++) { 14 | v[i] = bvalue_to_v(b[i]); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bvalue.h: -------------------------------------------------------------------------------- 1 | #ifndef BUILTIN_H 2 | #define BUILTIN_H 3 | 4 | #include "value.h" 5 | 6 | typedef struct bvalue bvalue_t; 7 | 8 | enum bvalue_type { 9 | bvalue_type_string, 10 | bvalue_type_number, 11 | }; 12 | 13 | struct bvalue { 14 | enum bvalue_type type; 15 | union { 16 | const char *string; 17 | double number; 18 | }; 19 | }; 20 | 21 | #define bv_nbr(n) \ 22 | { \ 23 | .type = bvalue_type_number, \ 24 | .number = (n), \ 25 | } 26 | 27 | #define bv_str(s) \ 28 | { \ 29 | .type = bvalue_type_string, \ 30 | .string = (s), \ 31 | } 32 | 33 | void bvalue_array_to_v(value_t *v, const bvalue_t *b, size_t length); 34 | 35 | #endif /* BUILTIN_H */ 36 | -------------------------------------------------------------------------------- /collect_garbage.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | 3 | static void mark_object(object_t *object); 4 | static void mark_compiled_func(const struct compiled_func *cf); 5 | 6 | static void mark_value(value_t v) { 7 | if (v_is_object(v)) { 8 | mark_object(v.object); 9 | } 10 | } 11 | 12 | static void mark_dict(dict_t *dict) { 13 | for (dict_entry_t *e = *dict; e; e = e->next) { 14 | mark_value(e->value); 15 | } 16 | } 17 | 18 | static void mark_compiled_file(const struct compiled_file *cf) { 19 | for (size_t i = 0; i < cf->func_count; i++) { 20 | mark_compiled_func(cf->funcs + i); 21 | } 22 | } 23 | 24 | static void mark_compiled_func(const struct compiled_func *cf) { 25 | for (size_t i = 0; i < cf->const_count; i++) { 26 | mark_value(cf->consts[i]); 27 | } 28 | } 29 | 30 | static void mark_func(object_t *object) { 31 | mark_value(object->func.parent_scope); 32 | const struct compiled_func *cf = object->func.compiled; 33 | if (cf) { 34 | mark_compiled_func(cf); 35 | mark_compiled_file(cf->file); 36 | } 37 | } 38 | 39 | static void mark_object(object_t *object) { 40 | if (object->marked) { 41 | return; 42 | } 43 | 44 | object->marked = 1; 45 | switch (object->type) { 46 | case object_type_list: 47 | case object_type_dict: 48 | mark_dict(&object->dict); 49 | break; 50 | case object_type_func: { 51 | mark_func(object); 52 | break; 53 | } 54 | default: 55 | break; 56 | } 57 | } 58 | 59 | void collect_garbage(void) { 60 | for (object_t *o = big_linked_list; o; o = o->next) { 61 | o->marked = 0; 62 | } 63 | 64 | for (object_t *o = big_linked_list; o; o = o->next) { 65 | if (o->ref_count) { 66 | mark_object(o); 67 | } 68 | } 69 | 70 | object_t *o = big_linked_list; 71 | while (o) { 72 | object_t *next = o->next; 73 | if (!o->marked) { 74 | free_object_unsafe(o); 75 | } 76 | o = next; 77 | } 78 | } 79 | 80 | static unsigned long object_count_after_last_gc = 0; 81 | 82 | void request_garbage_collection(void) { 83 | if (allocation_count_since_last_gc > object_count_after_last_gc) { 84 | collect_garbage(); 85 | allocation_count_since_last_gc = 0; 86 | object_count_after_last_gc = object_count; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /compile.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | #include 3 | 4 | static value_t v_die(value_t ctx, value_t message) { 5 | (void)ctx; 6 | die(v_to_string(message)); 7 | } 8 | 9 | static value_t v_print(value_t ctx, value_t message) { 10 | (void)ctx; 11 | char *s = v_to_string(message); 12 | puts(s); 13 | free(s); 14 | return v_null; 15 | } 16 | 17 | static value_t v_parse_int(value_t ctx, value_t vstring) { 18 | (void)ctx; 19 | char *endptr; 20 | char *s = v_to_string(vstring); 21 | errno = 0; 22 | long number = strtol(s, &endptr, 10); 23 | if (errno || *endptr) { 24 | free(s); 25 | return v_null; 26 | } 27 | free(s); 28 | return v_number(number); 29 | } 30 | 31 | static value_t v_math_floor(value_t ctx, value_t number) { 32 | (void)ctx; 33 | return v_number(v_to_integer(number)); 34 | } 35 | 36 | static value_t get_math(void) { 37 | value_t m = v_dict(); 38 | v_set(m, v_string("floor"), v_native_func(v_math_floor)); 39 | return m; 40 | } 41 | 42 | static value_t get_global_scope(void) { 43 | value_t scope = v_dict(); 44 | v_set(scope, v_string("die"), v_native_func(v_die)); 45 | v_set(scope, v_string("print"), v_native_func(v_print)); 46 | v_set(scope, v_string("parseInt"), v_native_func(v_parse_int)); 47 | v_set(scope, v_string("Math"), get_math()); 48 | return scope; 49 | } 50 | 51 | static value_t import_nodejs_module(value_t file_func, value_t global_scope) { 52 | value_t exports = v_dict(); 53 | value_t module = v_dict(); 54 | v_set(module, v_string("exports"), exports); 55 | v_set(global_scope, v_string("module"), module); 56 | eval_func(file_func, global_scope); 57 | return v_get(module, v_string("exports")); 58 | } 59 | 60 | static value_t get_builtin_file_func(void) { 61 | compiled_file_t *file = get_builtin_file(); 62 | value_t vglobal = { 63 | .type = value_type_object, 64 | .object = new_compiled_func_object(file->funcs), 65 | }; 66 | return vglobal; 67 | } 68 | 69 | static value_t import_builtin_compiler_module(void) { 70 | return import_nodejs_module(get_builtin_file_func(), get_global_scope()); 71 | } 72 | 73 | static compiled_func_t translate_compiled_func(value_t vfunc) { 74 | value_t vcode = v_get(vfunc, v_string("code")); 75 | value_t vconsts = v_get(vfunc, v_string("consts")); 76 | size_t code_length = v_list_length(vcode); 77 | unsigned char *code = xmalloc(code_length); 78 | size_t const_count = v_list_length(vconsts); 79 | value_t *consts = xmalloc(sizeof(value_t) * const_count); 80 | 81 | for (size_t i = 0; i < const_count; i++) { 82 | consts[i] = v_get(vconsts, v_number(i)); 83 | } 84 | 85 | for (size_t i = 0; i < code_length; i++) { 86 | value_t vinstr = v_get(vcode, v_number(i)); 87 | if (v_is_string(vinstr)) { 88 | int opcode = string_to_opcode(vinstr.object->string); 89 | if (opcode == -1) { 90 | die("unknown opcode"); 91 | } 92 | code[i] = opcode; 93 | } else { 94 | code[i] = v_to_integer(vinstr); 95 | } 96 | } 97 | 98 | return (compiled_func_t){ 99 | .param_name = v_to_string(v_get(vfunc, v_string("paramName"))), 100 | .code = code, 101 | .consts = consts, 102 | .const_count = const_count, 103 | }; 104 | } 105 | 106 | static compiled_file_t *translate_compiled_file(value_t vfuncs) { 107 | compiled_file_t *file = xmalloc(sizeof(compiled_file_t)); 108 | file->func_count = v_list_length(vfuncs); 109 | file->funcs = xmalloc(sizeof(compiled_func_t) * file->func_count); 110 | for (size_t i = 0; i < file->func_count; i++) { 111 | value_t vfunc = v_get(vfuncs, v_number(i)); 112 | compiled_func_t func = translate_compiled_func(vfunc); 113 | func.file = file; 114 | memcpy(file->funcs + i, &func, sizeof(compiled_func_t)); 115 | } 116 | return file; 117 | } 118 | 119 | static void free_compiled_file(compiled_file_t *file) { 120 | for (size_t i = 0; i < file->func_count; i++) { 121 | compiled_func_t *func = file->funcs + i; 122 | free(func->code); 123 | free(func->consts); 124 | free(func->param_name); 125 | } 126 | free(file->funcs); 127 | free(file); 128 | } 129 | 130 | value_t eval_source(const char *source) { 131 | value_t compile_func = import_builtin_compiler_module(); 132 | if (!v_is_func(compile_func)) { 133 | die("the builtin compiler module must export a function"); 134 | } 135 | 136 | value_t compiled_funcs = call_func(compile_func, v_string(source)); 137 | compiled_file_t *file = translate_compiled_file(compiled_funcs); 138 | value_t func = { 139 | .type = value_type_object, 140 | .object = new_compiled_func_object(file->funcs), 141 | }; 142 | value_t result = eval_func(func, get_global_scope()); 143 | free_compiled_file(file); 144 | collect_garbage(); 145 | return result; 146 | } 147 | -------------------------------------------------------------------------------- /compile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | ///////////////////////////////////////////////////////// PARSING 4 | 5 | 6 | 7 | var charIsInRange = function (arg) { 8 | var lower = arg[0]; 9 | var c = arg[1]; // falsy if eof 10 | var upper = arg[2]; 11 | return c && 12 | lower.charCodeAt(0) <= c.charCodeAt(0) && 13 | c.charCodeAt(0) <= upper.charCodeAt(0); 14 | }; 15 | 16 | var isDigit = function (c) { 17 | return charIsInRange(['0', c, '9']); 18 | }; 19 | var isLowercaseLetter = function (c) { 20 | return charIsInRange(['a', c, 'z']); 21 | }; 22 | var isUppercaseLetter = function (c) { 23 | return charIsInRange(['A', c, 'Z']); 24 | }; 25 | var isLetter = function (c) { 26 | return isLowercaseLetter(c) || isUppercaseLetter(c); 27 | }; 28 | var whitespaces = [' ', '\n', '\r', '\t']; 29 | var isWhitespace = function (c) { 30 | return whitespaces.indexOf(c) !== -1; 31 | }; 32 | 33 | var keywords = [ 34 | 'else', 'false', 'function', 'if', 'in', 'null', 'return', 'true', 35 | 'typeof', 'var', 'while' 36 | ]; 37 | 38 | var getLineNumberAtIndex = function (arg) { 39 | var lines = 1; 40 | var i = 0; 41 | while (i < arg.source.length && i < arg.index) { 42 | if (arg.source[i] === '\n') { 43 | lines = lines + 1; 44 | } 45 | i = i + 1; 46 | } 47 | return lines; 48 | }; 49 | 50 | // Returns a list of statements (they are AST nodes). 51 | var parse = function (source) { 52 | var index = 0; // Don't set directly. Use setIndex(). 53 | var farthestIndex = 0; // Only used for error management. 54 | 55 | // Most of the following parsing functions return a falsy value on 56 | // failure, or the read substring on success. 57 | 58 | var setIndex = function (newIndex) { 59 | index = newIndex; 60 | if (index > farthestIndex) { 61 | farthestIndex = index; 62 | } 63 | }; 64 | 65 | // Tries to read the given string. 66 | var read = function (expected) { 67 | // TODO: String#slice() is slow and performance matters here. 68 | if (source.slice(index).indexOf(expected) === 0) { 69 | setIndex(index + expected.length); 70 | return expected; 71 | } 72 | }; 73 | 74 | // Tries to read one character that matches the given predicate. 75 | var readCharIf = function (predicate) { 76 | var c = source[index]; 77 | if (predicate(c)) { 78 | setIndex(index + 1); 79 | return c; 80 | } 81 | }; 82 | 83 | // Pretty obvious. See how it is used below. 84 | var backtrack = function (func) { 85 | return function (arg) { 86 | var begin = index; 87 | var result = func(arg); 88 | if (result) { 89 | return result; 90 | } 91 | index = begin; 92 | }; 93 | }; 94 | 95 | var createToken = function (token) { 96 | token.index = index; 97 | return token; 98 | }; 99 | 100 | var digit = function () { 101 | return readCharIf(isDigit); 102 | }; 103 | var letter = function () { 104 | return readCharIf(isLetter); 105 | }; 106 | var whitespace = function () { 107 | // That's bit hackish for performance reasons. 108 | while (1) { 109 | if (source[index] === '/' && source[index + 1] === '/') { 110 | setIndex(index + 2); 111 | while (readCharIf(function (c) {return c !== '\n';})) {} 112 | } 113 | if (!readCharIf(isWhitespace)) { 114 | return 1; 115 | } 116 | } 117 | }; 118 | 119 | var stringEscapeTable = { 120 | n: '\n', t: '\t', r: '\r', '\'': '\'', '\\': '\\', 121 | }; 122 | var string = backtrack(function () { 123 | whitespace(); 124 | var quote = read('\''); 125 | if (!quote) { 126 | return; 127 | } 128 | 129 | var value = ''; 130 | while (1) { 131 | var c = source[index]; 132 | setIndex(index + 1); 133 | if (!c) { 134 | return; 135 | } 136 | if (c === '\'') { 137 | return createToken({type: 'string', value}); 138 | } 139 | if (c === '\\') { 140 | var d = source[index]; 141 | setIndex(index + 1); 142 | if (!(d in stringEscapeTable)) { 143 | return; 144 | } 145 | value = value + stringEscapeTable[d]; 146 | } 147 | if (c !== '\\') { 148 | value = value + c; 149 | } 150 | } 151 | }); 152 | 153 | var identifierish = backtrack(function () { 154 | whitespace(); 155 | var string = letter() || read('_'); 156 | if (!string) { 157 | return; 158 | } 159 | while (1) { 160 | var c = digit() || letter() || read('_'); 161 | if (!c) { 162 | return string; 163 | } 164 | string = string + c; 165 | } 166 | }); 167 | 168 | var identifier = backtrack(function () { 169 | var string = identifierish(); 170 | if (string && keywords.indexOf(string) === -1) { 171 | return createToken({type: 'identifier', string}); 172 | } 173 | }); 174 | 175 | var number = backtrack(function () { 176 | whitespace(); 177 | var string = digit(); 178 | if (!string) { 179 | return; 180 | } 181 | while (1) { 182 | var c = digit(); 183 | if (!c) { 184 | return createToken({ 185 | type: 'number', 186 | value: parseInt(string) 187 | }); 188 | } 189 | string = string + c; 190 | } 191 | }); 192 | 193 | var keyword = backtrack(function () { 194 | var string = identifierish(); 195 | if (string && keywords.indexOf(string) !== -1) { 196 | return string; 197 | } 198 | }); 199 | 200 | var thisKeyword = backtrack(function (expected) { 201 | var string = keyword(); 202 | if (string === expected) { 203 | return string; 204 | } 205 | }); 206 | 207 | var nullExpr = function () { 208 | return thisKeyword('null') && {type: 'null'}; 209 | }; 210 | 211 | var literal = function () { 212 | return nullExpr() || identifier() || number() || string(); 213 | }; 214 | 215 | // Used to parse parentheses, braces or square brackets. 216 | var wrapped = backtrack(function (arg) { 217 | whitespace(); 218 | if (!read(arg.left)) { 219 | return; 220 | } 221 | var e = arg.body(); 222 | whitespace(); 223 | if (read(arg.right)) { 224 | return e; 225 | } 226 | }); 227 | 228 | // Used to parse lists delimited by commas or semicolons. 229 | var sequence = backtrack(function (arg) { 230 | var items = []; 231 | while (1) { 232 | if (items.length && !arg.sep()) { 233 | return items; 234 | } 235 | var item = arg.item(); 236 | if (!item) { 237 | return items; 238 | } 239 | items.push(item); 240 | } 241 | }); 242 | 243 | var paren = function () { 244 | var body = function () { 245 | return expr() || {type: 'null'}; 246 | }; 247 | return wrapped({left: '(', body, right: ')'}); 248 | }; 249 | 250 | var statements = function () { 251 | return sequence({item: statement, sep: whitespace}); 252 | }; 253 | 254 | var block = function () { 255 | return wrapped({left: '{', body: statements, right: '}'}); 256 | }; 257 | 258 | var commaSeparated = function (item) { 259 | return sequence({ 260 | item, 261 | sep: function () {return read(',');} 262 | }); 263 | }; 264 | 265 | var list = function () { 266 | var children = wrapped({ 267 | left: '[', 268 | body: function () {return commaSeparated(expr);}, 269 | right: ']' 270 | }); 271 | if (children) { 272 | return {type: 'list', children}; 273 | } 274 | }; 275 | 276 | var dictEntry = backtrack(function () { 277 | var left = identifier() || string(); 278 | if (!left) { 279 | return; 280 | } 281 | left.type = 'string'; 282 | left.value = left.string || left.value; 283 | whitespace(); 284 | if (!read(':')) { 285 | var right = { 286 | type: 'identifier', 287 | string: left.value 288 | }; 289 | return {type: 'dictEntry', left, right}; 290 | } 291 | var right = expr(); 292 | if (right) { 293 | return {type: 'dictEntry', left, right}; 294 | } 295 | }); 296 | 297 | var dict = function () { 298 | var children = wrapped({ 299 | left: '{', 300 | body: function () {return commaSeparated(dictEntry);}, 301 | right: '}' 302 | }); 303 | if (children) { 304 | return {type: 'dict', children}; 305 | } 306 | }; 307 | 308 | var atom = function () { 309 | return literal() || list() || dict() || paren(); 310 | }; 311 | 312 | var postfixDot = backtrack(function () { 313 | whitespace(); 314 | return read('.') && identifier(); 315 | }); 316 | 317 | var postfixRight = function (left) { 318 | var right = paren(); 319 | if (right) { 320 | return {type: 'binaryOp', left, op: 'call', right}; 321 | } 322 | if (right = wrapped({left: '[', body: expr, right: ']'})) { 323 | return {type: 'subscript', left, right}; 324 | } 325 | if (right = postfixDot()) { 326 | return { 327 | type: 'subscript', 328 | left, 329 | right: {type: 'string', value: right.string}, 330 | }; 331 | } 332 | return; 333 | }; 334 | 335 | var postfix = function () { 336 | var left = atom(); 337 | while (left) { 338 | var n = postfixRight(left); 339 | if (!n) { 340 | return left; 341 | } 342 | left = n; 343 | } 344 | return left; 345 | }; 346 | 347 | var whileStatement = backtrack(function () { 348 | if (!thisKeyword('while')) { 349 | return; 350 | } 351 | var cond = paren(); 352 | var b = block(); 353 | if (cond && b) { 354 | return {type: 'while', cond, children: b}; 355 | } 356 | }); 357 | 358 | var ifStatement = backtrack(function () { 359 | if (!thisKeyword('if')) { 360 | return; 361 | } 362 | var cond = paren(); 363 | var b = block(); 364 | if (cond && b) { 365 | return {type: 'if', cond, children: b}; 366 | } 367 | }); 368 | 369 | var varExpr = backtrack(function () { 370 | if (!thisKeyword('var')) { 371 | return; 372 | } 373 | var name = identifier(); 374 | whitespace(); 375 | var eq = read('='); 376 | var value = expr(); 377 | if (name && eq && value) { 378 | return {type: 'var', name, value}; 379 | } 380 | }); 381 | 382 | var returnExpr = backtrack(function () { 383 | if (!thisKeyword('return')) { 384 | return; 385 | } 386 | return { 387 | type: 'return', 388 | value: expr() || {type: 'null'} 389 | }; 390 | }); 391 | 392 | var func = backtrack(function () { 393 | if (!thisKeyword('function')) { 394 | return; 395 | } 396 | var optionalId = function () { 397 | return identifier() || {type: 'null'}; 398 | }; 399 | var param = wrapped({left: '(', body: optionalId, right: ')'}); 400 | var b = block(); 401 | if (param && b) { 402 | return {type: 'function', param, children: b}; 403 | } 404 | }); 405 | 406 | var binaryOp = function (arg) { 407 | return backtrack(function () { 408 | var left = arg.next(); 409 | if (!left) { 410 | return; 411 | } 412 | while (1) { 413 | var op = arg.parseOp(); 414 | if (!op) { 415 | return left; 416 | } 417 | var right = arg.next(); 418 | left = {type: 'binaryOp', left, op, right}; 419 | } 420 | return left; 421 | }); 422 | }; 423 | 424 | var binaryOp2 = function (opStrings) { 425 | return function (next) { 426 | var parseOp = function () { 427 | var i = 0; 428 | whitespace(); 429 | while (i < opStrings.length) { 430 | var op = read(opStrings[i]); 431 | if (op) { 432 | return op; 433 | } 434 | i = i + 1; 435 | } 436 | }; 437 | 438 | return binaryOp({next, parseOp}); 439 | }; 440 | }; 441 | 442 | var unary = backtrack(function () { 443 | whitespace(); 444 | var op = read('-') || read('!') || thisKeyword('typeof'); 445 | var right = postfix(); 446 | if (op && right) { 447 | return {type: 'unaryOp', op, right}; 448 | } 449 | return right; 450 | }); 451 | 452 | var multiplication = binaryOp2(['*', '/', '%'])(unary); 453 | var addition = binaryOp2(['+', '-'])(multiplication); 454 | var relation = binaryOp2(['<=', '>=', '<', '>', 'in'])(addition); 455 | var equality = binaryOp2(['===', '!=='])(relation); 456 | var andExpr = binaryOp2(['&&'])(equality); 457 | var orExpr = binaryOp2(['||'])(andExpr); 458 | 459 | var assignment_ = backtrack(function () { 460 | var left = postfix(); 461 | whitespace(); 462 | if (!left || !read('=')) { 463 | return; 464 | } 465 | var right = assignment(); 466 | if (right) { 467 | return {type: 'assignment', left, right}; 468 | } 469 | }); 470 | var assignment = function () { 471 | return assignment_() || orExpr() || func(); 472 | }; 473 | 474 | var expr = function () { 475 | return func() || varExpr() || returnExpr() || assignment(); 476 | }; 477 | 478 | var exprStatement = backtrack(function () { 479 | var e = expr(); 480 | whitespace(); 481 | if (read(';')) { 482 | return e; 483 | } 484 | }); 485 | 486 | var statement = function () { 487 | return whileStatement() || ifStatement() || exprStatement(); 488 | }; 489 | 490 | var result = statements(); 491 | whitespace(); 492 | if (!result || index < source.length) { 493 | if (index > farthestIndex) { 494 | farthestIndex = index; 495 | } 496 | var line = getLineNumberAtIndex({source, index: farthestIndex}); 497 | die('syntax error (line ' + line + ')'); 498 | } 499 | return result; 500 | }; 501 | 502 | 503 | 504 | //////////////////////////////////////////////////// BYTECODE GENERATION 505 | 506 | 507 | 508 | var opSignsToNames = { 509 | '+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'mod', 510 | '===': 'eq', '!==': 'neq', 511 | '<': 'lt', '>': 'gt', '<=': 'lte', '>=': 'gte', 512 | 'in': 'in', call: 'call', 513 | }; 514 | 515 | var unarySignsToNames = { 516 | '-': 'unary_minus', 517 | '!': 'not', 518 | 'typeof': 'typeof' 519 | }; 520 | 521 | var compileFunctionBody = function (statements) { 522 | var code = []; 523 | var consts = []; 524 | 525 | var toUint16 = function (n) { 526 | return [Math.floor(n / 256), n % 256]; // big endian 527 | }; 528 | 529 | var genUint16 = function (n) { 530 | code = code.concat(toUint16(n)); 531 | }; 532 | 533 | var setUint16At = function (arg) { 534 | var number = arg[0]; 535 | var index = arg[1]; 536 | code[index] = toUint16(number)[0]; 537 | code[index + 1] = toUint16(number)[1]; 538 | }; 539 | 540 | var genLoadConst = function (c) { 541 | code.push('load_const'); 542 | genUint16(consts.length); 543 | consts.push(c); 544 | }; 545 | 546 | var compileAssign = function (expr) { 547 | if (expr.left.type === 'identifier') { 548 | compileExpr(expr.right); 549 | code.push('dup'); 550 | genLoadConst(expr.left.string); 551 | code.push('rot'); 552 | code.push('store_var'); 553 | return; 554 | } 555 | if (expr.left.type === 'subscript') { 556 | compileExpr(expr.right); 557 | code.push('dup'); 558 | compileExpr(expr.left.left); 559 | compileExpr(expr.left.right); 560 | code.push('set'); 561 | return; 562 | } 563 | die('unknown lvalue type'); 564 | }; 565 | 566 | var compileAndOr = function (expr) { 567 | // Shortcuts right operand. We need a `goto`. A plain old binary 568 | // operator is not suitable. 569 | compileExpr(expr.left); 570 | code.push('dup'); 571 | if (expr.op === '&&') { 572 | code.push('not'); 573 | } 574 | code.push('goto_if'); 575 | var breakLabel = code.length; 576 | genUint16(0); 577 | code.push('pop'); 578 | compileExpr(expr.right); 579 | setUint16At([code.length, breakLabel]); 580 | }; 581 | 582 | var compileExpr = function (expr) { 583 | if (expr.type === 'string' || expr.type === 'number') { 584 | return genLoadConst(expr.value); 585 | } 586 | 587 | if (expr.type === 'null') { 588 | return code.push('load_null'); 589 | } 590 | 591 | if (expr.type === 'assignment') { 592 | return compileAssign(expr); 593 | } 594 | 595 | if (expr.type === 'binaryOp') { 596 | if (expr.op === '&&' || expr.op === '||') { 597 | return compileAndOr(expr); 598 | } 599 | 600 | compileExpr(expr.left); 601 | compileExpr(expr.right); 602 | if (!opSignsToNames[expr.op]) { 603 | die('unknown op ' + expr.op); 604 | } 605 | code.push(opSignsToNames[expr.op]); 606 | return; 607 | } 608 | 609 | if (expr.type === 'unaryOp') { 610 | compileExpr(expr.right); 611 | code.push(unarySignsToNames[expr.op]); 612 | return; 613 | } 614 | 615 | if (expr.type === 'identifier') { 616 | genLoadConst(expr.string); 617 | code.push('load_var'); 618 | return; 619 | } 620 | 621 | if (expr.type === 'function') { 622 | code.push('load_func'); 623 | genUint16(expr._id); 624 | return; 625 | } 626 | 627 | if (expr.type === 'subscript') { 628 | compileExpr(expr.left); 629 | compileExpr(expr.right); 630 | code.push('get'); 631 | return; 632 | } 633 | 634 | if (expr.type === 'list') { 635 | code.push('load_empty_list'); 636 | var i = 0; 637 | while (i < expr.children.length) { 638 | compileExpr(expr.children[i]); 639 | code.push('list_push'); 640 | i = i + 1; 641 | } 642 | return; 643 | } 644 | 645 | if (expr.type === 'dict') { 646 | code.push('load_empty_dict'); 647 | var i = 0; 648 | while (i < expr.children.length) { 649 | var entry = expr.children[i]; 650 | compileExpr(entry.left); 651 | compileExpr(entry.right); 652 | code.push('dict_push'); 653 | i = i + 1; 654 | } 655 | return; 656 | } 657 | die('unknown expr type'); 658 | }; 659 | 660 | var compileStatement = function (expr) { 661 | if (expr.type === 'var') { 662 | genLoadConst(expr.name.string); 663 | code.push('dup'); 664 | code.push('decl_var'); 665 | compileExpr(expr.value); 666 | code.push('store_var'); 667 | return; 668 | } 669 | 670 | if (expr.type === 'return') { 671 | compileExpr(expr.value); 672 | code.push('return'); 673 | return; 674 | } 675 | 676 | if (expr.type === 'while') { 677 | // This is what people call "spaghetty code". 678 | var beginLabel = code.length; 679 | compileExpr(expr.cond); 680 | code.push('not'); 681 | code.push('goto_if'); 682 | var breakLabel = code.length; 683 | genUint16(0); 684 | compileStatements(expr.children); 685 | code.push('goto'); 686 | genUint16(beginLabel); 687 | setUint16At([code.length, breakLabel]); 688 | return; 689 | } 690 | 691 | if (expr.type === 'if') { 692 | compileExpr(expr.cond); 693 | code.push('not'); 694 | code.push('goto_if'); 695 | var breakLabel = code.length; 696 | genUint16(0); 697 | compileStatements(expr.children); 698 | setUint16At([code.length, breakLabel]); 699 | return; 700 | } 701 | 702 | compileExpr(expr); 703 | code.push('pop'); // In statements like `print("hello");`, we 704 | // don't care about the value returned by `print`. 705 | }; 706 | 707 | var compileStatements = function (statements) { 708 | var i = 0; 709 | while (i < statements.length) { 710 | compileStatement(statements[i]); 711 | i = i + 1; 712 | } 713 | }; 714 | 715 | compileStatements(statements); 716 | code.push('load_null'); 717 | code.push('return'); 718 | 719 | return {consts, code}; 720 | }; 721 | 722 | // Returns a list of the functions present in the given AST node. 723 | // Performs a depth-first search. 724 | var getFunctions = function (node) { 725 | if (node.type === 'var') { 726 | return getFunctions(node.value); 727 | } 728 | 729 | if (node.type === 'function') { 730 | return [node].concat(getFunctionsInList(node.children)); 731 | } 732 | 733 | if (node.type === 'while' || node.type === 'if') { 734 | return getFunctionsInList(node.children) 735 | .concat(getFunctions(node.cond)); 736 | } 737 | 738 | if (['binaryOp', 'assignment', 'subscript'].indexOf(node.type) !== -1) { 739 | return getFunctions(node.left) 740 | .concat(getFunctions(node.right)); 741 | } 742 | 743 | if (node.type === 'unaryOp') { 744 | return getFunctions(node.right); 745 | } 746 | 747 | if (node.type === 'return') { 748 | return getFunctions(node.value); 749 | } 750 | 751 | if (node.type === 'list' || node.type === 'dict') { 752 | return getFunctionsInList(node.children); 753 | } 754 | 755 | if (node.type === 'dictEntry') { 756 | return getFunctions(node.right); 757 | } 758 | 759 | return []; 760 | }; 761 | 762 | // Exactly like `getFunctions` but works on a list of AST nodes. 763 | var getFunctionsInList = function (list) { 764 | var funcs = []; 765 | var i = 0; 766 | while (i < list.length) { 767 | funcs = funcs.concat(getFunctions(list[i])); 768 | i = i + 1; 769 | } 770 | return funcs; 771 | }; 772 | 773 | // Returns a list of compiled functions. 774 | var codegen = function (statements) { 775 | var root = { 776 | type: 'function', 777 | children: statements, 778 | param: {type: 'null'} 779 | }; 780 | 781 | // Assign an unique `_id` property to each function. The first function 782 | // must be the entrypoint. 783 | var functions = getFunctions(root); 784 | var i = 0; 785 | while (i < functions.length) { 786 | functions[i]._id = i; 787 | i = i + 1; 788 | } 789 | 790 | var compiledFuncs = []; 791 | i = 0; 792 | while (i < functions.length) { 793 | var func = functions[i]; 794 | var compiled = compileFunctionBody(func.children); 795 | if (func.param.type !== 'null') { 796 | compiled.paramName = func.param.string; 797 | } 798 | compiledFuncs.push(compiled); 799 | i = i + 1; 800 | } 801 | return compiledFuncs; 802 | }; 803 | 804 | 805 | module.exports = function (source) { 806 | return codegen(parse(source)); 807 | }; 808 | -------------------------------------------------------------------------------- /dict.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | 3 | // Here is how *not* to implement a dictionnary. 4 | // (Do you want to know a secret? This is also used for the lists.) 5 | 6 | static dict_entry_t *dict_find_entry(dict_t *dict, const char *key) { 7 | for (dict_entry_t *e = *dict; e; e = e->next) { 8 | if (strcmp(e->key, key) == 0) { 9 | return e; 10 | } 11 | } 12 | return 0; 13 | } 14 | 15 | value_t dict_get(const dict_t *dict, const char *key) { 16 | const dict_entry_t *entry = dict_find_entry((dict_t *)dict, key); 17 | return entry ? entry->value : v_null; 18 | } 19 | 20 | value_t dict_getv(const dict_t *dict, value_t key) { 21 | char *skey = v_to_string(key); 22 | value_t v = dict_get(dict, skey); 23 | free(skey); 24 | return v; 25 | } 26 | 27 | int dict_has(const dict_t *dict, const char *key) { 28 | return !!dict_find_entry((dict_t *)dict, key); 29 | } 30 | 31 | int dict_hasv(const dict_t *dict, value_t key) { 32 | char *skey = v_to_string(key); 33 | int ok = dict_has(dict, skey); 34 | free(skey); 35 | return ok; 36 | } 37 | 38 | void dict_set(dict_t *dict, const char *key, value_t v) { 39 | dict_entry_t *entry = dict_find_entry(dict, key); 40 | if (entry) { 41 | entry->value = v; 42 | return; 43 | } 44 | entry = xmalloc(sizeof(dict_entry_t)); 45 | entry->key = xstrdup(key); 46 | entry->value = v; 47 | entry->next = *dict; 48 | entry->prev = 0; 49 | if (*dict) { 50 | (*dict)->prev = entry; 51 | } 52 | *dict = entry; 53 | } 54 | 55 | void dict_setv(dict_t *dict, value_t key, value_t v) { 56 | char *skey = v_to_string(key); 57 | dict_set(dict, skey, v); 58 | free(skey); 59 | } 60 | 61 | void dict_delete_all(dict_t *dict) { 62 | dict_entry_t *e = *dict; 63 | while (e) { 64 | dict_entry_t *next = e->next; 65 | free(e->key); 66 | free(e); 67 | e = next; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /dict.h: -------------------------------------------------------------------------------- 1 | #ifndef DICT_H 2 | #define DICT_H 3 | 4 | #include "value.h" 5 | 6 | typedef struct dict_entry dict_entry_t; 7 | typedef dict_entry_t *dict_t; // A pointer to the first entry 8 | 9 | struct dict_entry { 10 | char *key; 11 | value_t value; 12 | dict_entry_t *prev, *next; 13 | }; 14 | 15 | value_t dict_get(const dict_t *dict, const char *key); 16 | value_t dict_getv(const dict_t *dict, value_t key); 17 | int dict_has(const dict_t *dict, const char *key); 18 | int dict_hasv(const dict_t *dict, value_t key); 19 | void dict_set(dict_t *dict, const char *key, value_t v); 20 | void dict_setv(dict_t *dict, value_t key, value_t v); 21 | void dict_delete_all(dict_t *dict); 22 | 23 | #endif /* DICT_H */ 24 | -------------------------------------------------------------------------------- /examples/99_bottles_of_beer.js: -------------------------------------------------------------------------------- 1 | 2 | var bottleCountString = function (bottles) { 3 | return function (capitalize) { 4 | if (bottles > 1) { 5 | return bottles + ' bottles'; 6 | } 7 | if (bottles === 1) { 8 | return '1 bottle'; 9 | } 10 | if (capitalize) { 11 | return 'No more bottles'; 12 | } 13 | return 'no more bottles'; 14 | }; 15 | }; 16 | 17 | var drink = function (bottles) { 18 | print(bottleCountString(bottles)(1) + ' of beer on the wall, ' + 19 | bottleCountString(bottles)(0) + ' of beer.'); 20 | 21 | if (!bottles) { 22 | print('We\'ve taken them down and passed them around; now we\'re ' + 23 | 'drunk and passed out!'); 24 | return; 25 | } 26 | 27 | var cs = bottleCountString(bottles - 1)(0); 28 | print('Take one down, pass it around, ' + cs + ' of beer on the wall.'); 29 | drink(bottles - 1); 30 | }; 31 | 32 | drink(99); 33 | -------------------------------------------------------------------------------- /examples/y.js: -------------------------------------------------------------------------------- 1 | 2 | // You are not expected to understand this. 3 | var Y = function (le) { 4 | return (function(f) { 5 | return f(f); 6 | })(function (f) { 7 | return le(function (x) { 8 | return (f(f))(x); 9 | }); 10 | }); 11 | }; 12 | 13 | var fac = function (fac2) { 14 | return function (n) { 15 | if (n <= 1) { 16 | return 1; 17 | } 18 | return n * fac2(n - 1); 19 | }; 20 | }; 21 | 22 | print(Y(fac)(5)); 23 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | 3 | int main(int argc, const char **argv) { 4 | if (argc <= 1) { 5 | die("invoke with a file name"); 6 | } 7 | 8 | FILE *file = fopen(argv[1], "r"); 9 | if (!file) { 10 | die("cannot open the given file"); 11 | } 12 | 13 | size_t max_file_size = 64 * 1000; 14 | char *source = xmalloc(max_file_size); 15 | size_t length = fread(source, 1, max_file_size, file); 16 | source[length] = 0; 17 | eval_source(source); 18 | fclose(file); 19 | free(source); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /object.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | 3 | unsigned long object_count = 0; 4 | unsigned long allocation_count_since_last_gc = 0; 5 | object_t *big_linked_list = 0; 6 | 7 | static object_t *new_object(void) { 8 | object_count++; 9 | allocation_count_since_last_gc++; 10 | object_t *o = xmalloc(sizeof(object_t)); 11 | o->ref_count = 0; 12 | o->prev = 0; 13 | o->next = big_linked_list; 14 | if (big_linked_list) { 15 | big_linked_list->prev = o; 16 | } 17 | big_linked_list = o; 18 | return o; 19 | } 20 | 21 | void free_object_unsafe(object_t *o) { 22 | if (o == big_linked_list) { 23 | big_linked_list = o->next; 24 | } 25 | if (o->prev) { 26 | o->prev->next = o->next; 27 | } 28 | if (o->next) { 29 | o->next->prev = o->prev; 30 | } 31 | 32 | switch (o->type) { 33 | case object_type_list: // fallthrough 34 | case object_type_dict: 35 | dict_delete_all(&o->dict); 36 | break; 37 | case object_type_string: 38 | free(o->string); 39 | break; 40 | case object_type_func: 41 | break; 42 | } 43 | free(o); 44 | object_count--; 45 | } 46 | 47 | object_t *new_string_object(const char *cs) { 48 | object_t *o = new_object(); 49 | o->type = object_type_string; 50 | o->string = xstrdup(cs); 51 | return o; 52 | } 53 | 54 | object_t *new_dict_object(void) { 55 | object_t *o = new_object(); 56 | o->type = object_type_dict; 57 | memset(&o->dict, 0, sizeof(dict_t)); 58 | return o; 59 | } 60 | 61 | object_t *new_list_object(void) { 62 | object_t *o = new_dict_object(); 63 | o->type = object_type_list; 64 | dict_set(&o->dict, "length", v_number(0)); 65 | return o; 66 | } 67 | 68 | static object_t *new_func_object(func_t func) { 69 | object_t *o = new_object(); 70 | o->type = object_type_func; 71 | o->func = func; 72 | return o; 73 | } 74 | 75 | object_t *new_native_func_object(native_func_t native) { 76 | return new_func_object((func_t){ 77 | .compiled = 0, 78 | .native = native, 79 | .parent_scope = v_null, 80 | }); 81 | } 82 | 83 | object_t *new_compiled_func_object(compiled_func_t *compiled) { 84 | return new_func_object((func_t){ 85 | .compiled = compiled, 86 | .native = 0, 87 | .parent_scope = v_null, 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /object.h: -------------------------------------------------------------------------------- 1 | #ifndef OBJECT_H 2 | #define OBJECT_H 3 | 4 | #include "dict.h" 5 | 6 | typedef struct object object_t; 7 | typedef struct value value_t; 8 | typedef struct func func_t; 9 | 10 | typedef value_t (*native_func_t)(value_t parent_scope, value_t arg); 11 | 12 | enum object_type { 13 | object_type_dict, 14 | object_type_list, 15 | object_type_string, 16 | object_type_func, 17 | }; 18 | 19 | struct func { 20 | struct compiled_func *compiled; // null if native 21 | native_func_t native; // null if not native 22 | 23 | // If the function is compiled, the parent_scope must be a dict 24 | // with an optional `` property. 25 | // If the function is native, it is user-defined and can be anything. 26 | value_t parent_scope; 27 | }; 28 | 29 | struct object { 30 | enum object_type type; 31 | object_t *prev, *next; // Garbage collection junk 32 | int marked, ref_count; // More garbage collection junk 33 | union { 34 | dict_t dict; // This is also used for lists. No, I’m not kidding. 35 | char *string; 36 | func_t func; 37 | }; 38 | }; 39 | 40 | void free_object_unsafe(object_t *o); 41 | object_t *new_string_object(const char *cs); 42 | object_t *new_dict_object(void); 43 | object_t *new_list_object(void); 44 | object_t *new_native_func_object(native_func_t func); 45 | object_t *new_compiled_func_object(struct compiled_func *compiled); 46 | 47 | // These global variables are used by the garbage collector. 48 | extern object_t *big_linked_list; // Contains every allocated object. 49 | extern unsigned long object_count; 50 | extern unsigned long allocation_count_since_last_gc; 51 | 52 | #endif /* OBJECT_H */ 53 | -------------------------------------------------------------------------------- /opcode.def: -------------------------------------------------------------------------------- 1 | 2 | // See `vm.c` and the code generation in `compile.js` in order to 3 | // understand what these instructions are doing. 4 | 5 | X(return) // Must be the first one. 6 | 7 | X(add) X(sub) X(mul) X(div) X(mod) 8 | X(and) X(or) X(eq) X(neq) 9 | X(gt) X(lt) X(gte) X(lte) 10 | X(not) X(typeof) X(unary_minus) 11 | 12 | X(set) X(get) X(in) 13 | 14 | X(load_empty_list) X(load_empty_dict) 15 | X(load_null) 16 | X(load_func) 17 | X(load_const) 18 | X(load_var) 19 | X(store_var) 20 | X(decl_var) 21 | 22 | X(goto) X(goto_if) 23 | 24 | X(call) 25 | 26 | X(dup) X(pop) X(rot) 27 | 28 | X(list_push) X(dict_push) 29 | 30 | X(_count) // Must be the last one. 31 | -------------------------------------------------------------------------------- /toy.h: -------------------------------------------------------------------------------- 1 | #ifndef TOY_H 2 | #define TOY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "object.h" 11 | #include "vm.h" 12 | #include "value.h" 13 | #include "util.h" 14 | 15 | value_t eval_source(const char *source); 16 | void collect_garbage(void); 17 | void request_garbage_collection(void); 18 | struct compiled_file *get_builtin_file(void); 19 | 20 | #endif /* TOY_H */ 21 | -------------------------------------------------------------------------------- /translate_to_c.node.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./util.node.js'); 4 | var fs = require('fs'); 5 | 6 | // This is basically simple translation from JSON to C source code in order 7 | // to magically bundle the compiled parser into the `toy` executable. 8 | 9 | var compile = require('./compile'); 10 | 11 | var escapeStringChar = function (c) { 12 | var map = { 13 | '\n': '\\n', 14 | '\r': '\\r', 15 | '\t': '\\t', 16 | '\\': '\\\\', 17 | '"': '\\"' 18 | }; 19 | return map[c] || c; 20 | } 21 | 22 | var escapeString = function (string) { 23 | var e = ''; 24 | var i = 0; 25 | while (i < string.length) { 26 | e = e + escapeStringChar(string[i]); 27 | i = i + 1; 28 | } 29 | return '"' + e + '"'; 30 | }; 31 | 32 | var translateFunction = function (compiled) { 33 | var output = ''; 34 | var emit = function (s) { 35 | output = output + s; 36 | }; 37 | 38 | emit('{\n'); 39 | 40 | if (compiled.paramName) { 41 | emit(' .param_name = "' + compiled.paramName + '",\n'); 42 | } 43 | 44 | emit(' .code = (unsigned char[]){\n'); 45 | var i = 0; 46 | while (i < compiled.code.length) { 47 | var byte = compiled.code[i]; 48 | emit(' /* ' + i + ' */ '); 49 | if (typeof byte === 'string') { 50 | emit('opcode_' + byte); 51 | } 52 | if (typeof byte === 'number') { 53 | emit(byte); 54 | } 55 | emit(',\n'); 56 | i = i + 1; 57 | } 58 | emit(' },\n'); 59 | 60 | var consts = compiled.consts; 61 | 62 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65673 63 | if (consts.length) { 64 | emit(' .consts = (value_t[' + consts.length + ']){},\n'); 65 | emit(' .bconsts = (bvalue_t[]){\n'); 66 | i = 0; 67 | while (i < consts.length) { 68 | var value = consts[i]; 69 | emit(' /* ' + i + ' */ '); 70 | if (typeof value === 'string') { 71 | emit('bv_str(' + escapeString(value) + ')'); 72 | } 73 | if (typeof value === 'number') { 74 | emit('bv_nbr(' + value + ')'); 75 | } 76 | emit(',\n'); 77 | i = i + 1; 78 | } 79 | emit(' },\n'); 80 | } 81 | 82 | emit(' .const_count = ' + consts.length + ',\n'); 83 | 84 | emit(' .file = &file,\n'); 85 | 86 | emit('};\n'); 87 | 88 | return output; 89 | }; 90 | 91 | var translate = function (source) { 92 | var output = ''; 93 | var emit = function (s) {output = output + s;}; 94 | 95 | emit('// AUTOGENERATED FILE\n\n'); 96 | emit('#include "toy.h"\n'); 97 | emit('#include "bvalue.h"\n\n'); 98 | 99 | var funcs = compile(source); 100 | 101 | emit('static compiled_file_t file = {\n'); 102 | emit(' .func_count = ' + funcs.length + ',\n'); 103 | emit(' .funcs = (compiled_func_t[' + funcs.length + ']){},\n'); 104 | emit('};\n\n'); 105 | 106 | var i = 0; 107 | while (i < funcs.length) { 108 | var func = funcs[i]; 109 | emit('static compiled_func_t func' + i + ' = '); 110 | emit(translateFunction(func)); 111 | emit('\n'); 112 | i = i + 1; 113 | } 114 | 115 | emit('compiled_file_t *get_builtin_file(void) {\n'); 116 | 117 | emit(' compiled_func_t funcs[' + funcs.length + '] = {'); 118 | i = 0; 119 | while (i < funcs.length) { 120 | emit('func' + i + ', '); 121 | i = i + 1; 122 | } 123 | emit('};\n\n'); 124 | 125 | emit(' memmove(file.funcs, funcs, sizeof(funcs));\n'); 126 | emit(' for (int i = 0; i < ' + funcs.length + '; i++) {\n'); 127 | emit(' compiled_func_t *func = file.funcs + i;\n'); 128 | emit(' bvalue_array_to_v(func->consts, func->bconsts, func->const_count);\n'); 129 | emit(' }\n'); 130 | emit(' return &file;\n'); 131 | emit('}\n'); 132 | 133 | return output; 134 | }; 135 | 136 | var source = fs.readFileSync('compile.js').toString(); 137 | console.log(translate(source)); 138 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | 3 | #define ASSERT_ENOUGH_MEM(v) \ 4 | if (!v) { \ 5 | die("cannot allocate memory"); \ 6 | } 7 | 8 | void die(const char *error) { 9 | fprintf(stderr, "fatal: %s\n", error); 10 | exit(1); 11 | } 12 | 13 | void *xmalloc(size_t size) { 14 | void *d = malloc(size); 15 | ASSERT_ENOUGH_MEM(d); 16 | return d; 17 | } 18 | 19 | char *xstrdup(const char *s) { 20 | char *r = strdup(s); 21 | ASSERT_ENOUGH_MEM(r); 22 | return r; 23 | } 24 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #include 5 | 6 | __attribute__((noreturn)) void die(const char *error); 7 | void *xmalloc(size_t size); 8 | char *xstrdup(const char *s); 9 | 10 | #endif /* UTIL_H */ 11 | -------------------------------------------------------------------------------- /util.node.js: -------------------------------------------------------------------------------- 1 | 2 | // Polyfills in order to run Toy scripts with Node.js 3 | 4 | global.die = function (message) { 5 | console.error(message); 6 | process.exit(1); 7 | }; 8 | 9 | global.print = function (message) { 10 | console.log(message); 11 | }; 12 | -------------------------------------------------------------------------------- /value.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | #include 3 | 4 | const value_t v_null = {.type = value_type_null}; 5 | 6 | int v_to_bool(value_t v) { 7 | return v.type == value_type_number ? !!v.number : 8 | v.type == value_type_null ? 0 : 9 | v_is_string(v) ? strlen(v.object->string) : 10 | 1; 11 | } 12 | 13 | char *v_to_string(value_t v) { 14 | switch (v.type) { 15 | case value_type_number: { 16 | char buf[64]; 17 | snprintf(buf, 64, "%g", v.number); 18 | return xstrdup(buf); 19 | } 20 | 21 | case value_type_null: 22 | return xstrdup("null"); 23 | 24 | case value_type_object: 25 | switch (v.object->type) { 26 | case object_type_dict: return xstrdup("[dict]"); 27 | case object_type_list: return xstrdup("[list]"); 28 | case object_type_func: return xstrdup("[function]"); 29 | case object_type_string: return xstrdup(v.object->string); 30 | } 31 | } 32 | abort(); 33 | } 34 | 35 | double v_to_number(value_t v) { 36 | if (v_is_number(v)) { 37 | return v.number; 38 | } 39 | if (v_is_string(v)) { 40 | double n; 41 | if (sscanf(v.object->string, "%lf", &n) == 1) { 42 | return n; 43 | } 44 | } 45 | return 0; 46 | } 47 | 48 | long v_to_integer(value_t v) { 49 | double n = v_to_number(v); 50 | if (n >= MAX_SAFE_INTEGER) { 51 | n = MAX_SAFE_INTEGER; 52 | } 53 | if (n <= MIN_SAFE_INTEGER) { 54 | n = MIN_SAFE_INTEGER; 55 | } 56 | return n; 57 | } 58 | 59 | value_t v_add(value_t a, value_t b) { 60 | if (v_is_number(a) && v_is_number(b)) { 61 | return v_number(a.number + b.number); 62 | } 63 | 64 | char *left = v_to_string(a); 65 | char *right = v_to_string(b); 66 | char *s = xmalloc(strlen(left) + strlen(right) + 1); 67 | strcpy(s, left); 68 | strcat(s, right); 69 | free(left); 70 | free(right); 71 | value_t result = v_string(s); 72 | free(s); 73 | return result; 74 | } 75 | 76 | // Binary operations on numbers (except modulo, which requires integers) 77 | #define X(name, op) \ 78 | value_t v_##name(value_t a, value_t b) { \ 79 | if (v_is_number(a) && v_is_number(b)) { \ 80 | return v_number(a.number op b.number); \ 81 | } \ 82 | return v_number(0); \ 83 | } 84 | X(sub, -) X(mul, *) X(div, /) X(gt, >) X(lt, <) X(gte, >=) X(lte, <=) 85 | #undef X 86 | 87 | value_t v_mod(value_t a, value_t b) { 88 | if (v_is_number(a) && v_is_number(b)) { 89 | return v_number(v_to_integer(a) % v_to_integer(b)); 90 | } 91 | return v_number(0); 92 | } 93 | 94 | static int object_equal(const object_t *a, const object_t *b) { 95 | return a->type != b->type ? 0 : 96 | a->type == object_type_string ? strcmp(a->string, b->string) == 0 : 97 | 0; 98 | } 99 | 100 | int v_equal(value_t a, value_t b) { 101 | return a.type != b.type ? 0 : 102 | a.type == value_type_number ? a.number == b.number : 103 | a.type == value_type_null ? 1 : 104 | object_equal(a.object, b.object); 105 | } 106 | 107 | const char *v_typeof(value_t a) { 108 | switch (a.type) { 109 | case value_type_null: return "null"; 110 | case value_type_number: return "number"; 111 | case value_type_object: 112 | switch (a.object->type) { 113 | case object_type_string: return "string"; 114 | case object_type_func: return "function"; 115 | case object_type_list: return "list"; 116 | default: return "object"; 117 | } 118 | } 119 | abort(); 120 | } 121 | 122 | void v_set(value_t dict, value_t key, value_t v) { 123 | if (v_is_dict(dict)) { 124 | dict_setv(&dict.object->dict, key, v); 125 | } else if (v_is_list(dict)) { 126 | size_t index = v_to_integer(key); 127 | if (index < v_list_length(dict)) { 128 | return dict_setv(&dict.object->dict, key, v); 129 | } 130 | } 131 | } 132 | 133 | static value_t string_slice(value_t vstring, value_t vindex) { 134 | v_assert_type(vstring, string); 135 | size_t index = v_to_integer(vindex); 136 | const char *string = vstring.object->string; 137 | size_t len = strlen(string); 138 | if (index >= len) { 139 | return v_string(""); 140 | } 141 | size_t new_len = len - index; 142 | char *new = malloc(new_len + 1); 143 | memcpy(new, string + index, new_len); 144 | new[new_len] = 0; 145 | value_t vnew = v_string(new); 146 | free(new); 147 | return vnew; 148 | } 149 | 150 | static value_t string_index_of(value_t vstring, value_t vneedle) { 151 | v_assert_type(vneedle, string); 152 | v_assert_type(vstring, string); 153 | 154 | const char *s = vstring.object->string; 155 | char *begin = strstr(s, vneedle.object->string); 156 | return begin ? v_number(begin - s) : v_number(-1); 157 | } 158 | 159 | static value_t string_char_code_at(value_t vstring, value_t index) { 160 | v_assert_type(vstring, string); 161 | size_t i = v_to_integer(index); 162 | const char *s = vstring.object->string; 163 | return i < strlen(s) ? v_number(s[i]) : v_null; 164 | } 165 | 166 | value_t v_in(value_t key, value_t dict) { 167 | return v_number((v_is_dict(dict) || v_is_list(dict)) && 168 | dict_hasv(&dict.object->dict, key)); 169 | } 170 | 171 | size_t v_list_length(value_t list) { 172 | v_assert_type(list, list); 173 | return v_to_integer(dict_get(&list.object->dict, "length")); 174 | } 175 | 176 | value_t v_list_push(value_t list, value_t new) { 177 | v_assert_type(list, list); 178 | size_t index = v_list_length(list); 179 | dict_setv(&list.object->dict, v_number(index), new); 180 | dict_set(&list.object->dict, "length", v_number(index + 1)); 181 | return v_null; 182 | } 183 | 184 | static value_t v_list_concat(value_t a, value_t b) { 185 | v_assert_type(a, list); v_assert_type(b, list); 186 | value_t new = v_list(); 187 | size_t length = v_list_length(a), i; 188 | for (i = 0; i < length; i++) { 189 | v_list_push(new, v_get(a, v_number(i))); 190 | } 191 | length = v_list_length(b); 192 | for (i = 0; i < length; i++) { 193 | v_list_push(new, v_get(b, v_number(i))); 194 | } 195 | return new; 196 | } 197 | 198 | static value_t v_list_index_of(value_t list, value_t item) { 199 | v_assert_type(list, list); 200 | size_t length = v_list_length(list); 201 | for (size_t i = 0; i < length; i++) { 202 | if (v_equal(v_get(list, v_number(i)), item)) { 203 | return v_number(i); 204 | } 205 | } 206 | return v_number(-1); 207 | } 208 | 209 | static value_t create_method(value_t object, native_func_t func) { 210 | value_t m = v_native_func(func); 211 | m.object->func.parent_scope = object; 212 | return m; 213 | } 214 | 215 | static value_t get_list_property(value_t list, const char *key) { 216 | if (strcmp(key, "length") == 0) { 217 | return dict_get(&list.object->dict, "length"); 218 | } 219 | if (strcmp(key, "indexOf") == 0) { 220 | return create_method(list, v_list_index_of); 221 | } 222 | if (strcmp(key, "push") == 0) { 223 | return create_method(list, v_list_push); 224 | } 225 | if (strcmp(key, "concat") == 0) { 226 | return create_method(list, v_list_concat); 227 | } 228 | 229 | return v_null; 230 | } 231 | 232 | static value_t get_string_property(value_t string, const char *key) { 233 | if (strcmp(key, "length") == 0) { 234 | return v_number(strlen(string.object->string)); 235 | } 236 | if (strcmp(key, "slice") == 0) { 237 | return create_method(string, string_slice); 238 | } 239 | if (strcmp(key, "indexOf") == 0) { 240 | return create_method(string, string_index_of); 241 | } 242 | if (strcmp(key, "charCodeAt") == 0) { 243 | return create_method(string, string_char_code_at); 244 | } 245 | 246 | return v_null; 247 | } 248 | 249 | value_t v_get(value_t obj, value_t key) { 250 | if (v_is_dict(obj)) { 251 | return dict_getv(&obj.object->dict, key); 252 | 253 | } else if (v_is_list(obj)) { 254 | if (v_is_number(key)) { 255 | size_t index = v_to_integer(key); 256 | if (index < v_list_length(obj)) { 257 | return dict_getv(&obj.object->dict, key); 258 | } 259 | } 260 | 261 | char *skey = v_to_string(key); 262 | value_t result = get_list_property(obj, skey); 263 | free(skey); 264 | return result; 265 | 266 | } else if (v_is_string(obj)) { 267 | if (v_is_number(key)) { 268 | size_t index = key.number; 269 | const char *s = obj.object->string; 270 | if (index < strlen(s)) { 271 | char c[2] = {s[index], 0}; 272 | return v_string(c); 273 | } 274 | return v_null; 275 | } 276 | 277 | char *skey = v_to_string(key); 278 | value_t result = get_string_property(obj, skey); 279 | free(skey); 280 | return result; 281 | } 282 | 283 | return v_null; 284 | } 285 | -------------------------------------------------------------------------------- /value.h: -------------------------------------------------------------------------------- 1 | #ifndef VALUE_H 2 | #define VALUE_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAX_SAFE_INTEGER (9007199254740991) 8 | #define MIN_SAFE_INTEGER (-9007199254740991) 9 | 10 | typedef struct object object_t; 11 | typedef struct value value_t; 12 | 13 | enum value_type { 14 | value_type_null, 15 | value_type_number, 16 | value_type_object, 17 | }; 18 | 19 | struct value { 20 | enum value_type type; 21 | union { 22 | object_t *object; 23 | double number; // NaN is invalid 24 | }; 25 | }; 26 | 27 | extern const value_t v_null; 28 | 29 | static inline value_t v_number(double nbr) { 30 | return (value_t){ 31 | .type = value_type_number, 32 | .number = isnan((double)nbr) ? 0 : nbr 33 | }; 34 | } 35 | 36 | #define v_native_func(f) \ 37 | ((value_t){ \ 38 | .type = value_type_object, \ 39 | .object = new_native_func_object(f), \ 40 | }) 41 | 42 | #define v_string(cstr) \ 43 | ((value_t){ \ 44 | .type = value_type_object, \ 45 | .object = new_string_object(cstr), \ 46 | }) 47 | 48 | #define v_string_from_char(c) \ 49 | (v_string((char[]){c})) 50 | 51 | #define v_dict() \ 52 | ((value_t){ \ 53 | .type = value_type_object, \ 54 | .object = new_dict_object(), \ 55 | }) 56 | 57 | #define v_list() \ 58 | ((value_t){ \ 59 | .type = value_type_object, \ 60 | .object = new_list_object(), \ 61 | }) 62 | 63 | #define v_is_object(v) ((v).type == value_type_object) 64 | 65 | #define v_is_object_of_type(v, expected_type) \ 66 | (v_is_object(v) && \ 67 | (v).object->type == object_type_##expected_type) 68 | 69 | #define v_is_null(v) ((v).type == value_type_null) 70 | #define v_is_number(v) ((v).type == value_type_number) 71 | #define v_is_dict(v) (v_is_object_of_type((v), dict)) 72 | #define v_is_list(v) (v_is_object_of_type((v), list)) 73 | #define v_is_string(v) (v_is_object_of_type((v), string)) 74 | #define v_is_func(v) (v_is_object_of_type((v), func)) 75 | 76 | // Used to force the GC not to collect an object (because the GC does not 77 | // visit the C stack). 78 | #define v_inc_ref(v) \ 79 | ({ \ 80 | __auto_type inc_ref__v = (v); \ 81 | if ((inc_ref__v).type == value_type_object) { \ 82 | inc_ref__v.object->ref_count++; \ 83 | } \ 84 | }) 85 | 86 | #define v_dec_ref(v) \ 87 | ({ \ 88 | __auto_type dec_ref__v = (v); \ 89 | if ((dec_ref__v).type == value_type_object) { \ 90 | dec_ref__v.object->ref_count--; \ 91 | } \ 92 | }) 93 | 94 | #define v_assert_type(v, type) \ 95 | if (!v_is_##type(v)) { \ 96 | die("must be a " #type); \ 97 | } 98 | 99 | int v_to_bool(value_t v); 100 | char *v_to_string(value_t v); 101 | double v_to_number(value_t v); 102 | long v_to_integer(value_t v); 103 | 104 | // Binary operators 105 | #define X(name) value_t v_##name(value_t a, value_t b); 106 | X(add) X(sub) X(mul) X(div) X(mod) X(gt) X(lt) X(gte) X(lte) 107 | #undef X 108 | 109 | const char *v_typeof(value_t a); 110 | 111 | int v_equal(value_t a, value_t b); 112 | #define v_eq(a, b) (v_number(v_equal(a, b))) 113 | #define v_neq(a, b) (v_number(!v_equal(a, b))) 114 | 115 | value_t v_in(value_t key, value_t dict); 116 | void v_set(value_t dict, value_t key, value_t v); 117 | value_t v_get(value_t dict, value_t key); 118 | 119 | value_t v_list_push(value_t list, value_t new); 120 | size_t v_list_length(value_t list); 121 | 122 | #endif /* VALUE_H */ 123 | -------------------------------------------------------------------------------- /vm.c: -------------------------------------------------------------------------------- 1 | #include "toy.h" 2 | 3 | static value_t scope_lookup(value_t scope, const char *name) { 4 | if (dict_has(&scope.object->dict, name)) { 5 | return scope; 6 | } 7 | value_t parent = dict_get(&scope.object->dict, ""); 8 | if (!v_is_null(parent)) { 9 | return scope_lookup(parent, name); 10 | } 11 | return v_null; 12 | } 13 | 14 | static value_t scope_lookup_or_die(value_t scope, const char *name) { 15 | scope = scope_lookup(scope, name); 16 | if (!v_is_null(scope)) { 17 | return scope; 18 | } 19 | value_t namev = v_string(name); 20 | die(v_to_string(v_add(v_string("undefined variable "), namev))); 21 | } 22 | 23 | static value_t scope_get(value_t scope, const char *name) { 24 | scope = scope_lookup_or_die(scope, name); 25 | return dict_get(&scope.object->dict, name); 26 | } 27 | 28 | static void scope_set(value_t scope, const char *name, value_t v) { 29 | scope = scope_lookup_or_die(scope, name); 30 | dict_set(&scope.object->dict, name, v); 31 | } 32 | 33 | static void scope_decl(value_t scope, const char *name) { 34 | if (!dict_has(&scope.object->dict, name)) { 35 | dict_set(&scope.object->dict, name, v_null); 36 | } 37 | } 38 | 39 | value_t call_func(value_t func, value_t arg) { 40 | v_inc_ref(arg); 41 | v_inc_ref(func); 42 | if (!v_is_func(func)) { 43 | die("call_func(): not a function"); 44 | } 45 | value_t parent_scope = func.object->func.parent_scope; 46 | compiled_func_t *compiled = func.object->func.compiled; 47 | value_t result; 48 | if (compiled) { 49 | value_t child_scope = v_dict(); 50 | v_inc_ref(child_scope); 51 | dict_set(&child_scope.object->dict, "", parent_scope); 52 | if (compiled->param_name) { 53 | dict_set(&child_scope.object->dict, compiled->param_name, arg); 54 | } 55 | result = eval_func(func, child_scope); 56 | v_dec_ref(child_scope); 57 | } else { 58 | result = func.object->func.native(parent_scope, arg); 59 | } 60 | v_dec_ref(arg); 61 | v_dec_ref(func); 62 | return result; 63 | } 64 | 65 | typedef struct stackk stackk_t; 66 | 67 | #define STACK_CAPACITY 20 68 | 69 | struct stackk { 70 | value_t list[STACK_CAPACITY]; 71 | size_t size; 72 | }; 73 | 74 | static value_t stack_pop(stackk_t *stack) { 75 | if (!stack->size) { 76 | die("stack underflow"); 77 | } 78 | value_t value = stack->list[--(stack->size)]; 79 | v_dec_ref(value); 80 | return value; 81 | } 82 | 83 | static value_t stack_get_top(const stackk_t *stack) { 84 | if (!stack->size) { 85 | die("empty stack"); 86 | } 87 | return stack->list[stack->size - 1]; 88 | } 89 | 90 | static void stack_push(stackk_t *stack, value_t value) { 91 | if (stack->size == STACK_CAPACITY) { 92 | die("stack overflow"); 93 | } 94 | v_inc_ref(value); 95 | stack->list[stack->size++] = value; 96 | } 97 | 98 | static void stack_flush(stackk_t * stack) { 99 | for (size_t i = 0; i < stack->size; i++) { 100 | v_dec_ref(stack->list[i]); 101 | } 102 | } 103 | 104 | value_t eval_func(value_t funcv, value_t scope) { 105 | v_assert_type(funcv, func); 106 | v_inc_ref(funcv); 107 | v_inc_ref(scope); 108 | func_t *func = &funcv.object->func; 109 | const compiled_func_t *comp = func->compiled; 110 | stackk_t stack = {}; 111 | size_t ip = 0; 112 | 113 | #define tos (stack_get_top(&stack)) 114 | 115 | #define push(v) stack_push(&stack, (v)) 116 | #define pop() stack_pop(&stack) 117 | 118 | #define peek_opcode(offset) (comp->code[ip + (offset)]) 119 | 120 | #define next_opcode() \ 121 | ({ \ 122 | enum opcode next__op = peek_opcode(0); \ 123 | ip++; \ 124 | next__op; \ 125 | }) 126 | 127 | // Big endian 128 | #define peek_uint16() \ 129 | ((unsigned)peek_opcode(0) * 0x100 + peek_opcode(1)) \ 130 | 131 | for (;;) { 132 | request_garbage_collection(); 133 | 134 | enum opcode opcode = next_opcode(); 135 | switch (opcode) { 136 | case opcode_return: 137 | v_dec_ref(funcv); 138 | v_dec_ref(scope); 139 | stack_flush(&stack); 140 | if (!stack.size) { 141 | return v_null; 142 | } 143 | return tos; 144 | 145 | case opcode_load_const: { 146 | unsigned index = peek_uint16(); 147 | ip += 2; 148 | if (index >= comp->const_count) { 149 | die("load_const: const index out of range"); 150 | } 151 | push(comp->consts[index]); 152 | break; 153 | } 154 | 155 | case opcode_load_null: 156 | push(v_null); 157 | break; 158 | 159 | case opcode_load_empty_list: 160 | push(v_list()); 161 | break; 162 | 163 | case opcode_load_empty_dict: 164 | push(v_dict()); 165 | break; 166 | 167 | case opcode_list_push: { 168 | value_t item = pop(); 169 | value_t list = tos; 170 | v_assert_type(list, list); 171 | v_list_push(list, item); 172 | break; 173 | } 174 | 175 | case opcode_dict_push: { 176 | value_t value = pop(); 177 | value_t key = pop(); 178 | value_t dict = tos; 179 | v_set(dict, key, value); 180 | break; 181 | } 182 | 183 | case opcode_pop: 184 | pop(); 185 | break; 186 | 187 | case opcode_decl_var: { 188 | value_t vname = pop(); 189 | v_assert_type(vname, string); 190 | scope_decl(scope, vname.object->string); 191 | break; 192 | } 193 | 194 | case opcode_load_var: { 195 | value_t vname = pop(); 196 | v_assert_type(vname, string); 197 | push(scope_get(scope, vname.object->string)); 198 | break; 199 | } 200 | 201 | case opcode_load_func: { 202 | unsigned index = peek_uint16(); 203 | ip += 2; 204 | if (index >= comp->file->func_count) { 205 | die("load_func: func index out of range"); 206 | } 207 | compiled_func_t *comp_closure = comp->file->funcs + index; 208 | object_t *closure_obj = new_compiled_func_object(comp_closure); 209 | closure_obj->func.parent_scope = scope; 210 | value_t closure_value = { 211 | .type = value_type_object, 212 | .object = closure_obj, 213 | }; 214 | push(closure_value); 215 | break; 216 | } 217 | 218 | case opcode_call: { 219 | value_t arg = pop(); 220 | value_t child_func = pop(); 221 | push(call_func(child_func, arg)); 222 | break; 223 | } 224 | 225 | case opcode_store_var: { 226 | value_t value = pop(); 227 | value_t vname = pop(); 228 | v_assert_type(vname, string); 229 | scope_set(scope, vname.object->string, value); 230 | break; 231 | } 232 | 233 | case opcode_dup: 234 | push(tos); 235 | break; 236 | 237 | case opcode_goto: 238 | ip = peek_uint16(); 239 | break; 240 | 241 | case opcode_goto_if: { 242 | unsigned next = peek_uint16(); 243 | ip += 2; 244 | if (v_to_bool(pop())) { 245 | ip = next; 246 | } 247 | break; 248 | } 249 | 250 | case opcode_not: 251 | push(v_number(!v_to_bool(pop()))); 252 | break; 253 | 254 | case opcode_unary_minus: 255 | push(v_number(-v_to_number(pop()))); 256 | break; 257 | 258 | case opcode_typeof: 259 | push(v_string(v_typeof(pop()))); 260 | break; 261 | 262 | case opcode_set: { 263 | value_t key = pop(); 264 | value_t dict = pop(); 265 | value_t new_value = pop(); 266 | v_set(dict, key, new_value); 267 | break; 268 | } 269 | 270 | case opcode_get: { 271 | value_t key = pop(); 272 | value_t dict = pop(); 273 | push(v_get(dict, key)); 274 | break; 275 | } 276 | 277 | case opcode_rot: { 278 | value_t a = pop(); 279 | value_t b = pop(); 280 | push(a); 281 | push(b); 282 | break; 283 | } 284 | 285 | #define case_bin_op(name) \ 286 | case opcode_##name: { \ 287 | value_t _right = pop(); \ 288 | value_t _left = pop(); \ 289 | push(v_##name(_left, _right)); \ 290 | break; \ 291 | } 292 | 293 | case_bin_op(add) case_bin_op(sub) 294 | case_bin_op(mul) case_bin_op(div) case_bin_op(mod) 295 | case_bin_op(eq) case_bin_op(neq) 296 | case_bin_op(gt) case_bin_op(lt) 297 | case_bin_op(gte) case_bin_op(lte) 298 | case_bin_op(in) 299 | 300 | default: 301 | die(v_to_string(v_add(v_string("unknown opcode "), 302 | v_number(opcode)))); 303 | } 304 | } 305 | } 306 | 307 | static const char *opcode_names[opcode__count + 1] = { 308 | #define X(name) #name, 309 | # include "opcode.def" 310 | #undef X 311 | }; 312 | 313 | enum opcode string_to_opcode(const char *s) { 314 | for (int i = 0; i < opcode__count; i++) { 315 | if (opcode_names[i] && strcmp(opcode_names[i], s) == 0) { 316 | return i; 317 | } 318 | } 319 | return -1; 320 | } 321 | -------------------------------------------------------------------------------- /vm.h: -------------------------------------------------------------------------------- 1 | #ifndef VM_H 2 | #define VM_H 3 | 4 | #include 5 | #include "value.h" 6 | 7 | enum opcode { 8 | #define X(name) opcode_ ## name, 9 | # include "opcode.def" 10 | #undef X 11 | }; 12 | 13 | typedef struct compiled_func compiled_func_t; 14 | typedef struct compiled_file compiled_file_t; 15 | 16 | struct compiled_func { 17 | char *param_name; // may be null 18 | unsigned char *code; 19 | value_t *consts; 20 | struct bvalue *bconsts; 21 | size_t const_count; 22 | compiled_file_t *file; 23 | }; 24 | 25 | struct compiled_file { 26 | compiled_func_t *funcs; 27 | size_t func_count; 28 | }; 29 | 30 | value_t call_func(value_t func, value_t arg); 31 | 32 | // The optional `` property of the scope must be set 33 | value_t eval_func(value_t func, value_t scope); 34 | 35 | // Returns -1 on error 36 | enum opcode string_to_opcode(const char *s); 37 | 38 | #endif /* VM_H */ 39 | --------------------------------------------------------------------------------