Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve documentation #11

Merged
merged 4 commits into from
Feb 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();