# functions from SICP Python 4.1.1
def evaluate(component, env):
  if is_literal(component):
    return literal_value(component)
  elif is_name(component):
    return lookup_symbol_value(symbol_of_name(component), env)
  elif is_application(component):
    return apply(evaluate(function_expression(component), env),
                          llist_of_values(arg_expressions(component),
                                          env))
  elif is_operator_combination(component):
    return evaluate(operator_combination_to_application(component),
                    env)
  elif is_conditional(component):
    return eval_conditional(component, env)
  elif is_lambda_expression(component):
    return make_function(lambda_parameter_symbols(component),
                         make_return_statement(lambda_body(component)),
                         env)
  elif is_sequence(component):
    return eval_sequence(sequence_statements(component), env)
  elif is_return_statement(component):
    return eval_return_statement(component, env)
  elif is_function_definition(component):
    return evaluate(function_def_to_assignment(component), env)
  elif is_assignment(component):
    return eval_assignment(component, env)
  else:	return error("unknown syntax -- evaluate", component)
def apply(fun, arguments):
    if is_primitive_function(fun):
        return apply_primitive_function(fun, arguments)
    elif is_compound_function(fun):
        body = function_body(fun)
        locals = local_variables(body)
	unassigneds = llist_of_unassigned(locals)
        result = evaluate(body,
                          extend_environment(
                              append(function_parameters(fun),
			             locals),
                              append(arguments,
			             unassigneds),
                              function_environment(fun)))
        return (return_value_content(result)
                if is_return_value(result)
                else None)
    else:
        return error("unknown function type -- apply", fun)
def map(fun, items):
    return (None if is_none(items)
            else pair(fun(head(items)),
                      map(fun, tail(items))))
def llist_of_values(exps, env):
    return map(lambda arg: evaluate(arg, env), exps)
def eval_conditional(component, env):
    if is_truthy(evaluate(conditional_predicate(component), env)):
        return (evaluate(conditional_consequent(component), env)            
    else: return evaluate(conditional_alternative(component), env))
def eval_sequence(stmts, env):
    for_each(lambda stmt: evaluate(stmt, env), stmts)	    
    return None
def eval_return_statement(component, env):
    return make_return_value(evaluate(return_expression(component),
                                      env))
def eval_assignment(component, env):
    value = evaluate(assignment_value_expression(component), env)
    assign_symbol_value(assignment_symbol(component), value, env)
    return value
def eval_declaration(component, env):
    assign_symbol_value(
        declaration_symbol(component),
        evaluate(declaration_value_expression(component), env),
        env)
    return None

# functions from SICP JS 4.1.2
def is_tagged_list(component, the_tag):
    return is_pair(component) and head(component) == the_tag
def is_literal(component):
    return is_tagged_list(component, "literal")
def literal_value(component):
    return head(tail(component))
def make_literal(value):
    return llist("literal", value)
def is_name(component):
    return is_tagged_list(component, "name")
def make_name(symbol):
    return llist("name", symbol)
def symbol_of_name(component):
    return head(tail(component))
def is_assignment(component):
    return is_tagged_list(component, "assignment")
def assignment_symbol(component):
    return head(tail(head(tail(component))))
def assignment_value_expression(component):
    return head(tail(tail(component)))
def is_declaration(component):
    return is_tagged_list(component, "constant_declaration") or is_tagged_list(component, "variable_declaration") or is_tagged_list(component, "function_definition")
def is_lambda_expression(component):
    return is_tagged_list(component, "lambda_expression")
def lambda_parameter_symbols(component):
    return map(symbol_of_name, head(tail(component)))
def lambda_body(component):
    return head(tail(tail(component)))
def make_lambda_expression(parameters, body):
    return llist("lambda_expression", parameters, body)
def ref(items, n):
    return (head(items) if n == 0
            else ref(tail(items), n - 1))
def is_function_definition(component):
    return is_tagged_list(component, "function_definition")
def function_definition_name(component):
    return ref(component, 1)
def function_definition_parameters(component):
    return ref(component, 2)
def function_definition_body(component):
    return ref(component, 3)
def function_decl_to_constant_decl(component):
    return make_constant_declaration( function_definition_name(component), make_lambda_expression( function_definition_parameters(component), function_definition_body(component)))
def is_return_statement(component):
    return is_tagged_list(component, "return_statement")
def return_expression(component):
    return head(tail(component))
def is_conditional(component):
    return is_tagged_list(component, "conditional_expression") or is_tagged_list(component, "conditional_statement")
def conditional_predicate(component):
    return ref(component, 1)
def conditional_consequent(component):
    return ref(component, 2)
def conditional_alternative(component):
    return ref(component, 3)
def is_sequence(stmt):
    return is_tagged_list(stmt, "sequence")
