# functions from SICP JS 4.1.1
# with modifications for lazy evaluation
# according to SICP JS 4.2.2
def evaluate(component, env):
    return literal_value(component) if is_literal(component) else lookup_symbol_value(symbol_of_name(component), env) if is_name(component) else apply(actual_value(function_expression(component), env), arg_expressions(component), env) if is_application(component) else evaluate(operator_combination_to_application(component), env) if is_operator_combination(component) else eval_conditional(component, env) if is_conditional(component) else make_function(lambda_parameter_symbols(component), lambda_body(component), env) if is_lambda_expression(component) else eval_sequence(sequence_statements(component), env) if is_sequence(component) else eval_block(component, env) if is_block(component) else eval_return_statement(component, env) if is_return_statement(component) else evaluate(function_decl_to_constant_decl(component), env) if is_function_definition(component) else eval_declaration(component, env) if is_declaration(component) else eval_assignment(component, env) if is_assignment(component) else error("unknown syntax -- evaluate", component)
def actual_value(exp, env):
    return force_it(evaluate(exp, env))
def map(fun, items):
    return (None if is_none(items)
            else pair(fun(head(items)),
                      map(fun, tail(items))))
def list_of_arg_values(exps, env):
    return map(lambda exp: (actual_value(exp, env)), exps)
def list_of_delayed_args(exps, env):
    return map(lambda exp: (delay_it(exp, env)), exps)
def apply(fun, args, env):
    if is_primitive_function(fun):
        return apply_primitive_function(fun, list_of_arg_values(args, env))
    elif is_compound_function(fun):
        result = evaluate(function_body(fun), extend_environment(function_parameters(fun), list_of_delayed_args(args, env), function_environment(fun)))
        return return_value_content(result) if is_return_value(result) else None
    else:
        error("unknown function type -- apply", fun)
def eval_conditional(component, env):
    return evaluate(conditional_consequent(component), env) if is_truthy(actual_value(conditional_predicate(component), env)) else 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
def is_evaluated_thunk(obj):
    return is_tagged_list(obj, "evaluated_thunk")
def thunk_value(evaluated_thunk):
    return head(tail(evaluated_thunk))

def force_it(obj):
    if is_thunk(obj):
        result = actual_value(thunk_exp(obj), thunk_env(obj))
        set_head(obj, "evaluated_thunk")
        set_head(tail(obj), result)
        set_tail(tail(obj), None)
        return result
    elif is_evaluated_thunk(obj):
        return thunk_value(obj)
    else:
        return obj
def delay_it(exp, env):
    return llist("thunk", exp, env)
def is_thunk(obj):
    return is_tagged_list(obj, "thunk")
def thunk_exp(thunk):
    return head(tail(thunk))

def thunk_env(thunk):
    return head(tail(tail(thunk)))

