Note
This is the Metacircular Evaluator, different from the CSE Machine, (kinda corresponds to substitution model)
Steps
- Parsing
- Lexical analysis: Split the input program (string) into tokens (using regex)
- Parsing: Check that the tokens form allowable components, produce a syntax tree
Implementation
Syntax tree
The syntax tree is made out of tagged lists. A tagged list is a list where the first element is the tag (describes what the list is), and the remaining elements are the branches of the tree. The remaining elements themselves should be tagged lists.
parse("2 + 3;"); // will result in:
list("binary_operator_combination", "+",
list("literal", 2),
list("literal", 3));Using the syntax tree
This is part of the CSE Machine code (simplified), it handles expressions:
// note: comp stands for component
// syntax predicates
function is_literal(comp) {
return is_tagged_list(comp, "literal");
}
function is_tagged_list(comp, the_tag) {
return is_pair(comp) && head(comp) === the_tag;
}
// selectors
function literal_value(comp) {
return head(tail(comp));
}
// evaluate
function evaluate(expr, env) {
return is_literal(expr)
? literal_value(expr)
: is_operator_combination(expr)
? apply(op_comb_operator_symbol(expr), // operator
list_of_values( // evaluate the operand expessions
list(op_comb_first_operand(expr),
op_comb_second_operand(expr))))
: is_name(expr)
? lookup_symbol_value(symbol_of_name(comp), env)
// ...
: error(expr, "Unknown expression:");
}
function list_of_values(exprs) {
return map(evaluate, exprs);
}
function apply(operator, operands) {
const first_op = head(operands);
const second_op = head(tail(operands));
return operator === "+"
? first_op + second_op
: operator === "-"
? first_op - second_op
// ...
: error();
}
// to use:
const syntax_tree = parse("1 + 2 * 3");
evaluate(syntax_tree);Syntax tree nodes contents
Literal
- value
Operator Combinations
- operator
- operand 1 expression
- operand 2 expression
Conditional
- predicate expression
- expression if true
- expression if false
Sequence
- It is a list of expressions
Block
- The sequence that is its body
Declaration
- Symbol (name)
- Expression that the symbol should be assigned to
Compound Function
- Parameters
- Body block
- Env
Frames and environments
function make_frame(names, values) {
// names and values are lists
// value of the ith name is stored at the ith position in values
return pair(names, values);
}
function extend_environment(ns, vs, e) {
return pair(make_frame(ns, vs), e);
// can be seen that an environment is a list
}
const the_empty_environment = null;
const the_global_environment = the_empty_environment;
// functions that can be used to implement symbol(name) lookup
function first_frame(env) {
return head(env);
}
function enclosing_environment(env) {
return tail(env);
}
function is_empty_environment(env) {
return is_null(env);
// ^ since environments cannot be empty,
// this will be the outermost enclosing environment
}
function lookup_symbol_value(name, env) {
// recursively search for the symbol ...
}Evaluating Blocks
{ const x = y + 7; x * 2; } // example block
function eval_block(component, env) {
const body = block_body(component);
// ^ selector, will result in a sequence
const locals = scan_ount_declarations(body);
// ^ look through the sequence for name declarations
const unassigneds = list_of_unassigned(locals);
// ^ create the values list for the names
// all items have the value "*unassigned*"
return evaluate(body,
extend_environment(locals, unassigneds, env));
}Evaluating Declarations
function eval_declaration(comp, env) {
assign_symbol_value(
declaration_symbol(comp),
evaluate(declaration_value_expression(comp), env),
env);
// ^ this looks through the current frame for the symbol(name)
// then, it evalues the value expression and assigns it
return undefined;
}Functions
function function_decl_to_constant_decl(comp) {
// ...
} // this takes a "function aaa(x) {...}" and converts it to
// "const aaa = (x) => {...}"
function evaluate(comp, env) {
return // ...
: is_lambda_expression(comp)
? make_function(//...)
: is_function_declaration(comp)
? evaluate(function_decl_to_constant_decl(comp), env)
: is_application(comp)
? apply(evaluate(function_expression(comp), env),
list_of_values(arg_expressions(comp), env))
// ...
: error();
}
function apply(fun, args) {
return is_primitive_function(fun)
? apply_primitive_function(fun, args)
: is_compount_function(fun)
? evaluate(function_body(fun),
extend_environment(function_parameters(fun),
args,
function_environment(fun)))
: error();
}Declaring functions are handled by the evaluate function like this:
- Function declarations are converted to constant declarations, where the value is a lambda expression
- Lambda expressions are converted to function (objects)