def sequence_statements(stmt):
    return head(tail(stmt))
def first_statement(stmts):
    return head(stmts)
def rest_statements(stmts):
    return tail(stmts)
def is_empty_sequence(stmts):
    return is_none(stmts)
def is_last_statement(stmts):
    return is_none(tail(stmts))
def is_block(component):
    return is_tagged_list(component, "block")
def block_body(component):
    return head(tail(component))
def make_block(statement):
    return llist("block", statement)
def is_operator_combination(component):
    return is_unary_operator_combination(component) or is_binary_operator_combination(component)
def is_unary_operator_combination(component):
    return is_tagged_list(component, "unary_operator_combination")
def is_binary_operator_combination(component):
    return is_tagged_list(component, "binary_operator_combination")
def operator_symbol(component):
    return ref(component, 1)
def first_operand(component):
    return ref(component, 2)
def second_operand(component):
    return ref(component, 3)
def make_application(function_expression, argument_expressions):
    return llist("application", function_expression, argument_expressions)
def operator_combination_to_application(component):
    operator = operator_symbol(component)
    return make_application(make_name(operator), llist(first_operand(component))) if is_unary_operator_combination(component) else make_application(make_name(operator), llist(first_operand(component), second_operand(component)))
def is_application(component):
    return is_tagged_list(component, "application")
def function_expression(component):
    return head(tail(component))
def arg_expressions(component):
    return head(tail(tail(component)))

# functions from SICP JS 4.1.3
def is_truthy(x):
    return x if is_boolean(x) else error("boolean expected, received", x)
def is_falsy(x):
    return not is_truthy(x)
def make_function(parameters, body, env):
    return llist("compound_function", parameters, body, env)
def is_compound_function(f):
    return is_tagged_list(f, "compound_function")
def function_parameters(f):
    return ref(f, 1)

def function_body(f):
    return ref(f, 2)

def function_environment(f):
    return ref(f, 3)
def make_return_value(content):
    return llist("return_value", content)
def is_return_value(value):
    return is_tagged_list(value, "return_value")
def return_value_content(value):
    return head(tail(value))
def enclosing_environment(env):
    return tail(env)

def first_frame(env):
    return head(env)

the_empty_environment = None
def make_frame(symbols, values):
    return pair(symbols, values)

def frame_symbols(frame):
    return head(frame)

def frame_values(frame):
    return tail(frame)
def length(items):
    return (0 if is_none(items)
            else 1 + length(tail(items)))
def extend_environment(symbols, vals, base_env):
    return pair(make_frame(symbols, vals), base_env) if length(symbols) == length(vals) else error("too many arguments supplied: " + stringify(symbols) + ", " + stringify(vals)) if length(symbols) < length(vals) else error("too few arguments supplied: " + stringify(symbols) + ", " + stringify(vals))
def lookup_symbol_value(symbol, env):
    def env_loop(env):
        def scan(symbols, vals):
            return env_loop(enclosing_environment(env)) if is_none(symbols) else head(vals) if symbol == head(symbols) else scan(tail(symbols), tail(vals))
        if env == the_empty_environment:
            error("unbound name", symbol)
        else:
            frame = first_frame(env)
            return scan(frame_symbols(frame), frame_values(frame))
    return env_loop(env)
def assign_symbol_value(symbol, val, env):
    def env_loop(env):
        def scan(symbols, vals):
            return env_loop(enclosing_environment(env)) if is_none(symbols) else set_head(vals, val) if symbol == head(symbols) else scan(tail(symbols), tail(vals))
        if env == the_empty_environment:
            error("unbound name -- assignment", symbol)
        else:
            frame = first_frame(env)
            return scan(frame_symbols(frame), frame_values(frame))
    return env_loop(env)

# functions from SICP JS 4.1.4
def is_primitive_function(fun):
    return is_tagged_list(fun, "primitive")

def primitive_implementation(fun):
    return head(tail(fun))
primitive_functions = llist(llist("head", head), llist("tail", tail), llist("pair", pair), llist("list", list), llist("is_null", is_none), llist("display", print), llist("error", error), llist("math_abs", abs), llist("+", lambda x, y: (x + y)), llist("-", lambda x, y: (x - y)), llist("-unary", lambda x: (- x)), llist("*", lambda x, y: (x * y)), llist("/", lambda x, y: (x / y)), llist("%", lambda x, y: (x % y)), llist("===", lambda x, y: (x == y)), llist("!==", lambda x, y: (x != y)), llist("<", lambda x, y: (x < y)), llist("<=", lambda x, y: (x <= y)), llist(">", lambda x, y: (x > y)), llist(">=", lambda x, y: (x >= y)), llist("!", lambda x: ( not x)))
primitive_function_symbols = map(head, primitive_functions)
primitive_function_objects = map(lambda fun: (llist("primitive", head(tail(fun)))), primitive_functions)
primitive_constants = llist(llist("undefined", None), llist("Infinity", math_inf), llist("math_PI", math_pi), llist("math_E", math_e), llist("NaN", math_nan))
primitive_constant_symbols = map(lambda c: (head(c)), primitive_constants)
primitive_constant_values = map(lambda c: (head(tail(c))), primitive_constants)
def apply_primitive_function(fun, arglist):
    return apply_in_underlying_javascript(primitive_implementation(fun), arglist)
