diff --git a/ctest/test_casting.c b/ctest/test_casting.c index 4ff46d5..4fae81f 100644 --- a/ctest/test_casting.c +++ b/ctest/test_casting.c @@ -50,6 +50,27 @@ test_numbers_are_correctly_cast() TEST(JLCAST(int_num, T_UNDEF)->type == T_UNDEF); } +void +test_objects_are_correctly_cast() +{ + JLVALUE *obj = JLOBJ(); + + TEST(JLCAST(obj, T_BOOLEAN)->boolean.val == 1); + TEST(JLCAST(obj, T_NUMBER)->number.is_nan); + TEST(STREQ(JLCAST(obj, T_STRING)->string.ptr, "[Object object]")); +} + +void +test_functions_are_correctly_cast() +{ + void *fake_node = malloc(sizeof(void *)); + JLVALUE *func = JLFUNC(fake_node); + + TEST(JLCAST(func, T_BOOLEAN)->boolean.val == 1); + TEST(JLCAST(func, T_NUMBER)->number.is_nan); + TEST(STREQ(JLCAST(func, T_STRING)->string.ptr, "[Function]")); +} + void test_zero_is_false_others_true() { @@ -93,6 +114,8 @@ main() test_numeric_strings_are_correctly_cast(); test_non_numeric_strings_are_NaN_when_cast_to_num(); test_numbers_are_correctly_cast(); + test_objects_are_correctly_cast(); + test_functions_are_correctly_cast(); test_zero_is_false_others_true(); test_keywords_and_specials_cast_to_strings(); test_null_and_undefined_correctly_cast_to_number(); diff --git a/src/eval.c b/src/eval.c index 66ab7b6..6b2988f 100644 --- a/src/eval.c +++ b/src/eval.c @@ -50,11 +50,13 @@ jl_eval(JLVALUE *this, JLNode *node) while(!node->visited) result = jl_eval(this, pop_node(node)); break; case NODE_PROP: - jl_assign(this, node->e1->sval, jl_eval_exp(this, node->e2), "="); + jl_set(this, node->e1->sval, jl_eval_exp(this, node->e2)); break; case NODE_VAR_STMT: - // For now we just initialize the property to null. - jl_assign(this, node->e1->sval, JLNULL(), "="); + if (node->e2 != 0) + jl_set(this, node->e1->sval, jl_eval_exp(this, node->e2)); + else + jl_set(this, node->e1->sval, JLNULL()); break; case NODE_WHILE: jl_while(this, node->e1, node->e2); diff --git a/src/grammar.y b/src/grammar.y index 9902872..fac34b2 100644 --- a/src/grammar.y +++ b/src/grammar.y @@ -128,7 +128,8 @@ StatementList: ; VariableStatement: - VAR Identifier ';' { $$ = NEW_VARSTMT($2); } + VAR Identifier ';' { $$ = NEW_VARSTMT($2, NULL); } + | VAR Identifier '=' AssignmentExpression ';' { $$ = NEW_VARSTMT($2, $4); } ; EmptyStatement: diff --git a/src/jslite.c b/src/jslite.c index 53b7d28..4e3f99b 100644 --- a/src/jslite.c +++ b/src/jslite.c @@ -227,47 +227,79 @@ JLVALUE * jl_cast(JLVALUE *val, JLTYPE type) { if (val->type == type) return val; - if (val->type == T_NUMBER && type == T_STRING) { - if (val->number.is_nan) return JLSTR("NaN"); - if (val->number.is_inf) return JLSTR("Infinity"); - char tmp[100]; - sprintf(tmp, "%g", val->number.val); - return JLSTR(tmp); + if (type == T_NULL) return JLNULL(); + if (type == T_UNDEF) return JLUNDEF(); + + // Number => x + if (val->type == T_NUMBER) { + if (type == T_STRING) { + if (val->number.is_nan) return JLSTR("NaN"); + if (val->number.is_inf) return JLSTR("Infinity"); + char tmp[100]; + sprintf(tmp, "%g", val->number.val); + return JLSTR(tmp); + } + if (type == T_BOOLEAN) { + // O is false, x < 0 & x > 0 true + if (val->number.val == 0) return JLBOOL(0); + return JLBOOL(1); + } + } + + // String => x + if (val->type == T_STRING) { + if (type == T_NUMBER) { + char *err; + double d = strtod(val->string.ptr, &err); + if (*err != 0) return JLNAN(); + return JLNUM(d); + } + if (type == T_BOOLEAN) { + // "" is false, all others true + if (val->string.len == 0) return JLBOOL(0); + return JLBOOL(1); + } } - if (val->type == T_STRING && type == T_NUMBER) { - char *err; - double d = strtod(val->string.ptr, &err); - if (*err != 0) return JLNAN(); - return JLNUM(d); + + // Boolean => x + if (val->type == T_BOOLEAN) { + if (type == T_STRING) { + if (val->boolean.val == 1) return JLSTR("true"); + return JLSTR("false"); + } + if (type == T_NUMBER) { + if (val->boolean.val == 1) return JLNUM(1); + return JLNUM(0); + } } - if (val->type == T_NULL && type == T_STRING) - return JLSTR("null"); - if (val->type == T_NULL && type == T_NUMBER) - return JLNUM(0); - if (val->type == T_UNDEF && type == T_STRING) - return JLSTR("undefined"); - if (val->type == T_UNDEF && type == T_NUMBER) - return JLNAN(); - if (val->type == T_BOOLEAN && type == T_STRING) { - if (val->boolean.val == 1) return JLSTR("true"); - return JLSTR("false"); + + // Object => x + if (val->type == T_OBJECT) { + if (type == T_BOOLEAN) return JLBOOL(1); + if (type == T_NUMBER) return JLNAN(); + // TODO: Call Object.toString() + if (type == T_STRING) return JLSTR("[Object object]"); } - if (val->type == T_BOOLEAN && type == T_NUMBER) { - if (val->boolean.val == 1) return JLNUM(1); - return JLNUM(0); + + // Function => x + if (val->type == T_FUNCTION) { + if (type == T_BOOLEAN) return JLBOOL(1); + if (type == T_NUMBER) return JLNAN(); + // TODO: Call Function.toString() + if (type == T_STRING) return JLSTR("[Function]"); } - if (val->type == T_NUMBER && type == T_BOOLEAN) { - // O is false, x < 0 & x > 0 true - if (val->number.val == 0) return JLBOOL(0); - return JLBOOL(1); + + // null => x + if (val->type == T_NULL) { + if (type == T_STRING) return JLSTR("null"); + if (type == T_NUMBER) return JLNUM(0); } - if (val->type == T_STRING && type == T_BOOLEAN) { - // "" is false, all others true - if (val->string.len == 0) return JLBOOL(0); - return JLBOOL(1); + + // undefined => x + if (val->type == T_UNDEF) { + if (type == T_STRING) return JLSTR("undefined"); + if (type == T_NUMBER) return JLNAN(); } - if (type == T_NULL) return JLNULL(); - if (type == T_UNDEF) return JLUNDEF(); } void diff --git a/src/nodes.h b/src/nodes.h index aff68ca..e4cd3b1 100644 --- a/src/nodes.h +++ b/src/nodes.h @@ -78,7 +78,7 @@ void rewind_node(JLNode *); bool empty_node(JLNode *); #define NEW_IDENT(name) new_node(NODE_IDENT,0,0,0,0,name) -#define NEW_VARSTMT(ident) new_node(NODE_VAR_STMT,ident,0,0,0,0) +#define NEW_VARSTMT(ident,exp) new_node(NODE_VAR_STMT,ident,exp,0,0,0) #define NEW_WHILE(cnd,blck) new_node(NODE_WHILE,cnd,blck,0,0,0) #define NEW_DOWHILE(cnd,blck) new_node(NODE_DOWHILE,cnd,blck,0,0,0) #define NEW_BLOCK(stmtlst) new_node(NODE_BLOCK,stmtlst,0,0,0,0) diff --git a/src/runtime.c b/src/runtime.c index 24a55d0..d370c8d 100644 --- a/src/runtime.c +++ b/src/runtime.c @@ -16,6 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include "runtime.h" #include "../lib/console.h" @@ -31,6 +32,29 @@ jl_is_nan(JLARGS *args) return JLBOOL(1); } +JLVALUE * +jl_is_finite(JLARGS *args) +{ + JLVALUE *num = JLCAST(args->arg == 0 ? JLUNDEF() : args->arg, T_NUMBER); + if (num->number.is_nan || num->number.is_inf) return JLBOOL(0); + return JLBOOL(1); +} + +JLVALUE * +jl_parse_int(JLARGS *args) +{ + // Ecma 15.1.2.2 + // TODO: use radix argument, strip whitespace. + JLVALUE *num = JLCAST(args->arg == 0 ? JLUNDEF() : args->arg, T_NUMBER); + return JLNUM(floor(num->number.val)); +} + +JLVALUE * +jl_parse_float(JLARGS *args) +{ + return JLCAST(args->arg == 0 ? JLUNDEF() : args->arg, T_NUMBER); +} + JLVALUE * jl_bootstrap() { @@ -40,6 +64,9 @@ jl_bootstrap() jl_set(global, "NaN", JLNAN()); jl_set(global, "Infinity", JLINF()); jl_set(global, "isNaN", JLNFUNC((JLNATVFUNC)&jl_is_nan)); + jl_set(global, "isFinite", JLNFUNC((JLNATVFUNC)&jl_is_finite)); + jl_set(global, "parseInt", JLNFUNC((JLNATVFUNC)&jl_parse_int)); + jl_set(global, "parseFloat", JLNFUNC((JLNATVFUNC)&jl_parse_float)); jl_set(global, "undefined", JLUNDEF()); jl_set(global, "this", global); diff --git a/src/runtime.h b/src/runtime.h index 86df475..7550a6e 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -19,3 +19,5 @@ #include "jslite.h" JLVALUE * jl_bootstrap(void); +JLVALUE * jl_is_nan(JLARGS *); +JLVALUE * jl_is_finite(JLARGS *); diff --git a/test/test_assignment.js b/test/test_assignment.js index 0738ab9..bc8c919 100644 --- a/test/test_assignment.js +++ b/test/test_assignment.js @@ -1,7 +1,7 @@ // test_assignment.js // ------------------ -x = 0; +var x = 0; x += 4; console.assert(x == 4); diff --git a/test/test_boolean.js b/test/test_boolean.js index d1ac6a6..c63c54d 100644 --- a/test/test_boolean.js +++ b/test/test_boolean.js @@ -1,6 +1,5 @@ // test_boolean.js // --------------- -// Tests adapted from v8's msjunit console.assert(true == !false); console.assert(false == !true); diff --git a/test/test_calls.js b/test/test_calls.js index 32a732e..b4f07af 100644 --- a/test/test_calls.js +++ b/test/test_calls.js @@ -1,7 +1,7 @@ // test_calls.js // ------------- -myObject = { +var myObject = { myMethod: function() { return 42; } diff --git a/test/test_functions.js b/test/test_functions.js index d8634e5..109bf96 100644 --- a/test/test_functions.js +++ b/test/test_functions.js @@ -1,23 +1,23 @@ // test_functions.js // ----------------- -assertEquals = function(a, b) { +var assertEquals = function(a, b) { return console.assert(a == b); }; -add = function(a, b) { +var add = function(a, b) { return a + b; }; -square = function(x) { +var square = function(x) { return x * x; }; -incrementSquare = function(x) { +var incrementSquare = function(x) { return add(square(x), 1); }; -recursive = function(x) { +var recursive1 = function(x) { if (x < 10) { x = x + 1; return arguments.callee(x); @@ -25,8 +25,17 @@ recursive = function(x) { return x; }; +var recursive2 = function(x) { + if (x < 10) { + x = x + 1; + return recursive2(x); + } + return x; +}; + assertEquals(square(3), 9); assertEquals(add(2, 2), 4); assertEquals(incrementSquare(9), 82); assertEquals(incrementSquare(20), 401); -assertEquals(recursive(1), 10); +assertEquals(recursive1(1), 10); +assertEquals(recursive2(1), 10); diff --git a/test/test_globals.js b/test/test_globals.js new file mode 100644 index 0000000..c77f576 --- /dev/null +++ b/test/test_globals.js @@ -0,0 +1,18 @@ +// test_globals.js +// --------------- +// Test methods and properties on the global object. + +var assert = console.assert; + +assert(isFinite(42)); +assert(!isFinite(undefined)); +assert(!isFinite(NaN)); + +assert(isNaN(NaN)); +assert(isNaN(undefined)); +assert(!isNaN(12)); +assert(!isNaN(null)); + +assert(undefined === undefined); + +assert(this); diff --git a/test/test_operators.js b/test/test_operators.js index bb78956..8cdb10f 100644 --- a/test/test_operators.js +++ b/test/test_operators.js @@ -1,32 +1,33 @@ // test_operators.js // ----------------- -assert = console.assert; +var assert = console.assert; // ------------------------------------------------------------------ // UNARY PREFIX // ------------------------------------------------------------------ -a = 0; +var a = 0; assert(++a === 1); -b = 2; +var b = 2; assert(--b === 1); -numString = "3.14"; +var numString = "3.14"; assert(+numString === 3.14); assert(-numString === -3.14); assert(!numString === false); assert(!!numString === true); assert(!false === true); +assert(isNaN(+{})); -result = ++numString; +var result = ++numString; // Handle IEEE precision quirks assert(result >= 4.14 && result <= 4.14 + 0.0000001); -alphaString = "abc"; +var alphaString = "abc"; assert(isNaN(++alphaString)); @@ -34,11 +35,11 @@ assert(isNaN(++alphaString)); // UNARY POSTFIX // ------------------------------------------------------------------ -c = 0; +var c = 0; assert(c++ === 0); assert(c === 1); -d = 2; +var d = 2; assert(d-- === 2); assert(d === 1); @@ -58,8 +59,8 @@ assert(NaN != NaN && NaN !== NaN); assert(Infinity == Infinity && Infinity === Infinity); assert(2 == '2' && 2 !== '2'); -obj = {}; -differentObj = {}; +var obj = {}; +var differentObj = {}; assert(obj == obj && obj === obj); assert(obj != differentObj && obj !== differentObj); @@ -117,5 +118,5 @@ assert(2 <= 2); assert(2 > 1); assert(1.00000001 > 1); assert(2 >= 2); -x = 1; +var x = 1; assert(x < 2); diff --git a/test/test_while.js b/test/test_while.js index ebcd6c6..d3f223d 100644 --- a/test/test_while.js +++ b/test/test_while.js @@ -1,7 +1,7 @@ // test_while.js // ------------- -x = 0; +var x = 0; while(x < 5) { console.assert(x != 5); x = x + 1;