Skip to content

Commit

Permalink
Improve documentation (#11)
Browse files Browse the repository at this point in the history
Let's update the documentation for this evaluator

- Add explanatory comments within evaluator.js

- Improve examples in README, such that they can
  be directly copied to playground
  • Loading branch information
anubh-v authored Feb 11, 2020
1 parent c184e95 commit 32cbda2
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 65 deletions.
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,46 @@ 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
```
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
Expand Down
113 changes: 58 additions & 55 deletions evaluator.js
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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);
}
Expand All @@ -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)) &&
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -911,4 +914,4 @@ function driver_loop() {
});
}

//driver_loop();
//driver_loop();

0 comments on commit 32cbda2

Please sign in to comment.