def append(list1, list2):
    return (list2 if is_none(list1)
            else pair(head(list1), append(tail(list1), list2)))
def setup_environment():
    return extend_environment(append(primitive_function_symbols, primitive_constant_symbols), append(primitive_function_objects, primitive_constant_values), the_empty_environment)
the_global_environment = setup_environment()

def to_string(object):
    return "<compound-function>" if is_compound_function(object) else "<primitive-function>" if is_primitive_function(object) else "[" + to_string(head(object)) + ", " + to_string(tail(object)) + "]" if is_pair(object) else stringify(object)
def user_print(prompt_string, object):
    print(prompt_string + "\n" + to_string(object) + "\n", "----------------------------")
let current_environment = the_global_environment;

function get_current_environment() {
    return current_environment;
}

function set_current_environment(env) {
    current_environment = env;
}
def get_register(machine, reg_name):
    return machine("get_register")(reg_name)
def type(instruction):
    return head(instruction)
def lookup_prim(symbol, operations):
    val = assoc(symbol, operations)
    return (error("unknown operation -- assemble", symbol) if is_undefined(val)
            else head(tail(val)))
def lookup_label(labels, label_name):
    val = assoc(label_name, labels)
    return (error("undefined label -- assemble", label_name) if is_undefined(val)
            else tail(val))
def make_primitive_exp_ef(exp, machine, labels):
    if is_constant_exp(exp):
        c = constant_exp_value(exp)
        return lambda: c
    elif is_label_exp(exp):
        insts = lookup_label(labels, label_exp_label(exp))
        return lambda: insts
    elif is_register_exp(exp):
        r = get_register(machine, register_exp_reg(exp))
        return lambda: get_contents(r)
    else:
        error("unknown expression type -- assemble", exp)
def make_operation_exp_ef(exp, machine, labels, operations):
    op = lookup_prim(operation_exp_op(exp), operations)
    afuns = map(lambda e: make_primitive_exp_ef(e, machine, labels),
                operation_exp_operands(exp))
    return lambda: apply_in_underlying_python(op, map(lambda f: f(), afuns))
def assign(register_name, source):
    return llist("assign", register_name, source)
def assign_reg_name(assign_instruction):
    return head(tail(assign_instruction))
def assign_value_exp(assign_instruction):
    return head(tail(tail(assign_instruction)))
def make_assign_ef(inst, machine, labels, operations, pc):
    target = get_register(machine, assign_reg_name(inst))
    value_exp = assign_value_exp(inst)
    value_fun = (make_operation_exp_ef(value_exp, machine, labels, operations)
                 if is_operation_exp(value_exp)
                 else make_primitive_exp_ef(value_exp, machine, labels))
    def execution_fun():
        set_contents(target, value_fun())
        advance_pc(pc)
    return execution_fun
def advance_pc(pc):
    set_contents(pc, tail(get_contents(pc)))
def make_test_ef(inst, machine, labels, operations, flag, pc):
    condition = test_condition(inst)
    if is_operation_exp(condition):
        condition_fun = make_operation_exp_ef(condition, machine, labels, operations)
        def execution_fun():
            set_contents(flag, condition_fun())
            advance_pc(pc)
        return execution_fun
    else:
        error("bad test instruction -- assemble", inst)
def test(condition):
    return llist("test", condition)

def test_condition(test_instruction):
    return head(tail(test_instruction))
def make_branch_ef(inst, machine, labels, flag, pc):
    dest = branch_dest(inst)
    if is_label_exp(dest):
        insts = lookup_label(labels, label_exp_label(dest))
        def execution_fun():
            if get_contents(flag):
                set_contents(pc, insts)
            else:
                advance_pc(pc)
        return execution_fun
    else:
        error("bad branch instruction -- assemble", inst)
def branch(label):
    return llist("branch", label)

def branch_dest(branch_instruction):
    return head(tail(branch_instruction))
def reg(name):
    return llist("reg", name)

def is_register_exp(exp):
    return is_tagged_list(exp, "reg")

def register_exp_reg(exp):
    return head(tail(exp))
def constant(value):
    return llist("constant", value)

def is_constant_exp(exp):
    return is_tagged_list(exp, "constant")

