diff --git a/Makefile b/Makefile index e01c0de..3b6254b 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,16 @@ # -------- # flathead -COMPILER=gcc +CC=gcc +CFLAGS=-Wall -O +TFLAGS= YACC=bison -y -d -t -v LEX=flex MAIN=src/flathead.c SRC_FILES=src/nodes.c src/gc.c src/eval.c src/runtime.c LIB_FILES=lib/console.c lib/Math.c lib/Number.c lib/Object.c + OUT_FILE=-o bin/fh GRAMMAR_FILE=src/grammar.y LEX_FILE=src/lexer.l @@ -17,6 +20,9 @@ YACC_OUT=y.tab.c all: clean default +debug: CFLAGS += -g +debug: default + tests: node test/runner.js node ctest/crunner.js @@ -30,12 +36,8 @@ lexer: clean: rm -rf y.* lex.yy.c bin/fh* a.out -debug: grammar lexer - $(COMPILER) -g $(OUT_FILE) $(YACC_OUT) $(LEX_OUT) $(MAIN) $(LIB_FILES) $(SRC_FILES) -lm - gdb bin/fh - install: default cp bin/fh /usr/local/bin/ default: grammar lexer - $(COMPILER) $(OUT_FILE) $(YACC_OUT) $(LEX_OUT) $(MAIN) $(LIB_FILES) $(SRC_FILES) -lm + $(CC) $(CFLAGS) $(OUT_FILE) $(YACC_OUT) $(LEX_OUT) $(MAIN) $(LIB_FILES) $(SRC_FILES) -lm diff --git a/lib/Number.c b/lib/Number.c index ad2ef99..292bb2c 100644 --- a/lib/Number.c +++ b/lib/Number.c @@ -4,16 +4,74 @@ #include #include "Number.h" +JSValue * +number_proto_to_exponential(JSArgs *args, State *state) +{ + // TODO: stub + return JSUNDEF(); +} + +JSValue * +number_proto_to_fixed(JSArgs *args, State *state) +{ + // TODO: stub + return JSUNDEF(); +} + +JSValue * +number_proto_to_locale_string(JSArgs *args, State *state) +{ + // TODO: stub + return JSUNDEF(); +} + +JSValue * +number_proto_to_precision(JSArgs *args, State *state) +{ + // TODO: stub + return JSUNDEF(); +} + +JSValue * +number_proto_to_string(JSArgs *args, State *state) +{ + // TODO: stub + return JSUNDEF(); +} + +JSValue * +number_proto_value_of(JSArgs *args, State *state) +{ + // TODO: stub + return JSUNDEF(); +} + JSValue * bootstrap_number() { JSValue *number = JSOBJ(); + JSValue *prototype = JSOBJ(); + + // Number + // ------ + // Properties + fh_set(number, "prototype", prototype); fh_set(number, "MAX_VALUE", JSNUM(DBL_MAX)); fh_set(number, "MIN_VALUE", JSNUM(DBL_MIN)); fh_set(number, "NEGATIVE_INFINITY", JSNINF()); fh_set(number, "POSITIVE_INFINITY", JSINF()); fh_set(number, "NaN", JSNAN()); + // Number.prototype + // ---------------- + + fh_set(prototype, "toExponential", JSNFUNC(&number_proto_to_exponential)); + fh_set(prototype, "toFixed", JSNFUNC(&number_proto_to_fixed)); + fh_set(prototype, "toLocaleString", JSNFUNC(&number_proto_to_locale_string)); + fh_set(prototype, "toPrecision", JSNFUNC(&number_proto_to_precision)); + fh_set(prototype, "toString", JSNFUNC(&number_proto_to_string)); + fh_set(prototype, "valueOf", JSNFUNC(&number_proto_value_of)); + return number; } diff --git a/lib/Number.h b/lib/Number.h index 18a1280..aa12cd1 100644 --- a/lib/Number.h +++ b/lib/Number.h @@ -3,4 +3,11 @@ #include "../src/flathead.h" +JSValue * number_proto_to_exponential(JSArgs *, State *); +JSValue * number_proto_to_fixed(JSArgs *, State *); +JSValue * number_proto_to_locale_string(JSArgs *, State *); +JSValue * number_proto_to_precision(JSArgs *, State *); +JSValue * number_proto_to_string(JSArgs *, State *); +JSValue * number_proto_value_of(JSArgs *, State *); + JSValue * bootstrap_number(void); diff --git a/lib/Object.c b/lib/Object.c index e7b3a7c..7e80a29 100644 --- a/lib/Object.c +++ b/lib/Object.c @@ -9,6 +9,7 @@ JSValue * obj_create(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.defineProperty(obj, prop, descriptor) @@ -16,6 +17,7 @@ JSValue * obj_define_property(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.defineProperties(obj, props) @@ -23,6 +25,7 @@ JSValue * obj_define_properties(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.getOwnPropertyDescriptor(obj, prop) @@ -30,6 +33,7 @@ JSValue * obj_get_own_property_descriptor(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.keys(obj) @@ -37,6 +41,7 @@ JSValue * obj_keys(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.getOwnPropertyNames(obj) @@ -44,6 +49,7 @@ JSValue * obj_get_own_property_names(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.getPrototypeOf(obj) @@ -51,6 +57,7 @@ JSValue * obj_get_prototype_of(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.preventExtensions(obj) @@ -58,6 +65,7 @@ JSValue * obj_prevent_extensions(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.isExtensible(obj) @@ -65,6 +73,7 @@ JSValue * obj_is_extensible(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.seal(obj) @@ -72,6 +81,7 @@ JSValue * obj_seal(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.isSealed(obj) @@ -79,6 +89,7 @@ JSValue * obj_is_sealed(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.freeze(obj) @@ -86,6 +97,7 @@ JSValue * obj_freeze(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.isFrozen(obj) @@ -93,6 +105,7 @@ JSValue * obj_is_frozen(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.prototype.hasOwnProperty(prop) @@ -100,6 +113,7 @@ JSValue * obj_proto_has_own_property(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.prototype.isPrototypeOf(object) @@ -107,6 +121,7 @@ JSValue * obj_proto_is_prototype_of(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.prototype.propertyIsEnumerable(prop) @@ -114,27 +129,29 @@ JSValue * obj_proto_property_is_enumerable(JSArgs *args, State *state) { // TODO: stub + return JSUNDEF(); } // Object.prototype.toLocaleString() JSValue * obj_proto_to_locale_string(JSArgs *args, State *state) { - // TODO: stub + return obj_proto_to_string(args, state); } // Object.prototype.toString() JSValue * obj_proto_to_string(JSArgs *args, State *state) { - // TODO: stub + return JSSTR("[object Object]"); } // Object.prototype.valueOf() JSValue * obj_proto_value_of(JSArgs *args, State *state) { - // TODO: stub + // TODO + return JSSTR("[object Object]"); } JSValue * diff --git a/lib/console.c b/lib/console.c index 7d8f9f4..247a125 100644 --- a/lib/console.c +++ b/lib/console.c @@ -29,6 +29,7 @@ console_assert(JSArgs *args, State *state) if (result->boolean.val) return JSUNDEF(); } fh_error(state, E_ASSERTION, "assertion failed"); + assert(0); } JSValue * diff --git a/src/eval.c b/src/eval.c index b9d1fee..0edc440 100644 --- a/src/eval.c +++ b/src/eval.c @@ -76,6 +76,7 @@ fh_if(JSValue *ctx, Node *node) return fh_eval(ctx, node->e2); else if (node->e3 != NULL) return fh_eval(ctx, node->e3); + return NULL; } JSValue * @@ -159,23 +160,26 @@ fh_arr(JSValue *ctx, Node *node) } JSValue * -fh_str_from_ident(Node *ident) +fh_str_from_node(JSValue *ctx, Node *node) { - return ident->sval != NULL ? - JSSTR(ident->sval) : - JSCAST(JSNUM(ident->val), T_STRING); + // Need to evaluate the node to a string key. + // e.g. obj['a'] obj.a arr[1] arr[arr.length - 1] + + if (node->type == NODE_IDENT) + return JSSTR(node->sval); + return JSCAST(fh_eval(ctx, node), T_STRING); } JSValue * fh_member(JSValue *ctx, Node *member) { JSValue *id1, *id2, *parent; - id1 = fh_str_from_ident(member->e1); - id2 = fh_str_from_ident(member->e2); + id1 = fh_str_from_node(ctx, member->e1); + id2 = fh_str_from_node(ctx, member->e2); parent = member->e2->type == NODE_MEMBER ? fh_member(ctx, member->e2) : fh_get(ctx, id2->string.ptr); - return fh_get(parent, id1->string.ptr); + return fh_get_proto(parent, id1->string.ptr); } JSValue * @@ -245,10 +249,8 @@ fh_setup_func_env(JSValue *ctx, JSValue *func, JSArgs *args) { JSValue *arguments = JSOBJ(); Node *func_node = func->function.node; - JSValue *scope = func->function.closure; + JSValue *scope = func->function.closure ? func->function.closure : JSOBJ(); - if (scope == NULL) - scope = JSOBJ(); scope->object.parent = ctx; fh_set(scope, "arguments", arguments); if (func_node->sval != NULL) @@ -307,6 +309,7 @@ fh_eval_postfix_exp(JSValue *ctx, Node *node) fh_set(ctx, node->e1->sval, fh_sub(old_val, JSNUM(1))); return old_val; } + assert(0); } JSValue * @@ -350,6 +353,8 @@ fh_eval_prefix_exp(JSValue *ctx, Node *node) fh_set(ctx, node->e1->sval, new_val); return new_val; } + + assert(0); } JSValue * @@ -385,6 +390,8 @@ fh_eval_bin_exp(JSValue *ctx, Node *node) return JSBOOL(fh_lt(a, b)->boolean.val || fh_eq(a, b, false)->boolean.val); if (STREQ(op, ">=")) return JSBOOL(fh_gt(a, b)->boolean.val || fh_eq(a, b, false)->boolean.val); + + assert(0); } JSValue * @@ -418,6 +425,8 @@ fh_add(JSValue *a, JSValue *b) // Number and Boolean => Number if (T_XOR(a, b, T_NUMBER, T_BOOLEAN)) return fh_add(JSCAST(a, T_NUMBER), JSCAST(b, T_NUMBER)); + + assert(0); } JSValue * @@ -516,6 +525,8 @@ fh_gt(JSValue *a, JSValue *b) } if (T_BOTH(a, b, T_STRING)) return JSBOOL(strcmp(a->string.ptr, b->string.ptr) > 0); + + assert(0); } JSValue * diff --git a/src/eval.h b/src/eval.h index 6ad653b..16113aa 100644 --- a/src/eval.h +++ b/src/eval.h @@ -19,7 +19,7 @@ #include "nodes.h" #define T_BOTH(a,b,t) (a->type == t && b->type == t) -#define T_XOR(a,b,t1,t2) (a->type == t1 && b->type == t2 || a->type == t2 && b->type == t1) +#define T_XOR(a,b,t1,t2) ((a->type == t1 && b->type == t2) || (a->type == t2 && b->type == t1)) void fh_while(JSValue *, Node *, Node *); void fh_var_stmt(JSValue *, Node *); @@ -37,7 +37,7 @@ JSValue * fh_arr(JSValue *, Node *); JSValue * fh_call(JSValue *, Node *); JSValue * fh_function_call(JSValue *, State *, JSValue *, Node *); JSValue * fh_setup_func_env(JSValue *, JSValue *, JSArgs *); -JSValue * fh_str_from_ident(Node *); +JSValue * fh_str_from_node(JSValue *, Node *); JSValue * fh_member(JSValue *, Node *); JSValue * fh_eval_postfix_exp(JSValue *, Node *); JSValue * fh_eval_prefix_exp(JSValue *, Node *); diff --git a/src/flathead.c b/src/flathead.c index 2f0b269..9992f39 100644 --- a/src/flathead.c +++ b/src/flathead.c @@ -19,19 +19,45 @@ #include "flathead.h" #include "gc.h" +/** + * Lookup a property on an object, resolve the value, and return it. + */ JSValue * -fh_get(JSValue *obj, char *prop_name) +fh_get(JSValue *obj, char *name) { // We can't read properties from undefined. if (obj->type == T_UNDEF) - fh_error(NULL, E_TYPE, "Cannot read property '%s' of undefined", prop_name); + fh_error(NULL, E_TYPE, "Cannot read property '%s' of undefined", name); - JSProp *prop = fh_get_prop(obj, prop_name); + JSProp *prop = fh_get_prop(obj, name); // But we'll happily return undefined if a property doesn't exist. if (prop == NULL) return JSUNDEF(); return prop->ptr; } +/** + * Same as `fh_get`, but recurse the scope chain. + */ +JSValue * +fh_get_rec(JSValue *obj, char *name) +{ + JSProp *prop = fh_get_prop_rec(obj, name); + return prop == NULL ? JSUNDEF() : prop->ptr; +} + +/** + * Same as `fh_get`, but recurse the prototype chain (if one exists). + */ +JSValue * +fh_get_proto(JSValue *obj, char *name) +{ + JSProp *prop = fh_get_prop_proto(obj, name); + return prop == NULL ? JSUNDEF() : prop->ptr; +} + +/** + * Lookup a property on an object and return it. + */ JSProp * fh_get_prop(JSValue *obj, char *name) { @@ -60,17 +86,6 @@ fh_set(JSValue *obj, char *name, JSValue *val) HASH_ADD_KEYPTR(hh, obj->object.map, prop->name, strlen(prop->name), prop); } -/* - * Look for a property on an object and return its value, - * checking parent scopes. - */ -JSValue * -fh_get_rec(JSValue *obj, char *name) -{ - JSProp *prop = fh_get_prop_rec(obj, name); - return prop == NULL ? JSUNDEF() : prop->ptr; -} - JSProp * fh_get_prop_rec(JSValue *obj, char *name) { @@ -81,6 +96,15 @@ fh_get_prop_rec(JSValue *obj, char *name) return prop; } +JSProp * +fh_get_prop_proto(JSValue *obj, char *name) +{ + JSProp *prop = fh_get_prop(obj, name); + if (prop == NULL && obj->proto != NULL) + return fh_get_prop_proto(obj->proto, name); + return prop; +} + /* * (Re)set a property on an object, setting the property on the * given object, or the closest parent scope on which it is @@ -111,6 +135,18 @@ fh_set_rec(JSValue *obj, char *name, JSValue *val) fh_set(scope_to_set, name, val); } +JSValue * +fh_try_get_proto(char *type) +{ + JSValue *global = fh_global(); + if (global != NULL) { + JSValue *obj = fh_get(global, type); + if (obj->type != T_UNDEF) + return fh_get(obj, "prototype"); + } + return NULL; +} + JSValue * fh_new_val(JSType type) { @@ -127,6 +163,7 @@ fh_new_number(double x, bool is_nan, bool is_inf, bool is_neg) val->number.is_nan = is_nan; val->number.is_inf = is_inf; val->number.is_neg = is_neg; + val->proto = fh_try_get_proto("Number"); return val; } @@ -137,6 +174,7 @@ fh_new_string(char *x) val->string.ptr = malloc((strlen(x) + 1) * sizeof(char)); strcpy(val->string.ptr, x); val->string.len = strlen(x); + //val->object.proto = fh_get(fh_get(fh_global(), "String"), "prototype"); return val; } @@ -145,6 +183,7 @@ fh_new_boolean(bool x) { JSValue *val = fh_new_val(T_BOOLEAN); val->boolean.val = x; + //val->object.proto = fh_get(fh_get(fh_global(), "Boolean"), "prototype"); return val; } @@ -154,6 +193,7 @@ fh_new_object() JSValue *val = fh_new_val(T_OBJECT); JSProp *map = NULL; val->object.map = map; + val->proto = fh_try_get_proto("Object"); return val; } @@ -294,6 +334,8 @@ fh_cast(JSValue *val, JSType type) if (type == T_STRING) return JSSTR("undefined"); if (type == T_NUMBER) return JSNAN(); } + + assert(0); } void diff --git a/src/flathead.h b/src/flathead.h index a6d885b..4b6d0da 100644 --- a/src/flathead.h +++ b/src/flathead.h @@ -23,6 +23,7 @@ #include #include #include +#include #include typedef int bool; @@ -96,7 +97,6 @@ struct JSObject { bool frozen; bool sealed; bool extensible; - struct JSValue *proto; struct JSValue *parent; JSProp *map; }; @@ -107,6 +107,7 @@ struct JSFunction { bool is_native; struct Node *node; struct JSValue *closure; + struct JSValue *prototype; JSNativeFunction native; }; @@ -119,6 +120,7 @@ typedef struct JSValue { struct JSFunction function; }; JSType type; + struct JSValue *proto; bool marked; } JSValue; @@ -126,7 +128,9 @@ void fh_set(JSValue *, char *, JSValue *); void fh_set_rec(JSValue *, char *, JSValue *); JSProp * fh_get_prop(JSValue *, char *); JSProp * fh_get_prop_rec(JSValue *, char *); +JSProp * fh_get_prop_proto(JSValue *, char *); JSValue * fh_get(JSValue *, char *); +JSValue * fh_get_proto(JSValue *, char *); JSValue * fh_get_rec(JSValue *, char *); JSValue * fh_new_val(JSType); diff --git a/src/gc.c b/src/gc.c index db08f4f..5bf9da6 100644 --- a/src/gc.c +++ b/src/gc.c @@ -36,6 +36,7 @@ fh_malloc(bool first_attempt) return fh_malloc(false); } fh_error(NULL, E_ERROR, "process out of memory"); + assert(0); } ContainerMetadata * @@ -57,6 +58,12 @@ fh_get_container() return container; } +JSValue * +fh_global() +{ + return fh_get_container()->global; +} + void fh_gc() { diff --git a/src/gc.h b/src/gc.h index c9ff25f..ad629a4 100644 --- a/src/gc.h +++ b/src/gc.h @@ -33,6 +33,7 @@ typedef struct ContainerMetadata { ContainerMetadata * fh_new_container(void); ContainerMetadata * fh_get_container(void); +JSValue * fh_global(void); JSValue * fh_malloc(bool); void fh_gc(void); void fh_gc_register_global(JSValue *); diff --git a/src/grammar.y b/src/grammar.y index 305fe07..b7dcbb9 100644 --- a/src/grammar.y +++ b/src/grammar.y @@ -21,6 +21,7 @@ #include #include #include "src/nodes.h" + #include "src/eval.h" #include "src/runtime.h" #define YYDEBUG 0 diff --git a/src/lexer.l b/src/lexer.l index 50c294b..dcef9b0 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -11,6 +11,7 @@ %} %option yylineno +%option nounput %% diff --git a/src/nodes.c b/src/nodes.c index c76373f..9a8e96e 100644 --- a/src/nodes.c +++ b/src/nodes.c @@ -53,7 +53,7 @@ new_node(enum NodeType type, Node *e1, Node *e2, Node *e3, node->column = column; if (type == NODE_NUM || type == NODE_BOOL) node->val = x; - if (s != 0) { + if (s != NULL) { node->sval = malloc((strlen(s) + 1) * sizeof(char)); strcpy(node->sval, s); } @@ -63,19 +63,10 @@ new_node(enum NodeType type, Node *e1, Node *e2, Node *e3, Node * pop_node(Node *node) { - // Several of our AST nodes follow the pattern: - // - // Head Tail - // Head Tail - // Head Tail - // Head ` - // - // ...where "Head" is e1 and "Tail" is e2. - - if (node->e2 != 0 && !node->e2->visited) + if (node->e2 != NULL && !node->e2->visited) return pop_node(node->e2); node->visited = true; - return node->e1 != 0 ? node->e1 : NULL; + return node->e1; } bool @@ -90,7 +81,7 @@ rewind_node(Node *node) // Unset visited on a node linked list. if (node->e1 == 0) return; node->visited = false; - if (node->e2 != 0) rewind_node(node->e2); + if (node->e2 != NULL) rewind_node(node->e2); } void diff --git a/src/runtime.c b/src/runtime.c index 9fc4234..7168b28 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -65,10 +65,11 @@ fh_bootstrap() fh_gc_register_global(global); + fh_set(global, "Object", bootstrap_object()); + fh_set(global, "Number", bootstrap_number()); + fh_set(global, "console", bootstrap_console()); fh_set(global, "Math", bootstrap_math()); - fh_set(global, "Number", bootstrap_number()); - fh_set(global, "Object", bootstrap_object()); fh_set(global, "NaN", JSNAN()); fh_set(global, "Infinity", JSINF()); fh_set(global, "isNaN", JSNFUNC(&is_nan)); diff --git a/test/test_arrays.js b/test/test_arrays.js index 5294534..8732ac8 100644 --- a/test/test_arrays.js +++ b/test/test_arrays.js @@ -32,3 +32,11 @@ assertEquals(12, a2[6].a); var a3 = [[[[[[1, 2]]]]]]; assertEquals(1, a3[0][0][0][0][0][0]); assertEquals(2, a3[0][0][0][0][0][1]); + +// Calculated index +var getIndex = function() { + return 1; +}; +var a4 = ['a', 'b', 'c', 'd']; +assertEquals('c', a4[4 - 2]); +assertEquals('b', a4[getIndex()]); diff --git a/test/test_object.js b/test/test_object.js new file mode 100644 index 0000000..0728b88 --- /dev/null +++ b/test/test_object.js @@ -0,0 +1,32 @@ +// test_object.js +// -------------- + +var assert = console.assert; + +// ----------------------------------------------------------------------------- +// Prototype +// ----------------------------------------------------------------------------- + +// Via an object + +var x = {a: 42, b: [1,2,3]}; +assert(x.toString() === '[object Object]'); +assert(x.toLocaleString() === '[object Object]'); +assert(x.hasOwnProperty('a')); +console.log(x.hasOwnProperty('a')); +assert(!x.hasOwnProperty('toString')); +assert(x.propertyIsEnumerable('b')); +assert(x.valueOf() === '[object Object]'); + +// Directly via the prototype + +assert(Object.prototype.toString() === '[object Object]'); +assert(Object.prototype.toLocaleString() === '[object Object]'); +assert(Object.prototype.hasOwnProperty('toString')); +assert(!Object.prototype.hasOwnProperty('a')); +assert(!Object.prototype.propertyIsEnumerable('toString')); +assert(Object.prototype.valueOf() === '[object Object]'); + +// ----------------------------------------------------------------------------- +// Prototype +// ----------------------------------------------------------------------------- diff --git a/test/test_prototype.js b/test/test_prototype.js new file mode 100644 index 0000000..5a82dcb --- /dev/null +++ b/test/test_prototype.js @@ -0,0 +1,7 @@ +// test_prototype.js +// ----------------- + +var assert = console.assert; + +var x = {a: 42, b: [1,2,3]}; +assert(x.toString() === '[object Object]');