diff --git a/README.md b/README.md index ceedc92..468d71b 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,10 @@ Generate even integers between 0 and 10 (inclusive). parse_and_eval("function int_between(low, high) {\ return low > high ? amb() : amb(low, int_between(low + 1, high));\ }\ - \ function is_even(x) { return (x % 2) === 0;}\ - \ - let integer = int_between(0, 10);\ - require(is_even(integer));\ + function divisible_by_three(x) { return (x % 3) === 0;}\ + let integer = int_between(0, 12);\ + require(is_even(integer) || divisible_by_three(integer));\ integer;"); // result: 0 @@ -22,26 +21,35 @@ parse_and_eval("function int_between(low, high) {\ Subsequent values can be generated by typing: ```js try_again(); // result: 2 +try_again(); // result: 3 try_again(); // result: 4 try_again(); // result: 6 +try_again(); // result: 8 +try_again(); // result: 9 +try_again(); // result: 10 +try_again(); // result: 12 +try_again(); // result: null ``` ### Other quick examples ```js -const integer_from = n => amb(n, integer_from(n + 1)); integer_from(1); -// Result: 1, 2, 3, 4, ... +parse_and_eval("const integer_from = n => amb(n, integer_from(n + 1)); integer_from(1);"); +// result: 1 +try_again(); // result: 2 +try_again(); // result: 3 +try_again(); // result: 4 ... and so on... ``` ```js -const f = amb(1, 2, 3); const g = amb(5, 6, 7); f; -// Result: 1, 1, 1, 2, 2, 2, 3, 3, 3 +parse_and_eval("const f = amb(1, 2, 3); const g = amb(5, 6, 7); f;"); +// Result: 1, 1, 1, 2, 2, 2, 3, 3, 3 (use the try_again() function) ``` ```js -const f = amb(1, 2, 3); const g = amb(5, 6, 7); g; -// Result: 5, 6, 7, 5, 6, 7, 5, 6, 7 +parse_and_eval("const f = amb(1, 2, 3); const g = amb(5, 6, 7); g;"); +// Result: 5, 6, 7, 5, 6, 7, 5, 6, 7 (use the try_again() function) ``` ### Running Tests diff --git a/evaluator.js b/evaluator.js index c7b42f7..501aec6 100644 --- a/evaluator.js +++ b/evaluator.js @@ -1,35 +1,12 @@ /* -Evaluator for language with booleans, conditionals, -sequences, functions, constants, variables and blocks - -This is an evaluator for a language that lets you declare -functions, variables and constants, apply functions, and -carry out simple arithmetic calculations, boolean operations. - -The covered Source ยง1 sublanguage is: - -stmt ::= const name = expr ; - | let name = expr ; - | function name(params) block - | expr ; - | stmt stmt - | name = expr ; - | block -block ::= { stmt } -expr ::= expr ? expr : expr - | expr binop expr - | unop expr - | name - | number - | expr(expr, expr, ...) -binop ::= + | - | * | / | % | < | > | <= | >= - | === | !== | && | || -unop ::= ! -*/ - -/* CONSTANTS: NUMBERS, STRINGS, TRUE, FALSE */ - -// constants (numbers, strings, booleans) +Evaluator for a non-deterministic language with booleans, conditionals, +sequences, functions, constants, variables and blocks. + +(examples available on our github repo) + +/* CONSTANTS: NUMBERS, STRINGS, TRUE, FALSE, NULL */ + +// constants (numbers, strings, booleans, null) // are considered "self_evaluating". This means, they // represent themselves in the syntax tree @@ -48,12 +25,15 @@ function is_tagged_list(stmt, the_tag) { return is_pair(stmt) && head(stmt) === the_tag; } -/* AMB operator */ +/* AMB OPERATOR */ +/* The amb operator accepts a number of arguments + and ambiguously returns one of them. */ function is_amb(stmt) { return is_tagged_list(stmt, "application") && is_name(operator(stmt)) && name_of_name(operator(stmt)) === "amb"; } + function amb_choices(stmt) { return operands(stmt); } @@ -73,6 +53,10 @@ function analyze_amb(exp) { }; } +/* REQUIRE OPERATOR */ +/* The require operator verifies whether a certain predicate is satisfied. + If the predicate is not satisfied, the require operator forces + the evaluator to backtrack and retrieve the next possible value. */ function is_require(stmt) { return is_tagged_list(stmt, "application") && is_name(operator(stmt)) && @@ -378,8 +362,6 @@ function analyze_logical_or(left_hand_expr_func, right_hand_expr_func) { /* FUNCTION APPLICATION */ -// The core of our evaluator is formed by the -// implementation of function applications. // Applications are tagged with "application" // and have "operator" and "operands" @@ -441,27 +423,37 @@ function get_args(arg_funcs, env, succeed, fail) { fail); } -/* APPLY */ - -// apply_in_underlying_javascript allows us -// to make use of JavaScript's primitive functions -// in order to access operators such as addition +// primitive functions are tagged with "primitive" +// and come with a Source function "implementation" -function apply_primitive_function(fun, argument_list) { - return apply_in_underlying_javascript( - primitive_implementation(fun), - argument_list); +function make_primitive_function(impl) { + return list("primitive", impl); +} +function is_primitive_function(fun) { + return is_tagged_list(fun, "primitive"); +} +function primitive_implementation(fun) { + return list_ref(fun, 1); } + +/* APPLY */ + // function application needs to distinguish between // primitive functions (which are evaluated using the // underlying JavaScript), and compound functions. -// An application of the latter needs to evaluate the -// body of the function value with respect to an + +// Just like deterministic Source, +// application of compound functions is done by evaluating the +// body of the function with respect to an // environment that results from extending the function // object's environment by a binding of the function // parameters to the arguments and of local names to -// the special value no_value_yet +// the special value no_value_yet. + +// One difference is that we do not return the result of function +// application. Instead, we rely on the "succeed" continuation +// to use the result. function execute_application(fun, args, succeed, fail) { if (is_primitive_function(fun)) { @@ -475,13 +467,24 @@ function execute_application(fun, args, succeed, fail) { locals); const values = append(args, temp_values); body(extend_environment(names, values, function_environment(fun)), - succeed, - fail); + succeed, + fail); } else { error(fun, "Unknown function type in apply"); } } +// apply_in_underlying_javascript allows us +// to make use of JavaScript's primitive functions +// in order to access operators such as addition + +function apply_primitive_function(fun, argument_list) { + return apply_in_underlying_javascript( + primitive_implementation(fun), + argument_list); +} + + // We use a nullary function as temporary value for names whose // declaration has not yet been evaluated. The purpose of the // function definition is purely to create a unique identity; @@ -737,7 +740,7 @@ function extend_environment(names, vals, base_env) { // invokes the appropriate analysis. Analysing a statement // will return a function that accepts an environment // and returns the value of the statement. Note that some -// statements may only have side effects and no value (e.g. assignment). +// statements may have side effects in addition to the value returned (e.g. assignment). function analyze(stmt) { return is_self_evaluating(stmt) @@ -840,13 +843,14 @@ function setup_environment() { const the_global_environment = setup_environment(); -/* parse and eval */ +/* Some global variables that help the `parse_and_eval` function */ function no_current_problem() { display("// There is no current problem"); } let try_again = no_current_problem; -let final_result = null; // stores the final result of the program, simplifying testing. +let final_result = null; // stores the final result of the program, useful for testing. + function parse_and_eval(input) { const program_block = make_block(parse(input)); ambeval(program_block, @@ -864,9 +868,6 @@ function parse_and_eval(input) { }); } - -/* THE READ-EVAL-PRINT LOOP */ - // The function user_print is necessary to // avoid infinite recursion when printing // circular environments @@ -881,6 +882,8 @@ function user_print(object) { const input_prompt = "input:"; const output_prompt = "result:"; + +/* THE READ-EVAL-PRINT LOOP */ function driver_loop() { function internal_loop(try_again) { const input = prompt(input_prompt); @@ -911,4 +914,4 @@ function driver_loop() { }); } -//driver_loop(); +//driver_loop(); \ No newline at end of file