def constant_exp_value(exp):
    return head(tail(exp))
def label(name):
    return llist("label", name)

def is_label_exp(exp):
    return is_tagged_list(exp, "label")

def label_exp_label(exp):
    return head(tail(exp))
def op(name):
    return llist("op", name)

def is_operation_exp(exp):
    return is_pair(exp) and is_tagged_list(head(exp), "op")

def operation_exp_op(op_exp):
    return head(tail(head(op_exp)))

def operation_exp_operands(op_exp):
    return tail(op_exp)
def make_go_to_ef(inst, machine, labels, pc):
    dest = go_to_dest(inst)
    if is_label_exp(dest):
        insts = lookup_label(labels, label_exp_label(dest))
        return lambda: set_contents(pc, insts)
    elif is_register_exp(dest):
        reg = get_register(machine, register_exp_reg(dest))
        return lambda: set_contents(pc, get_contents(reg))
    else:
        error("bad go_to instruction -- assemble", inst)
def go_to(label):
    return llist("go_to", label)

def go_to_dest(go_to_instruction):
    return head(tail(go_to_instruction))
def pop(stack):
    return stack("pop")
def push(stack, value):
    return stack("push")(value)
def make_save_ef(inst, machine, stack, pc):
    reg = get_register(machine, stack_inst_reg_name(inst))
    def execution_fun():
        push(stack, get_contents(reg))
        advance_pc(pc)
    return execution_fun
def make_restore_ef(inst, machine, stack, pc):
    reg = get_register(machine, stack_inst_reg_name(inst))
    def execution_fun():
        set_contents(reg, pop(stack))
        advance_pc(pc)
    return execution_fun
def save(reg):
    return llist("save", reg)

def restore(reg):
    return llist("restore", reg)

def stack_inst_reg_name(stack_instruction):
    return head(tail(stack_instruction))
def make_perform_ef(inst, machine, labels, operations, pc):
    action = perform_action(inst)
    if is_operation_exp(action):
        action_fun = make_operation_exp_ef(action, machine, labels, operations)
        def execution_fun():
            action_fun()
            advance_pc(pc)
        return execution_fun
    else:
        error("bad perform instruction -- assemble", inst)
def perform(action):
    return llist("perform", action)

def perform_action(perform_instruction):
    return head(tail(perform_instruction))
def make_execution_function(inst, labels, machine,
                            pc, flag, stack, ops):
    inst_type = type(inst)
    return (make_assign_ef(inst, machine, labels, ops, pc) if inst_type == "assign"
            else make_test_ef(inst, machine, labels, ops, flag, pc) if inst_type == "test"
            else make_branch_ef(inst, machine, labels, flag, pc) if inst_type == "branch"
            else make_go_to_ef(inst, machine, labels, pc) if inst_type == "go_to"
            else make_save_ef(inst, machine, stack, pc) if inst_type == "save"
            else make_restore_ef(inst, machine, stack, pc) if inst_type == "restore"
            else make_push_marker_to_stack_ef(machine, stack, pc) if inst_type == "push_marker_to_stack"
            else make_revert_stack_to_marker_ef(machine, stack, pc) if inst_type == "revert_stack_to_marker"
            else make_perform_ef(inst, machine, labels, ops, pc) if inst_type == "perform"
            else error("unknown instruction type -- assemble", inst))
def make_inst(inst_controller_instruction):
    return pair(inst_controller_instruction, None)
def inst_controller_instruction(inst):
    return head(inst)
def inst_execution_fun(inst):
    return tail(inst)
def set_inst_execution_fun(inst, fun):
    set_tail(inst, fun)
def update_insts(insts, labels, machine):
    pc = get_register(machine, "pc")
    flag = get_register(machine, "flag")
    stack = machine("stack")
    ops = machine("operations")
    return for_each(
        lambda inst: set_inst_execution_fun(
            inst,
            make_execution_function(inst_controller_instruction(inst),
                                    labels, machine, pc, flag, stack, ops)),
        insts)
def make_label_entry(label_name, insts):
    return pair(label_name, insts)
def extract_labels(controller, receive):
    def incorporate(insts, labels):
        next_element = head(controller)
        return (receive(insts,
                        pair(make_label_entry(next_element, insts),
                             labels))
                if is_string(next_element)
                else receive(pair(make_inst(next_element), insts),
                             labels))
    return (receive(None, None) if is_null(controller)
            else extract_labels(tail(controller), incorporate))
def assemble(controller, machine):
    def receive(insts, labels):
        update_insts(insts, labels, machine)
        return insts
    return extract_labels(controller, receive)
