def EVAL(ast, env):
    while True:
        if not isinstance(ast, mal_types.list_types):
            return eval_ast(ast, env)
        elif not ast:
            return ast
        elif isinstance(ast, mal_types.list_types):
            if len(ast) == 0:
                return ast
            if isinstance(ast[0], mal_types.MalSymbol):
                if ast[0].data == 'def!':
                    value = EVAL(ast[2], env)
                    env.set(ast[1].data, value)
                    return value

                elif ast[0].data == 'let*':
                    let_env = Env(outer=env)
                    for k, v in zip(ast[1][::2], ast[1][1::2]):
                        let_env.set(k, EVAL(v, let_env))
                    ast, env = ast[2], let_env
                    continue

                elif ast[0].data == 'do':
                    ast = eval_ast(ast[1:], env)[-1]
                    return ast

                elif ast[0].data == 'if':
                    if EVAL(ast[1], env):
                        ast = ast[2]
                        continue
                    else:
                        if len(ast) == 4:
                            ast = ast[3]
                            continue
                        return mal_types.MalNil()

                elif ast[0].data == 'fn*':

                    def fn(*exprs):
                        new_env = Env(outer=env, binds=ast[1], exprs=exprs)
                        return EVAL(ast[2], new_env)

                    return mal_types.MalFn(ast=ast[2],
                                           params=ast[1],
                                           env=env,
                                           fn=fn)

            # apply
            evaluated_ast = eval_ast(ast, env)
            if callable(evaluated_ast[0]):
                f, args = evaluated_ast[0], evaluated_ast[1:]
                if isinstance(f, mal_types.MalFn):
                    ast = f.ast
                    env = Env(outer=f.env, binds=f.params, exprs=args)
                    # print(f)
                    continue
                else:
                    return f(*args)
            return evaluated_ast
        return mal_types.MalNil()
def EVAL(ast, env):
    while True:
        # print('INLOOP', ast)
        if not isinstance(ast, mal_types.list_types):
            return eval_ast(ast, env)
        elif not ast:
            return ast
        elif isinstance(ast, mal_types.list_types):
            if len(ast) == 0:
                return ast
            ast = macroexpand(ast, env)
            if not isinstance(ast, mal_types.list_types):
                return eval_ast(ast, env)
            if isinstance(ast[0], mal_types.MalSymbol):
                if ast[0].data == 'macroexpand':
                    return macroexpand(ast[1], env)

                if ast[0].data == 'def!':
                    value = EVAL(ast[2], env)
                    env.set(ast[1].data, value)
                    return value

                elif ast[0].data == 'defmacro!':
                    value = EVAL(ast[2], env)
                    value.is_macro = True
                    env.set(ast[1].data, value)
                    return value

                elif ast[0].data == 'let*':
                    let_env = Env(outer=env)
                    for k, v in zip(ast[1][::2], ast[1][1::2]):
                        new_value = EVAL(v, let_env)
                        let_env.set(k, new_value)
                    # print('let_env', k, new_value)
                    ast, env = ast[2], let_env
                    continue

                elif ast[0].data == 'do':
                    return eval_ast(ast[1:], env)[-1]

                elif ast[0].data == 'if':
                    if EVAL(ast[1], env):
                        ast = ast[2]
                        continue
                    else:
                        if len(ast) == 4:
                            ast = ast[3]
                            continue
                        return mal_types.MalNil()

                elif ast[0].data == 'fn*':

                    def fn(*exprs):
                        new_env = Env(outer=env, binds=ast[1], exprs=exprs)
                        return EVAL(ast[2], new_env)

                    return mal_types.MalFn(ast=ast[2],
                                           params=ast[1],
                                           env=env,
                                           fn=fn)

                elif ast[0].data == 'quote':
                    return ast[1]

                elif ast[0].data == 'quasiquote':
                    ast = quasiquote(ast[1])
                    continue

                elif ast[0].data == 'try*':
                    try:
                        return EVAL(ast[1], env)
                    except mal_types.MalException as e:
                        new_env = Env(outer=env)
                        new_env.set(ast[2][1], e)
                        return EVAL(ast[2][2], new_env)
            # apply
            evaluated_ast = eval_ast(ast, env)
            if callable(evaluated_ast[0]):
                #print("function", ast)
                f, args = evaluated_ast[0], evaluated_ast[1:]
                # print('f', f)
                # print('args', args)
                if isinstance(f, mal_types.MalFn):
                    ast = f.ast
                    env = Env(outer=f.env, binds=f.params, exprs=args)
                    continue
                else:
                    # print('********************f', f)
                    # print('********************args', args)
                    # print('@result', type(x), x)
                    return f(*args)
            return evaluated_ast
        return mal_types.MalNil()