# 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()

plus = llist("primitive", lambda x, y: x + y)
apply(plus, llist(1, 2))