def make_stack():
    stack = None
    frame = None
    def push_marker():
        nonlocal frame
        frame = pair(stack, frame)
        return "done"
    def pop_marker():
        nonlocal stack, frame
        stack = head(frame)
        frame = tail(frame)
        return "done"
    def push(x):
        nonlocal stack
        stack = pair(x, stack)
        return "done"
    def pop():
        nonlocal stack
        if is_null(stack):
            error("empty stack -- pop")
        else:
            top = head(stack)
            stack = tail(stack)
            return top
    def initialize():
        nonlocal stack
        stack = None
        return "done"
    def dispatch(message):
        return (push if message == "push"
                else pop() if message == "pop"
                else push_marker() if message == "push_marker"
                else pop_marker() if message == "pop_marker"
                else initialize() if message == "initialize"
                else error("unknown request -- stack", message))
    return dispatch
def make_push_marker_to_stack_ef(machine, stack, pc):
    def execution_fun():
        push_marker(stack)
        advance_pc(pc)
    return execution_fun
def make_revert_stack_to_marker_ef(machine, stack, pc):
    def execution_fun():
        pop_marker(stack)
        advance_pc(pc)
    return execution_fun
def push_marker_to_stack():
    return llist("push_marker_to_stack")
def revert_stack_to_marker():
    return llist("revert_stack_to_marker")
def pop_marker(stack):
    return stack("pop_marker")
def push_marker(stack):
    return stack("push_marker")
def make_register(name):
    contents = "*unassigned*"
    def dispatch(message):
        def set_value(value):
            nonlocal contents
            contents = value
        return (contents if message == "get"
                else set_value if message == "set"
                else error("unknown request -- make_register", message))
    return dispatch
def lookup(key, table):
    record = assoc(key, tail(table))
    return (None
            if is_none(record)
            else tail(record))

def assoc(key, records):
    return (None
            if is_none(records)
            else head(records)
            if key == head(head(records))
            else assoc(key, tail(records)))
def get_contents(register):
    return register("get")
def set_contents(register, value):
    return register("set")(value)
def make_new_machine():
    pc = make_register("pc")
    flag = make_register("flag")
    stack = make_stack()
    the_instruction_sequence = None
    the_ops = llist(llist("initialize_stack", lambda: stack("initialize")))
    register_table = llist(llist("pc", pc), llist("flag", flag))
    def allocate_register(name):
        nonlocal register_table
        if is_undefined(assoc(name, register_table)):
            register_table = pair(llist(name, make_register(name)),
                                  register_table)
        else:
            error("multiply defined register", name)
        return "register allocated"
    def lookup_register(name):
        val = assoc(name, register_table)
        return (error("unknown register", name) if is_undefined(val)
                else head(tail(val)))
    def execute():
        insts = get_contents(pc)
        if is_null(insts):
            return "done"
        else:
            inst_execution_fun(head(insts))()
            return execute()
    def dispatch(message):
        def start():
            set_contents(pc, the_instruction_sequence)
            return execute()
        def install_instruction_sequence(seq):
            nonlocal the_instruction_sequence
            the_instruction_sequence = seq
        def install_operations(ops):
            nonlocal the_ops
            the_ops = append(the_ops, ops)
        return (start() if message == "start"
                else install_instruction_sequence
                     if message == "install_instruction_sequence"
                else allocate_register if message == "allocate_register"
                else lookup_register if message == "get_register"
                else install_operations if message == "install_operations"
                else stack if message == "stack"
                else the_ops if message == "operations"
                else error("unknown request -- machine", message))
    return dispatch
def make_machine(register_names, ops, controller):
    machine = make_new_machine()
    for_each(lambda register_name: machine("allocate_register")(register_name),
             register_names)
    machine("install_operations")(ops)
    machine("install_instruction_sequence")(assemble(controller, machine))
    return machine
def start(machine):
    return machine("start")
def get_register_contents(machine, register_name):
    return get_contents(get_register(machine, register_name))
def set_register_contents(machine, register_name, value):
    set_contents(get_register(machine, register_name), value)
    return "done"
def user_read(prompt_string):
    return prompt(prompt_string)
function empty_arglist() { return null; }

