# functions from SICP Python 4.1.1
def map(fun, items):
    return (None if is_none(items)
            else pair(fun(head(items)),
                      map(fun, tail(items))))
def llist_of_unassigned(symbols):
    return map(lambda symbol: "*unassigned*", symbols)
# 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()

# functions from SICP JS 4.1.7
def analyze_literal(component):
    return lambda env: (literal_value(component))
def analyze_name(component):
    return lambda env: (lookup_symbol_value(symbol_of_name(component), env))
def analyze_assignment(component):
    symbol = assignment_symbol(component)
    vfun = analyze(assignment_value_expression(component))
    def the_assignment(env):
        value = vfun(env)
        assign_symbol_value(symbol, value, env)
        return value
    return the_assignment

def analyze_declaration(component):
    symbol = declaration_symbol(component)
    vfun = analyze(declaration_value_expression(component))
    def the_declaration(env):
        assign_symbol_value(symbol, vfun(env), env)
        return None
    return the_declaration
def analyze_conditional(component):
    pfun = analyze(conditional_predicate(component))
    cfun = analyze(conditional_consequent(component))
    afun = analyze(conditional_alternative(component))
    return lambda env: (cfun(env) if is_truthy(pfun(env)) else afun(env))
def accumulate(op, initial, sequence):
    return (initial if is_none(sequence)
            else op(head(sequence),
                    accumulate(op, initial, tail(sequence))))
def scan_out_bindings(component):
    if is_sequence(component):
        return accumulate(append,
                          None,
                          map(scan_out_bindings,
                              sequence_statements(component)))
    elif is_conditional(component):
        return append(scan_out_bindings(conditional_consequent),
                      scan_out_bindings(conditional_alternative)),
    elif is_assignment(component):
        return llist(assignment_symbol(component))
    elif is_assignment(component):
        return llist(assignment_symbol(component))
    else None
def analyze_lambda_expression(component):
    params = lambda_parameter_symbols(component)
    bfun = analyze(lambda_body(component))
    return lambda env: (make_function(params, bfun, env))
def analyze_sequence(stmts):
    def sequentially(fun1, fun2):
        def the_sequence(env):
            fun1_val = fun1(env)
            return (fun1_val
                    if is_return_value(fun1_val)
                    else fun2(env))
        return the_sequence
    def loop(first_fun, rest_funs):
        return (first_fun
                if is_none(rest_funs)
                else loop(sequentially(first_fun, head(rest_funs)),
                          tail(rest_funs)))
    funs = map(analyze, stmts)
    return ((lambda env: None)
            if is_none(funs)
            else loop(head(funs), tail(funs)))
def analyze_block(component):
    body = block_body(component)
    bfun = analyze(body)
    locals = scan_out_declarations(body)
    unassigneds = list_of_unassigned(locals)
    return lambda env: (bfun(extend_environment(locals, unassigneds, env)))
def analyze_return_statement(component):
    rfun = analyze(return_expression(component))
    return lambda env: (make_return_value(rfun(env)))
def analyze_application(component):
    ffun = analyze(function_expression(component))
    afuns = map(analyze, arg_expressions(component))
    return lambda env: (execute_application(ffun(env), map(lambda afun: (afun(env)), afuns)))
def execute_application(fun, args):
    if is_primitive_function(fun):
        return apply_primitive_function(fun, args)
    elif is_compound_function(fun):
        result = function_body(fun) (extend_environment(function_parameters(fun), args, function_environment(fun)))
        return return_value_content(result) if is_return_value(result) else None
    else:
        error("unknown function type -- execute_application", fun)
def analyze(component):
    return analyze_literal(component) if is_literal(component) else analyze_name(component) if is_name(component) else analyze_application(component) if is_application(component) else analyze(operator_combination_to_application(component)) if is_operator_combination(component) else analyze_conditional(component) if is_conditional(component) else analyze_lambda_expression(component) if is_lambda_expression(component) else analyze_sequence(sequence_statements(component)) if is_sequence(component) else analyze_block(component) if is_block(component) else analyze_return_statement(component) if is_return_statement(component) else analyze(function_decl_to_constant_decl(component)) if is_function_definition(component) else analyze_declaration(component) if is_declaration(component) else analyze_assignment(component) if is_assignment(component) else error("unknown syntax -- analyze", component)
def evaluate(component, env):
    return analyze(component)(env)
def parse_and_evaluate(input):
    program = parse(input)
    locals = scan_out_declarations(program)
    unassigneds = list_of_unassigned(locals)
    program_env = extend_environment(locals, unassigneds, the_global_environment)
    return evaluate(program, program_env)
parse_and_evaluate("               \
function factorial(n) {            \
    return n === 1                 \
           ? 1                     \
           : n * factorial(n - 1); \
}                                  \
factorial(5);                      ")

# expected: 120
