diff --git a/.gitignore b/.gitignore index de63f45..8166c69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ bin/flat y.* -lex.yy.c +lex.yy.? *.out tags *.o diff --git a/Makefile b/Makefile index 2ee24bd..9e74364 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,12 @@ CC = gcc CFLAGS = -Wall -Wextra -Wno-unused-parameter -O3 -std=c99 -pedantic -D_XOPEN_SOURCE -YACC = bison -y -d -t -v + +YACC = bison +YACC_FLAGS = -y -d -t -v + LEX = flex +LEX_FLAGS = LIBS = -I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib -lm OBJ_FILES = y.tab.o lex.yy.o src/eval.o src/str.o src/regexp.o src/cli.o \ @@ -77,13 +81,15 @@ malloc-debug: LIBS += -lefence malloc-debug: debug -lex.yy.o: $(LEX_FILE) - $(LEX) $(LEX_FILE) +lex.yy.c: + $(LEX) --header-file=lex.yy.h $(LEX_FLAGS) $(LEX_FILE) + +lex.yy.o: lex.yy.c $(LEX_FILE) @echo "[CC -o] lex.yy.o" @$(CC) -c $(CFLAGS) lex.yy.c -o lex.yy.o -y.tab.o: $(YACC_FILE) - $(YACC) $(YACC_FILE) +y.tab.o: lex.yy.c $(YACC_FILE) + $(YACC) $(YACC_FLAGS) $(YACC_FILE) @echo "[CC -o] y.tab.o" @$(CC) -c $(CFLAGS) y.tab.c -o y.tab.o @@ -94,7 +100,7 @@ linker: $(OBJ_FILES) @$(CC) -c $(CFLAGS) $< -o $@ clean: - rm -rf y.* lex.yy.c $(OUT_FILE) $(OBJ_FILES) + rm -rf y.* lex.yy.c y.tab.c y.tab.h $(OUT_FILE) $(OBJ_FILES) install: cp $(OUT_FILE) /usr/local/bin/ diff --git a/src/flathead.h b/src/flathead.h index 80bbc00..b119592 100644 --- a/src/flathead.h +++ b/src/flathead.h @@ -251,7 +251,8 @@ eval_state * fh_new_state(int, int); js_val * fh_get_arg(js_args *, int); unsigned int fh_arg_len(js_args*); -js_val * fh_eval_file(FILE *, js_val *, int); +js_val * fh_eval_file(FILE *, js_val *); +js_val * fh_eval_string(char *, js_val *); js_val * fh_try_get_proto(char *); bool fh_is_callable(js_val *); diff --git a/src/grammar.y b/src/grammar.y index 7792228..d0c2859 100644 --- a/src/grammar.y +++ b/src/grammar.y @@ -29,6 +29,7 @@ #include #endif + #include "lex.yy.h" #include "src/flathead.h" #include "src/nodes.h" #include "src/eval.h" @@ -99,10 +100,7 @@ #define NEW_WITH(exp,stmt) NEW_NODE(NODE_WITH_STMT,exp,stmt,0,0,0) void yyerror(const char *); - void yyrestart(FILE *); - int yylex(void); int yydebug; - FILE *yyin; int fh_get_input(char *, int); ast_node *root; eval_state *state; @@ -1142,8 +1140,10 @@ fh_get_input(char *buf, int size) return 0; } strcpy(buf, line); - // Ghetto ASI + + // Poor man's ASI: if (!strchr(buf, ';')) strcat(buf, ";"); + strcat(buf, "\n"); free(line); add_history(buf); @@ -1162,7 +1162,7 @@ fh_get_input(char *buf, int size) } js_val * -fh_eval_file(FILE *file, js_val *ctx, int is_repl) +fh_eval_file(FILE *file, js_val *ctx) { yyrestart(file); yyparse(); @@ -1174,6 +1174,20 @@ fh_eval_file(FILE *file, js_val *ctx, int is_repl) return res; } +js_val * +fh_eval_string(char *string, js_val *ctx) +{ + void *buffer = yy_scan_string(string); + yyparse(); + + if (fh->opt_print_ast) + print_node(root, true, 0); + + js_val *res = fh_eval(ctx, root); + yy_delete_buffer(buffer); + return res; +} + int main(int argc, char **argv) { @@ -1227,10 +1241,10 @@ main(int argc, char **argv) // continue. Use setjmp here and longjmp in `fh_error` to simulate // exception handling. if (!setjmp(fh->jmpbuf)) - DEBUG(fh_eval_file(source, fh->global, true)); + DEBUG(fh_eval_file(source, fh->global)); } } else { - fh_eval_file(source, fh->global, false); + fh_eval_file(source, fh->global); } return 0; diff --git a/src/lexer.l b/src/lexer.l index 9b94e5c..c42b9f0 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -26,6 +26,8 @@ int yycolumn = 1; int prev_token = 0; void yyuseraction(void); + YY_BUFFER_STATE yy_scan_string(const char *); + void yy_delete_buffer(YY_BUFFER_STATE); char * fh_extract_string(char *); void fh_parse_error(char *); diff --git a/src/runtime/runtime.c b/src/runtime/runtime.c index 32ad876..9992a6c 100644 --- a/src/runtime/runtime.c +++ b/src/runtime/runtime.c @@ -80,9 +80,9 @@ global_parse_float(js_val *instance, js_args *args, eval_state *state) js_val * global_eval(js_val *instance, js_args *args, eval_state *state) { - // TODO: implement - fh_error(state, E_ERROR, "eval is not yet implemented"); - UNREACHABLE(); + js_val *code = TO_STR(ARG(args, 0)); + code->string.ptr[code->string.length] = '\0'; + return fh_eval_string(code->string.ptr, state->ctx); } // gc() @@ -108,7 +108,7 @@ global_load(js_val *instance, js_args *args, eval_state *state) fh_error(state, E_ERROR, "File could not be read"); FILE *file = fopen(name->string.ptr, "r"); - fh_eval_file(file, fh->global, false); + fh_eval_file(file, fh->global); fclose(file); } return JSUNDEF(); diff --git a/test/test_eval.js b/test/test_eval.js new file mode 100644 index 0000000..158a002 --- /dev/null +++ b/test/test_eval.js @@ -0,0 +1,20 @@ +// test_eval.js +// ------------ +// Note the unfortunate limitation that until ASI is added, statements within +// strings passed to eval need to be semicolon-terminated. + +(this.load || require)((this.load ? 'test' : '.') + '/tools/assertions.js'); + + +test('eval without references', function() { + assertEquals(1, eval('1;')); + assertEquals(4, eval('2 + 2;')); + assertEquals(42, eval('(function() { return 42; });')()); +}); + +test('eval with references', function() { + var x = 42; + assertEquals(x, eval('x;')); + assertEquals(f, eval('f;')); + function f() { return 'The f function'; }; +}); diff --git a/test/test_globals.js b/test/test_globals.js index 0466833..e1dfcd6 100644 --- a/test/test_globals.js +++ b/test/test_globals.js @@ -53,23 +53,23 @@ test('global functions', function() { assertIsFunction(parseInt, 2); // without radix - assert(parseInt("42") === 42); - assert(parseInt("3.14") === 3); + assertEquals(42, parseInt("42")); + assertEquals(3, parseInt("3.14")); assertNaN(parseInt("Not a number")); // with radix - assert(parseInt(" 0xF", 16) === 15); - assert(parseInt(" F", 16) === 15); - assert(parseInt("17", 8) === 15); - // FIXME: assert(parseInt(021, 8) === 15); - assert(parseInt("015", 10) === 15); - assert(parseInt(15.99, 10) === 15); - // FIXME: assert(parseInt("FXX123", 16) === 15); - assert(parseInt("1111", 2) === 15); - // FIXME: assert(parseInt("15*3", 10) === 15); - // FIXME: assert(parseInt("15e2", 10) === 15); - // FIXME: assert(parseInt("15px", 10) === 15); - assert(parseInt("12", 13) === 15); + assertEquals(15, parseInt(" 0xF", 16)); + assertEquals(15, parseInt(" F", 16)); + assertEquals(15, parseInt("17", 8)); + // FIXME: assertEquals(15, parseInt(021, 8)); + assertEquals(15, parseInt("015", 10)); + assertEquals(15, parseInt(15.99, 10)); + // FIXME: assertEquals(15, parseInt("FXX123", 16)); + assertEquals(15, parseInt("1111", 2)); + // FIXME: assertEquals(15, parseInt("15*3", 10)); + // FIXME: assertEquals(15, parseInt("15e2", 10)); + // FIXME: assertEquals(15, parseInt("15px", 10)); + assertEquals(15, parseInt("12", 13)); assertNaN(parseInt("Not a number", 10)); }); @@ -82,7 +82,10 @@ test('global functions', function() { }); test('eval(x)', function() { + // See also: test_eval.js assertIsFunction(eval, 1); + assertEquals(4, eval('2 + 2;')); + assertEquals(42, eval('21 * 2;')); }); });