function adjoin_arg(arg, arglist) {
    return append(arglist, list(arg));
}
function is_last_argument_expression(arg_expression) {
    return is_null(tail(arg_expression));
}
const eceval_operations = 
    list(
      // args
      list("arg_expressions"      , arg_expressions),
      list("function_expression"  , function_expression),
      list("is_null"
                                  , is_null),
      list("head"        , head),
      list("is_last_argument_expression", is_last_argument_expression),
      list("tail"        , tail),

      //arg
      list("empty_arglist"        , empty_arglist),
      list("adjoin_arg"           , adjoin_arg),

      // comp (sequence)
      list("first_statement"     , first_statement),
      list("rest_statements"     , rest_statements),
      list("is_last_statement"   , is_last_statement),
      list("is_empty_sequence"   , is_empty_sequence),
      list("sequence_statements" , sequence_statements),

      // eval functions from meta-circular evaluator
      list("is_literal"  , is_literal),
      list("literal_value"       , literal_value),
      list("is_name"             , is_name),
      list("symbol_of_name"      , symbol_of_name),
      list("is_assignment"       , is_assignment),
      list("assignment_symbol"   , assignment_symbol),
      list("assignment_value_expression"
                                 , assignment_value_expression),
      list("assign_symbol_value" , assign_symbol_value),
      list("is_declaration"      , is_declaration),
      list("declaration_symbol"  , declaration_symbol),
      list("declaration_value_expression"
                                 , declaration_value_expression),
      list("assign_symbol_value" , assign_symbol_value),
      list("is_lambda_expression", is_lambda_expression),
      list("lambda_parameter_symbols"
                                 , lambda_parameter_symbols),
      list("lambda_body"         , lambda_body),
      list("is_return_statement" , is_return_statement),
      list("return_expression"   , return_expression),
      list("is_conditional"
                                 , is_conditional),
      list("conditional_predicate"
				 , conditional_predicate),
      list("conditional_consequent"
                                 , conditional_consequent),
      list("conditional_alternative"
                                 , conditional_alternative),
      list("is_sequence"         , is_sequence),
      list("is_block"            , is_block),
      list("block_body"          , block_body),
      list("scan_out_declarations"
                                 , scan_out_declarations),
      list("list_of_unassigned"  , list_of_unassigned),
      list("is_application"      , is_application),
      list("is_primitive_function"
                                 , is_primitive_function),
      list("apply_primitive_function"
                                 , apply_primitive_function),
      list("is_compound_function", is_compound_function),
      list("function_parameters" , function_parameters),
      list("function_environment", function_environment),
      list("function_body"       , function_body),
      list("extend_environment"  , extend_environment),
      list("make_function"       , make_function),

      list("lookup_symbol_value" , lookup_symbol_value),

      list("get_current_environment"
                                 , get_current_environment),
      list("set_current_environment"
                                 , set_current_environment),

      // Unsorted
      list("is_function_definition"  , is_function_definition),
      list("function_definition_body"  , function_definition_body),
      list("function_definition_parameters"  , function_definition_parameters),
      list("function_definition_name"  , function_definition_name),
      list("function_decl_to_constant_decl", function_decl_to_constant_decl),
      list("declaration_symbol"  , declaration_symbol),
      list("is_operator_combination", is_operator_combination),
      list("operator_combination_to_application", operator_combination_to_application),
      list("parse", parse),

      // generic helpers
      list("is_truthy", is_truthy),
      list("is_falsy", v => !is_truthy(v)),
      list("is_null", is_null),

      list("user_read", user_read),
      list("user_print", user_print),
      list("display", display),
      list("list", list)
    );
const eceval_controller =	
    list(
      "read_evaluate_print_loop",
      perform(list(op("initialize_stack"))),
      assign("comp", list(op("user_read"), constant("EC-evaluate input:"))),
      test(list(op("is_null"), reg("comp"))),
      branch(label("evaluator_done")),
      assign("comp", list(op("parse"), reg("comp"))),
      assign("env", list(op("get_current_environment"))),
      assign("val", list(op("scan_out_declarations"), reg("comp"))),
      save("comp"), // temporarily store to comp
      assign("comp", list(op("list_of_unassigned"), reg("val"))),
      assign("env", list(op("extend_environment"), 
                         reg("val"), reg("comp"), reg("env"))),
      perform(list(op("set_current_environment"), reg("env"))),
      restore("comp"),
      assign("continue", label("print_result")),
      go_to(label("eval_dispatch")),

      "print_result",
      perform(list(op("user_print"),
                   constant("EC-evaluate value:"), reg("val"))),
      go_to(label("read_evaluate_print_loop")), 

      "eval_dispatch",
      test(list(op("is_literal"), reg("comp"))),
      branch(label("ev_literal")),
      test(list(op("is_name"), reg("comp"))),
      branch(label("ev_name")),
      // Unsorted
      test(list(op("is_operator_combination"), reg("comp"))),
      branch(label("ev_operator_combination")),
      test(list(op("is_function_definition"), reg("comp"))),
      branch(label("ev_function_definition")),
      test(list(op("is_operator_combination"), reg("comp"))),
      branch(label("ev_operator_combination")),
      // Treat let/const the same
      test(list(op("is_declaration"), reg("comp"))),
      branch(label("ev_declaration")),
      test(list(op("is_assignment"), reg("comp"))),
      branch(label("ev_assignment")),
      test(list(op("is_return_statement"), reg("comp"))),
      branch(label("ev_return")),
      test(list(op("is_conditional"), reg("comp"))),
      branch(label("ev_conditional")),
      test(list(op("is_lambda_expression"), reg("comp"))),
      branch(label("ev_lambda")),
      test(list(op("is_sequence"), reg("comp"))),
      branch(label("ev_sequence_start")),
      test(list(op("is_block"), reg("comp"))),
      branch(label("ev_block")),
      test(list(op("is_application"), reg("comp"))),
      branch(label("ev_application")),
      go_to(label("unknown_component_type")),

      "ev_return",
      revert_stack_to_marker(),
      restore("continue"),
      assign("comp", list(op("return_expression"), reg("comp"))),
      go_to(label("eval_dispatch")),

      "ev_literal",
      assign("val", list(op("literal_value"), reg("comp"))),
      go_to(reg("continue")),
      
      "ev_name",
      assign("val", list(op("symbol_of_name"), reg("comp"))),
      assign("val", list(op("lookup_symbol_value"), reg("val"), reg("env"))),
      go_to(reg("continue")),
      
      "ev_lambda",
      assign("unev", list(op("lambda_parameter_symbols"), reg("comp"))),
      assign("comp", list(op("lambda_body"), reg("comp"))),
      assign("val", list(op("make_function"),
                         reg("unev"), reg("comp"), reg("env"))),
      go_to(reg("continue")),

      "ev_operator_combination",
      assign("comp", list(op("operator_combination_to_application"),
                          reg("comp"))),
      
      "ev_application",
      save("continue"),
      save("env"),
      assign("unev", list(op("arg_expressions"), reg("comp"))),
      save("unev"),
      assign("comp", list(op("function_expression"), reg("comp"))),
      assign("continue", label("ev_appl_did_function_expression")),
      go_to(label("eval_dispatch")),
      
      "ev_appl_did_function_expression",
      restore("unev"), // the args
      restore("env"),
      assign("argl", list(op("empty_arglist"))),
      assign("fun", reg("val")), // the function_expression
      test(list(op("is_null"),
                reg("unev"))),
      branch(label("apply_dispatch")),
      save("fun"),
      
      "ev_appl_argument_expression_loop",
      save("argl"),
      assign("comp", list(op("head"), reg("unev"))),
      test(list(op("is_last_argument_expression"),
                reg("unev"))),
      branch(label("ev_appl_last_arg")),
      save("env"),
      save("unev"),
      assign("continue", label("ev_appl_accumulate_arg")),
      go_to(label("eval_dispatch")),
      
      "ev_appl_accumulate_arg",
      restore("unev"),
      restore("env"),
      restore("argl"),
      assign("argl", list(op("adjoin_arg"),
                          reg("val"), reg("argl"))),
      assign("unev", list(op("tail"), reg("unev"))),
      go_to(label("ev_appl_argument_expression_loop")),
      
      "ev_appl_last_arg",
      assign("continue", label("ev_appl_accum_last_arg")),
      go_to(label("eval_dispatch")),
      
      "ev_appl_accum_last_arg",
      restore("argl"),
      assign("argl", list(op("adjoin_arg"),
                          reg("val"), reg("argl"))),
      restore("fun"),
      go_to(label("apply_dispatch")),
      
      "apply_dispatch",
      test(list(op("is_primitive_function"),
                reg("fun"))),
      branch(label("primitive_apply")),
      test(list(op("is_compound_function"),
                reg("fun"))),
      branch(label("compound_apply")),
      go_to(label("unknown_function_type")),

      "primitive_apply",
      assign("val", list(op("apply_primitive_function"), 
                         reg("fun"), 
                         reg("argl"))),
      restore("continue"),
      go_to(reg("continue")),

      "compound_apply",
      assign("unev", list(op("function_parameters"), reg("fun"))),
      assign("env", list(op("function_environment"), reg("fun"))),
      assign("env", list(op("extend_environment"), 
                         reg("unev"), reg("argl"), reg("env"))),
      assign("comp", list(op("function_body"), reg("fun"))),
      push_marker_to_stack(),
      assign("continue", label("return_undefined")),
      go_to(label("eval_dispatch")),

      "return_undefined",
      assign("val", constant(undefined)),
      revert_stack_to_marker(),
      restore("continue"),
      go_to(reg("continue")),

      "ev_block",
      assign("comp", list(op("block_body"), reg("comp"))),
      assign("val", list(op("scan_out_declarations"), reg("comp"))),

      save("comp"), // temporarily store to comp
      assign("comp", list(op("list_of_unassigned"), reg("val"))),
      assign("env", list(op("extend_environment"), 
                         reg("val"), 
                         reg("comp"), 
                         reg("env"))),
      restore("comp"),
      go_to(label("eval_dispatch")),

"ev_sequence_start",
      assign("unev", list(op("sequence_statements"), reg("comp"))),
      test(list(op("is_empty_sequence"), reg("unev"))),
      branch(label("ev_empty_sequence")),
      save("continue"),

"ev_sequence",
      assign("comp", list(op("first_statement"), reg("unev"))),
      test(list(op("is_last_statement"), reg("unev"))),
      branch(label("ev_sequence_last_statement")),
      save("unev"),
      save("env"),
      assign("continue", label("ev_sequence_continue")),
      go_to(label("eval_dispatch")),
      
"ev_sequence_continue",
      restore("env"),
      restore("unev"),
      assign("unev", list(op("rest_statements"), reg("unev"))),
      go_to(label("ev_sequence")),
 
"ev_sequence_last_statement",
      restore("continue"),
      go_to(label("eval_dispatch")),

"ev_sequence_empty",
      assign("val", constant(undefined)),
      go_to(reg("continue")), 

      "ev_conditional",
      save("comp"), // save expression for later
      save("env"),
      save("continue"),
      assign("continue", label("ev_conditional_decide")),
      assign("comp", list(op("conditional_predicate"), reg("comp"))),
      go_to(label("eval_dispatch")), // evaluate the predicate
      
      "ev_conditional_decide",
      restore("continue"),
      restore("env"),
      restore("comp"),
      test(list(op("is_falsy"), reg("val"))),
      branch(label("ev_conditional_alternative")),
      
      "ev_conditional_consequent",
      assign("comp", list(op("conditional_consequent"), reg("comp"))),
      go_to(label("eval_dispatch")),
      
      "ev_conditional_alternative",
      assign("comp", list(op("conditional_alternative"), reg("comp"))),
      go_to(label("eval_dispatch")),
      
      "ev_assignment",
      assign("unev", list(op("assignment_symbol"), reg("comp"))),
      save("unev"), // save variable for later
      assign("comp", list(op("assignment_value_expression"), reg("comp"))),
      save("env"),
      save("continue"),
      assign("continue", label("ev_assignment_1")),
      go_to(label("eval_dispatch")), // evaluate assignment value
      
      "ev_assignment_1",
      restore("continue"),
      restore("env"),
      restore("unev"),
      perform(list(op("assign_symbol_value"),
                   reg("unev"), reg("val"), reg("env"))),
      go_to(reg("continue")),
      
      "ev_function_definition",
      assign("comp", list(op("function_decl_to_constant_decl"), reg("comp"))),
      
      "ev_declaration",
      assign("unev", list(op("declaration_symbol"),
                          reg("comp"))),
      save("unev"), // save variable for later
      assign("comp", list(op("declaration_value_expression"),
                          reg("comp"))),
      save("env"),
      save("continue"),
      assign("continue", label("ev_declaration_assign")),
      go_to(label("eval_dispatch")), // evaluate declaration value
      
      "ev_declaration_assign",
      restore("continue"),
      restore("env"),
      restore("unev"),
      perform(list(op("assign_symbol_value"),
                   reg("unev"), reg("val"), reg("env"))),
      assign("val", constant(undefined)),
      go_to(reg("continue")),
      
      // Error handling
      "unknown_component_type",
      assign("val", constant("Unknown expression type")),
      go_to(label("signal_error")),
      
      "unknown_function_type",
      restore("continue"), /// clean up stack (from apply_dispatch)
      assign("val", constant("Unknown function type")),
      go_to(label("signal_error")),
      
      "signal_error",
      perform(list(op("user_print"),
                   constant("EC-evaluator error:"), reg("comp"))),
      go_to(label("read_evaluate_print_loop")),
      
      "evaluator_done"
     );
const eceval =
    make_machine(list("comp", "env", "val", "fun",
                      "argl", "continue", "unev"),
                 eceval_operations,
                 eceval_controller);
const test_program_1 = "                       \
function test1() {                             \
    display('A 1');                            \
    const result = test2();                    \
    return display(stringify(result) + ' 3');  \
    display('I should not be printed 7');      \
}                                              \
function test2() {                             \
    display('B 2');                            \
    return 'C';                                \
    display('I should not be printed 4');      \
}                                              \
function test3() {                             \
    display('Hello from test3');               \
}                                              \
function test4() {                             \
    return display('Hello from test4');        \
}                                              \
display('5 ' + stringify(test1()));                           \
display('6 ' + stringify(test3())); // Should print undefined \
display('8 ' + stringify(test4()));                           \
";

const test_program_2 = "           \
function adder(a, b) {             \
    return                         \
        a === 0                    \
      ? b                          \
      : adder(a - 1, b + 1);       \
 42;                               \
}                                  \
display(\"7 + 5 = \" + stringify(adder(7, 5)); \
";

start(eceval);
get_register_contents(eceval, "val");
