import json
import os
import sys
import traceback
from importlib import util
from types import FunctionType
from typing import Any, Dict, List, Union, Literal

from .dp_ast import (
    ENDERS,
    ContinueStatement,
    DefineClassStatement,
    DefineFunctionStatement,
    ExecuteMacroStatement,
    IfFlowStatement,
    ImportStatement,
    InlineStatement,
    IntroduceVariableStatement,
    LoopStatement,
    ReturnStatement,
    ScheduleStatement,
    Statement,
    StatementType,
    COMMANDS,
    chain_tokens,
    make_expr,
    parse_str, parse, DefineEnumStatement
)
from .error import raise_syntax_error, raise_syntax_error_t, show_warning, raise_error, RadonError
from .nbt_definitions import ENTITIES_OBJ
from .tokenizer import (
    BlockIdentifierToken,
    GroupToken,
    SelectorIdentifierToken,
    Token,
    SET_OP,
    INC_OP,
    CMP_OP,
    CMP_COMBINE_OP,
    cpl_def_from_tokens,
    split_tokens,
    tokenize, LambdaFunctionToken
)
from .utils import (
    INT_TYPE,
    CplDef,
    VariableDeclaration,
    TokenType,
    get_uuid,
    INT_LIMIT,
    CplDefArray, reset_expr_id, FLOAT_PREC, CplDefFunction, get_float_limit
)

cwd = os.path.dirname(os.path.abspath(sys.argv[0]))
if sys.argv[0] == "":
    cwd = "/home/pyodide"


def get_lib_contents(path: str):
    with open(cwd + "/" + path, "r") as f:
        return f.read()


def _get_score_type(score):
    if isinstance(score, int):
        return "int"
    elif isinstance(score, float):
        return "float"
    return "float" if score.startswith("float_") else "int"


def transpile_str(code: str):
    (statements, macros) = parse_str(code)

    return Transpiler(statements, macros)


def split_cmp(expr: List[Token]):
    spl = []
    spl_ci = 0
    spl.append([[], None, []])
    for t in expr:
        if t.value in CMP_COMBINE_OP:
            spl_ci = False
            spl.append(t)
            spl.append([[], None, []])
        elif t.value in CMP_OP:
            if spl_ci != 0:
                raise_syntax_error("Unexpected comparison operator", t)
            spl_ci = 2
            sn1 = spl[-1]
            if isinstance(sn1, list):
                sn1[1] = t
        else:
            spl[-1][spl_ci].append(t)
    return spl


def split_fn_args(tokens: List[Token]):
    return list(map(lambda x: chain_tokens(x), split_tokens(tokens, ",")))


class FunctionArgument:
    def __init__(self, name, store, store_via):
        # type: (str, CplScore | CplNBT, str) -> None
        self.name = name
        self.unique_type = store.unique_type.content if store_via in {"stack", "macro"} else store.unique_type
        self.store = store
        self.store_via = store_via  # set, stack, macro


class LoopDeclaration:
    def __init__(self, eid: str, file: List[str], file_path: str, continue_: str):
        self.eid = eid
        self.file = file
        self.file_path = file_path
        self.continue_ = continue_


class FunctionDeclaration:
    def __init__(
            self,
            type: Literal[
                "radon", "python", "python-cpl", "python-raw", "python-cpl-imported", "mcfunction-imported"],
            name: str,
            returns="void",  # type: CplScore | CplNBT | str
            arguments: List[FunctionArgument] = None,
            function: Any = None,
            file_name: str = "",
            class_name: str | None = None,
            raw_args: List[int] = None
    ):
        if arguments is None:
            arguments = []
        if raw_args is None:
            raw_args = []
        self.id = get_uuid()
        self.type = type
        self.name = name
        self.file_name = file_name
        self.returns = returns
        self.arguments: List[FunctionArgument] = arguments
        self.function = function
        self.class_name = class_name
        self.raw_args = raw_args


class ClassDeclaration:
    def __init__(
            self,
            name: str,
            attributes,  # type: CplObject
            methods: List[FunctionDeclaration],
            sample  # type: CplObjectNBT
    ):
        self.id = get_uuid()
        self.name = name
        self.attributes = attributes
        self.methods = methods
        self.sample = sample


class TranspilerContext:
    def __init__(
            self,
            transpiler,  # type: Transpiler
            file_name: str,
            file: List[str],
            radon_path: str,
            function: FunctionDeclaration | None = None,
            loop: LoopDeclaration | Any | None = None,
            class_name: str | None = None
    ):
        self.transpiler = transpiler
        self.file_name = file_name
        self.file = file
        self.radon_path = radon_path
        self.function = function
        self.loop = loop
        self.class_name = class_name

    def clone(self):
        return TranspilerContext(
            self.transpiler,
            self.file_name,
            self.file,
            self.radon_path,
            self.function,
            self.loop,
            self.class_name
        )

    def clone_at(self, file_name: str):
        new = self.clone()
        new.file_name = file_name
        new.file = self.transpiler.files[file_name]
        return new


def _get_def(v: Any | CplDef):
    return v if isinstance(v, CplDef) else v.unique_type


def raw_group_args(token):
    return list(
        map(lambda x: GroupToken(token.code, x[0].start, x[-1].end, x), split_tokens(token.children, ",")))


from .cpl import Cpl, CplInt, CplFloat, CplString, CplArray, CplTuple, CplObject, CplScore, CplSelector, CplNBT, \
    CplObjectNBT, CplArrayNBT, val_nbt


class CustomCplObject(CplObject):
    def __init__(self, obj: Dict[str, Cpl | Any] = None, raw_functions: Dict[str, Any] = None):
        super().__init__(None)
        self.obj = obj or {}
        self.raw_functions = raw_functions or {}

    def _raw_call_index(self, ctx, index, arguments, token):
        if index in self.raw_functions:
            fn = self.raw_functions[index]
            return fn(ctx, arguments, token)
        return None

    def _call_index(self, ctx, index, arguments, token):
        if index in self.obj:
            fn = self.obj[index]
            if isinstance(fn, FunctionType):
                if fn.__code__.co_argcount == 2:
                    return fn(ctx, arguments)
                return fn(ctx, arguments, token)
        return None

    def _get_index(self, ctx, index: Cpl):
        if (isinstance(index, CplString)
                and index.value in self.obj
                and isinstance(self.obj[index.value], Cpl)):
            return self.obj[index.value]
        return None


def _type_to_cpl(
        token: Token, type: CplDef, score_loc: str, nbt_loc: str, force_nbt: bool = False
) -> Union[CplScore, CplNBT]:
    if force_nbt or type.type in {"string", "array", "object"}:
        return val_nbt(token, nbt_loc, type)
    return CplScore(token, score_loc, type.type)


def py_to_cpl(v):
    if isinstance(v, Cpl):
        return v
    if isinstance(v, str):
        return CplString(None, v)
    if isinstance(v, int):
        return CplInt(None, v)
    if isinstance(v, float):
        return CplFloat(None, v)
    if isinstance(v, bool):
        return CplInt(None, 1 if v else 0)
    return CplInt(None, 0)


builtin_fns: Dict[str, List[FunctionDeclaration]] = {}
builtin_vars: Dict[str, VariableDeclaration] = {}


def add_lib(*libs):
    for lib in libs:
        if isinstance(lib, FunctionDeclaration):
            if lib.name in builtin_fns:
                builtin_fns[lib.name].append(lib)
            else:
                builtin_fns[lib.name] = [lib]
        elif isinstance(lib, VariableDeclaration):
            builtin_vars[lib.name] = lib


radon_module_id = 0


def import_module_from_path(file_path):
    global radon_module_id
    radon_module_id += 1
    module_name = f"radon__{radon_module_id}"
    spec = util.spec_from_file_location(module_name, file_path)
    module = util.module_from_spec(spec)
    sys.modules[module_name] = module
    spec.loader.exec_module(module)
    return module


def get_module_attr(module):
    d = {}
    for k in module.__dict__:
        if k.startswith("__"):
            continue
        v = module.__dict__[k]
        d[k] = v
    return d


# add lib
from .builtin import rmath, print as _no, pyeval, time, swap, stdvar, listener, exit, recipe, success, raycast, getpos, \
    data

_ = [rmath, _no, pyeval, time, swap, stdvar, listener, exit, recipe, success, raycast, getpos, data]


def get_fn_macro_obj(ctx: TranspilerContext):
    if not ctx.function:
        return ""
    d = []
    has_macro = False
    for arg in ctx.function.arguments:
        if arg.store_via == "macro":
            has_macro = True
            d.append(f"'{arg.name}':'$({arg.name})'")
    if not has_macro:
        return ""
    return " {" + ",".join(d) + "}"


class Transpiler:
    def __init__(self, statements: List[Statement], macros: List[Statement], pack_namespace: str = "mypack",
                 pack_description: str = "", pack_format: int = 48, main_dir: str = "./",
                 main_file_path: str = "main.rn", debug_mode=False) -> None:
        reset_expr_id()
        self.files = dict()
        self.dp_files = dict()
        self.pack_namespace = pack_namespace
        self.pack_description = pack_description
        self.pack_format = pack_format
        self.main_dir = main_dir
        self.variables: Dict[str, VariableDeclaration] = dict()
        self.functions: List[FunctionDeclaration] = []
        self.classes: Dict[str, ClassDeclaration] = dict()
        self.loops: Dict[Any, LoopDeclaration] = dict()
        self.init_libs = []
        self.data = dict()
        self.main_file = []
        self.tick_file = []
        self.tempFiles: Dict[str, str] = dict()
        self.builtin_fns = builtin_fns
        self.builtin_vars = builtin_vars
        self.s = "" if self.pack_format >= 48 else "s"
        self.debug_mode = debug_mode

        if len(statements) == 0:
            self.files = {}
            return
        self.macros = macros
        load_file = [
            'scoreboard objectives add __temp__ dummy',
            f'scoreboard players set FLOAT_PREC __temp__ {FLOAT_PREC}'
        ]
        self.files["__load__"] = load_file
        self.load_file = load_file
        main_file = []
        self.main_file = main_file
        self._transpile(
            TranspilerContext(
                transpiler=self,
                file_name="__main__",
                file=main_file,
                radon_path=main_file_path,
                function=None,
                loop=None),
            statements
        )
        load_file.extend(main_file)

        if "tick" not in self.files:
            if self.tick_file:
                self.files["tick"] = self.tick_file
        else:
            self.files["tick"] = self.tick_file + self.files["tick"]

        if len(self.variables.keys()) > 0:
            self.load_file.insert(0, 'scoreboard objectives add global dummy "global"')

        # remove the lines after immediate returns
        for file in self.files:
            lines = self.files[file]
            if len(lines) == 0:
                continue
            index = 0
            for index, line in enumerate(lines):
                if line.startswith("return "):
                    break
            self.files[file] = lines[:index + 1]
        if self.debug_mode:
            for file in self.files:
                lines = self.files[file]
                new_file = []
                for line in lines:
                    new_file.append(line)
                    if line.startswith("scoreboard players "):
                        spl = line.split(" ")
                        new_file.append('tellraw @a ["' + spl[3] + " " + spl[4] + ' = ",{"score":{"name":"' + spl[
                            3] + '","objective":"' + spl[4] + '"}}]')
                self.files[file] = new_file

    def get_datapack_files(self):
        fn_folder = "function" + self.s
        dp_files = dict()
        for file in self.files:
            dp_files[f"data/{self.pack_namespace}/{fn_folder}/{file}.mcfunction"] = "\n".join(self.files[file])
        dp_files["pack.mcmeta"] = json.dumps({
            "pack": {
                "pack_format": self.pack_format,
                "description": self.pack_description,
            }
        }, indent=2)
        dp_files[f"data/minecraft/tags/{fn_folder}/load.json"] = json.dumps(
            {
                "values": [f"{self.pack_namespace}:__load__"]
            }, indent=2)
        if "tick" in self.files:
            dp_files[f"data/minecraft/tags/{fn_folder}/tick.json"] = json.dumps(
                {
                    "values": [f"{self.pack_namespace}:tick"]
                }, indent=2)
        for f in self.dp_files:
            dp_files[f"data/{self.pack_namespace}/{f}"] = self.dp_files[f]
        return dp_files

    def get_literal_num(self, num):
        isf = isinstance(num, float)
        if isf:
            num = int(num * FLOAT_PREC)
        k = f"literal_num_{num}"
        if k not in self.data:
            self.data[k] = True
            self.load_file.append(f"scoreboard players set literal_{num} __temp__ {num}")
        return CplScore(None, f"literal_{num}", "float" if isf else "int")

    def get_temp_file_name(self, content: str | List[str]):
        if isinstance(content, list):
            content = "\n".join(content)
        if content in self.tempFiles:
            return self.tempFiles[content]
        file_name = f"__temp__/{get_uuid()}"
        self.tempFiles[content] = file_name
        self.files[file_name] = content.split("\n")
        return file_name

    def get_all_fn_by_name(self, name):
        ls = []
        for fn in self.functions:
            if fn.name == name:
                ls.append(fn)
        return ls

    def check_args(self, args1, args2, base):
        if len(args1) != len(args2):
            return False
        for index, arg1 in enumerate(args1):
            arg2 = args2[index]
            if isinstance(_get_def(arg2), CplDefFunction) and isinstance(arg1, CplString) and arg1.is_fn_reference:
                temp = arg1
                arg1 = arg2
                arg2 = temp
            t1 = _get_def(arg1)
            t2 = _get_def(arg2)
            if isinstance(t1, CplDefFunction) and isinstance(arg2, CplString) and arg2.is_fn_reference:
                arg_fn = self.get_fn(arg2.value, t1.arguments, base)
                if not arg_fn:
                    return False
                if arg_fn.returns == "auto":
                    raise_syntax_error("Cannot use function as a value before the return type is not determined", base)
                if arg_fn.returns == "void" and t1.returns is None:
                    continue
                if arg_fn.returns.unique_type != t1.returns:
                    continue
                continue
            if t1 != t2:
                return False
        return True

    def assert_args(self, name, args1, args2, base):
        if not self.check_args(args1, args2, base):
            raise_syntax_error(
                f"Invalid arguments. Expected usage: {name}({', '.join(map(str, args1))}), but got: {name}({', '.join(map(str, args2))})",
                base)

    def get_fn(self, name, arguments: List[Cpl | CplDef], base) -> FunctionDeclaration | None:
        for fn in self.functions:
            if fn.name == name and self.check_args(fn.arguments, arguments, base):
                return fn
        return None

    def fn_exists(self, name):
        for fn in self.functions:
            if fn.name == name:
                return fn
        return None

    def _transpile_statement(self, ctx: TranspilerContext, statement: Statement):
        if isinstance(statement, DefineEnumStatement):
            values = {}
            for k in statement.values:
                v = statement.values[k]
                if isinstance(v, int):
                    values[k] = CplInt(None, v)
                elif isinstance(v, float):
                    values[k] = CplFloat(None, v)
                else:
                    values[k] = CplString(None, v)
            self.variables[statement.name.value] = VariableDeclaration(
                statement.name.value, CplObject(None, values), True)
            return
        if isinstance(statement, DefineClassStatement):
            if ctx.function or ctx.loop:
                raise_syntax_error(
                    "Classes should be declared in the main scope", statement
                )
            name = statement.name.value
            if name in self.classes or self.fn_exists(name):
                raise_syntax_error(f"Class {name} is already defined", statement)
            base_object = CplObject(statement.name, dict(), name)
            for (attr_name, attr_expr) in statement.attributes:
                cpl = self.tokens_to_cpl(ctx, attr_expr)
                base_object.value[attr_name] = cpl
                base_object.unique_type.content[attr_name] = cpl.unique_type
            sample: CplObjectNBT = base_object.cache(ctx, nbt_loc=f"storage class_sample {name}",  # type: ignore
                                                     force="nbt")

            class_ctx = TranspilerContext(
                transpiler=self,
                file_name=ctx.file_name,
                file=ctx.file,
                radon_path=ctx.radon_path,
                function=None,
                loop=None,
                class_name=name
            )

            methods = []
            has_init = False
            cls = ClassDeclaration(name, base_object, methods, sample)
            self.classes[name] = cls
            for mt in statement.methods:
                if mt.name.value == name:
                    has_init = True
                args = self._chains_to_args(mt.arguments)
                fn_dec = FunctionDeclaration(
                    type="radon",
                    name=name + "." + mt.name.value,
                    returns="auto",
                    arguments=args,
                    file_name=statement.name.value,
                    function="replace me"
                )
                self.functions.append(fn_dec)
                methods.append(fn_dec)

            new_methods = []

            for mt in statement.methods:
                self._transpile_statement(class_ctx, mt)
                fn = self.functions[-1]
                new_methods.append(fn.name)
            cls.methods = new_methods

            if not has_init:
                self._transpile(class_ctx, parse(
                    tokenize("fn " + name + "() {}")[0],
                    [], list(self.classes.keys())))
                fn = self.functions[-1]
                methods.append(fn)
            return
        if isinstance(statement, ImportStatement):
            pt = statement.path.value[1:-1]
            if pt.endswith(".py"):
                bef = os.getcwd()
                os.chdir(os.path.dirname(ctx.radon_path))
                if not os.path.exists(pt) or not os.path.isfile(pt):
                    os.chdir(bef)
                    raise_syntax_error("File not found", statement)
                try:
                    lib_module = import_module_from_path(pt)
                except Exception as e:
                    os.chdir(bef)
                    raise_error("Import error", str(e), statement)
                    raise e
                attrs = get_module_attr(lib_module)
                os.chdir(bef)
                if statement.as_ is not None:
                    name = statement.as_.value
                    if name not in self.variables:
                        self.variables[name] = VariableDeclaration(name, CustomCplObject(attrs), True)
                    return True
                for name in attrs:
                    v: Any = attrs[name]
                    if isinstance(v, FunctionType) and v.__code__.co_argcount in {2, 3}:
                        self.functions.append(FunctionDeclaration(
                            type="python-cpl-imported",
                            name=name,
                            function=v
                        ))
                    elif isinstance(v, Cpl):
                        if name not in self.variables:
                            self.variables[name] = VariableDeclaration(name, v, True)
                return True
            if pt.endswith(".mcfunction"):
                if statement.as_ is None:
                    raise_syntax_error("Expected 'as' in mcfunction import statement", statement)
                bef = os.getcwd()
                os.chdir(os.path.dirname(ctx.radon_path))
                if not os.path.exists(pt) or not os.path.isfile(pt):
                    os.chdir(bef)
                    raise_syntax_error("File not found", statement)
                content = open(pt, "r", encoding="utf-8").read()
                os.chdir(bef)
                fn_id = get_uuid()
                self.files[f"__imported__/{fn_id}"] = content.split("\n")
                self.functions.append(FunctionDeclaration(
                    type="mcfunction-imported",
                    name=statement.as_.value,
                    file_name=f"__imported__/{fn_id}"
                ))
                return True
            if pt.endswith(".rn"):
                if statement.as_ is not None:
                    raise_syntax_error("Unexpected 'as' in radon file import statement", statement)
                bef = os.getcwd()
                os.chdir(os.path.dirname(ctx.radon_path))
                if not os.path.exists(pt) or not os.path.isfile(pt):
                    os.chdir(bef)
                    raise_syntax_error("File not found", statement)
                content = open(pt, "r", encoding="utf-8").read()
                os.chdir(bef)
                (tokens, macros) = tokenize(content)
                ctx.transpiler._transpile(
                    TranspilerContext(
                        transpiler=ctx.transpiler,
                        file_name="__main__",
                        file=self.main_file,
                        radon_path=pt,
                        function=None,
                        loop=None,
                        class_name=None
                    ), parse(tokens, macros, list(ctx.transpiler.classes.keys()))
                )
                return True
            raise_syntax_error("Invalid import", statement)
        if isinstance(statement, ScheduleStatement):
            file_name = self._run_safe("__schedule__", statement.body, ctx)
            ctx.file.append(
                f"schedule function {self.pack_namespace}:{file_name} {statement.time.value} replace"
            )
            _return_safe(ctx)
            return True
        if statement.type == StatementType.BREAK:
            if not ctx.loop:
                raise_syntax_error("Cannot use break outside of loop", statement)
            if ctx.file is ctx.loop.file:
                ctx.file.append("return 0")
            else:
                loop_id = ctx.loop.eid
                ctx.file.append(f"scoreboard players set __break__{loop_id} __temp__ 1")
                ctx.file.append(f"return 0")
            return True
        if isinstance(statement, ContinueStatement):
            if not ctx.loop:
                raise_syntax_error("Cannot use continue outside of loop", statement)
            if ctx.file is ctx.loop.file:
                ctx.file.append(ctx.loop.continue_)
                ctx.file.append("return 0")
            else:
                loop_id = ctx.loop.eid
                ctx.file.append(f"scoreboard players set __continue__{loop_id} __temp__ 1")
                ctx.file.append(f"return 0")
            return True
        if isinstance(statement, LoopStatement):
            file_name = self._run_safe(
                "__loop__",
                statement.body,
                TranspilerContext(
                    transpiler=self,
                    file_name=ctx.file_name,
                    file=ctx.file,
                    radon_path=ctx.radon_path,
                    function=ctx.function,
                    loop=statement)
            )
            mac = get_fn_macro_obj(ctx)
            ctx.file.append(f"{'$' if mac else ''}function {self.pack_namespace}:{file_name}" + mac)
            loop_file = self.files[file_name]
            loop_id = int(file_name.split("/")[-1])
            loop_file.insert(0, f"scoreboard players set __break__{loop_id} __temp__ 0")
            loop_file.insert(0, f"scoreboard players set __continue__{loop_id} __temp__ 0")
            loop_c = self.loops[loop_id]
            loop_file.append(loop_c.continue_)
            return True
        if isinstance(statement, IfFlowStatement):
            has_if = len(statement.body) != 0
            has_else = statement.elseBody and len(statement.elseBody) != 0
            if not has_if and not has_else:
                return True
            resp = self.tokens_to_cpl(ctx, statement.condition)
            if (
                    isinstance(resp, CplInt)
                    or isinstance(resp, CplFloat)
                    or (resp.unique_type.type not in {"int", "float"} and not isinstance(resp, CplSelector))):
                is_true = (not isinstance(resp, CplInt) and not isinstance(resp, CplFloat)) or resp.value != 0
                if is_true:
                    if not has_if:
                        return True
                    self._transpile(ctx, statement.body)
                elif (
                        has_else and statement.elseBody
                ):  # I know this is bad, but the linter wants it (statement.elseBody)
                    self._transpile(ctx, statement.elseBody)
                return True
            if isinstance(resp, CplNBT):
                resp = resp.cache(ctx, force="score")
            if isinstance(resp, CplScore):
                if_cmd = f"execute unless score {resp.location} matches 0..0 run "
                unless_cmd = f"execute if score {resp.location} matches 0..0 run "
            elif isinstance(resp, CplSelector):
                if_cmd = f"execute if entity {resp.value} run "
                unless_cmd = f"execute unless entity {resp.value} run "
            else:
                raise "Invalid condition"
            has_one = (has_if or has_else) and (not has_if or not has_else)
            if has_one:
                one_body = statement.body if has_if else statement.elseBody
                i_cmd = if_cmd if has_if else unless_cmd
                if (
                        ctx.loop
                        and one_body
                        and ctx.loop.file is ctx.file
                        and len(one_body) == 1
                        and one_body[0].type == StatementType.BREAK
                ):
                    ctx.file.append(f"{i_cmd}return 0")
                    return True

            if has_if:
                if_name = self._run_safe("__if__", statement.body, ctx)
                ctx.file.append(f"{if_cmd}function {self.pack_namespace}:{if_name}")
            if has_else:
                else_name = self._run_safe("__else__", statement.elseBody, ctx)
                ctx.file.append(f"{unless_cmd}function {self.pack_namespace}:{else_name}")
            _return_safe(ctx)
            return True
        if isinstance(statement, InlineStatement):
            expr_tokens = statement.exprTokens
            ret = self.tokens_to_cpl(ctx, expr_tokens)
            if isinstance(ret, CplScore):
                # Cleaning the return results of the inline expression since they aren't going to be used
                cl1 = f"scoreboard players operation {ret.location} = "
                cl2 = f"execute store result score {ret.location} run "
                cl3 = f"execute store result score {ret.location} run data get"
                cl4 = f"execute store result score {ret.location} run scoreboard players get"
                if len(ctx.file) > 0 and (
                        ctx.file[-1].startswith(cl1)
                        or ctx.file[-1].startswith(cl3)
                        or ctx.file[-1].startswith(cl4)
                ):
                    ctx.file.pop()
                    return True
                if len(ctx.file) > 0 and ctx.file[-1].startswith(cl2):
                    ctx.file[-1] = ctx.file[-1][len(cl2):]
                if len(ctx.file) > 1 and ret.location not in ctx.file[-1] and ctx.file[-2].startswith(cl1):
                    ctx.file.pop(-2)
            if isinstance(ret, CplNBT) and ret.location.startswith("storage temp "):
                # Cleaning the return results of the inline expression since they aren't going to be used
                cl1 = f"data modify {ret.location} set from"
                if len(ctx.file) > 0 and ctx.file[-1].startswith(cl1):
                    ctx.file.pop()
                    return True
                if len(ctx.file) > 1 and ctx.file[-2].startswith(cl1) and ret.location not in ctx.file[-1]:
                    ctx.file.pop(-2)
                    return True
            return True
        if isinstance(statement, DefineFunctionStatement):
            fn_name = statement.name.value
            if ctx.class_name is not None and fn_name != ctx.class_name:
                fn_name = f"{ctx.class_name}.{fn_name}"
            arguments: List[FunctionArgument] = self._chains_to_args(statement.arguments)
            ret = (
                cpl_def_from_tokens(self.classes, statement.returns)
                if not isinstance(statement.returns, str)
                else statement.returns
            )
            if ret is None:
                raise_syntax_error("Invalid return type for a function", statement)
                assert False
            is_class_init = fn_name == ctx.class_name
            ret_loc = ret
            if isinstance(ret_loc, CplDef):
                assert isinstance(statement.returns, list)
                assert isinstance(ret, CplDef)
                ret_loc = _type_to_cpl(
                    statement.returns[0],
                    ret,
                    score_loc=f"fn_return __temp__",
                    nbt_loc=f"storage temp fn_return"
                )
            if is_class_init:
                if isinstance(statement.returns, list) and len(statement.returns) > 0:
                    raise_syntax_error("Class initializers cannot have return types", statement.returns[0])
                cls = self.classes[fn_name]
                ret_loc = CplObjectNBT(statement.name, f"storage variables this[-1]",
                                       cls.attributes.unique_type)
            count = 0
            new_functions = []
            for f in self.functions:
                if fn_name == f.name:
                    count += 1
                    if self.check_args(f.arguments, arguments, statement.name):
                        if f.function == "replace me":
                            count -= 1
                            continue
                        raise_syntax_error(
                            "Function with the same name and arguments already exists",
                            statement.name
                        )
                new_functions.append(f)
            self.functions.clear()
            self.functions.extend(new_functions)
            file_name = statement.name.value.lower()
            if ctx.class_name is not None:
                cls = self.classes[ctx.class_name]
                file_name = "__class__/" + cls.name.lower() + "_" + file_name
            base_file_name = file_name
            file_name_counter = 0
            while file_name in self.files:
                file_name_counter += 1
                file_name = base_file_name + str(file_name_counter)
            f = FunctionDeclaration(
                type="radon",
                name=fn_name,
                returns=ret_loc,
                arguments=arguments,
                file_name=file_name,
                class_name=ctx.class_name
            )

            self.functions.append(f)
            fn_file = []
            self.files[file_name] = fn_file
            for index, arg in enumerate(arguments):
                if arg.store_via == "macro":
                    t = arg.store.unique_type.content  # type: CplDef
                    macro = f"$({arg.name})"
                    if t.type == "string":
                        macro = f'"{macro}"'
                    fn_file.append(f"$data modify {arg.store.location} append value {macro}")
            self._transpile(
                TranspilerContext(
                    transpiler=self,
                    file_name=file_name,
                    file=fn_file,
                    radon_path=ctx.radon_path,
                    function=f,
                    loop=ctx.loop,
                    class_name=ctx.class_name),
                statement.body
            )
            if is_class_init:
                cls = self.classes[ctx.class_name]
                fn_file.insert(0, f"data modify storage variables this append {cls.sample.get_data_str(ctx)}")
            if f.returns == "auto":
                f.returns = "void"
            fn_file.insert(0, f"scoreboard players set __returned__ __temp__ 0")
            return True
        if isinstance(statement, ReturnStatement):
            if not ctx.function:
                raise_syntax_error(
                    "Cannot use return keyword outside functions", statement
                )
                return True
            expr_tokens = statement.exprTokens
            if ctx.function.returns == "void" and len(expr_tokens) != 0:
                raise_syntax_error(
                    f"Function cannot return a value because it has a void return type",
                    statement
                )
            if ctx.function.returns != "void":
                cpl = self.tokens_to_cpl(ctx, expr_tokens)
                if ctx.function.returns == "auto":
                    ctx.function.returns = cpl.cache(ctx)
                else:
                    if cpl.unique_type != ctx.function.returns.unique_type:
                        raise_syntax_error(
                            f"Function has a return type of {ctx.function.returns.unique_type}, "
                            f"but a {cpl.unique_type} was returned",
                            statement
                        )
                    ctx.function.returns._set(ctx, cpl)

            if ctx.file is not self.files[ctx.function.file_name]:
                ctx.file.append(f"scoreboard players set __returned__ __temp__ 1")
            ctx.file.append(f"return 0")
            return True
        if isinstance(statement, IntroduceVariableStatement):
            name = statement.name.value
            if name in self.variables:
                raise_syntax_error("Variable is already introduced", statement)
            type_v = cpl_def_from_tokens(self.classes, statement.varType)
            if not type_v:
                raise_syntax_error(f"Invalid type", statement)
                assert False
            self.variables[name] = VariableDeclaration(name, type_v, False)
            self.load_file.append(
                f'scoreboard objectives add {name} dummy "{name}"'
            )
            self.load_file.append(
                f"scoreboard players enable {name} global"
            )
            return True
        if isinstance(statement, ExecuteMacroStatement):
            exec_name = self._run_safe("__execute__", statement.body, ctx)
            cmd_str, has_repl = self.proc_cmd(ctx, statement.command, statement)
            mac = get_fn_macro_obj(ctx)
            if has_repl:
                fid = f"__execute__/{get_uuid()}"
                self.files[fid] = [f"$execute {cmd_str} run function {self.pack_namespace}:{exec_name}{mac}"]
                for arg in ctx.function.arguments:
                    if arg.store_via == "macro":
                        ctx.file.append("data modify storage temp _cmd_mem set value {}")
                        break
                if mac:
                    for arg in ctx.function.arguments:
                        if arg.store_via == "macro":
                            ctx.file.append(f"$data modify storage temp _cmd_mem.{arg.name} set value '$({arg.name})'")
                ctx.file.append(f"function {self.pack_namespace}:{fid} with storage temp _cmd_mem")
            else:
                ctx.file.append(
                    f"{'$' if mac else ''}execute {cmd_str} run function {self.pack_namespace}:{exec_name}{mac}")
            _return_safe(ctx)
            return True
        raise_syntax_error("Invalid statement", statement)

    def _transpile(self, ctx: TranspilerContext, statements: List[Statement]):
        for statement in statements:
            self._transpile_statement(ctx, statement)

    def _transpile_str(self, ctx: TranspilerContext, code: str):
        tokens, macros = tokenize(code)
        statements = parse(tokens, macros, list(self.classes.keys()))
        self._transpile(ctx, statements)

    def _run_safe(self, folder_name, statements, ctx: TranspilerContext):
        eid = get_uuid()
        file_name = f"{folder_name}/{eid}"
        new_file = []
        loop = ctx.loop
        if isinstance(loop, LoopStatement):
            st = loop
            mac = get_fn_macro_obj(ctx)
            continue_ = f"{'$' if mac else ''}function {self.pack_namespace}:{file_name}" + mac
            if st.time:
                if mac:
                    raise_syntax_error(
                        "Cannot use timed loops inside macro functions, move the loop into a normal function and run it there.",
                        st.time)
                continue_ = f"schedule function {self.pack_namespace}:{file_name} {st.time.value} replace"
            loop = LoopDeclaration(
                eid=eid,
                file=new_file,
                file_path=file_name,
                continue_=continue_
            )
            if st.step:
                step_name = f"__loop__/__step__/{get_uuid()}"
                loop.continue_ = f"{'$' if mac else ''}function {self.pack_namespace}:{step_name}{mac}"
                self.files[step_name] = []
                self._transpile(TranspilerContext(
                    transpiler=self,
                    file_name=step_name,
                    file=self.files[step_name],
                    radon_path=ctx.radon_path,
                    function=ctx.function,
                    loop=loop), st.step)
                self.files[step_name].append(continue_)
            self.loops[eid] = loop
        self.files[file_name] = new_file
        self._transpile(
            TranspilerContext(
                transpiler=self,
                file_name=file_name,
                file=new_file,
                radon_path=ctx.radon_path,
                function=ctx.function,
                loop=loop), statements
        )
        return file_name

    def _chains_to_args(self, chains: List[List[List[Token]]]):
        arg_names = []
        arguments = []
        for arg in chains:
            store_via = "stack"
            if len(arg) > 0 and arg[0][0].value == "$":
                store_via = "macro"
                arg = arg[1:]
            if len(arg) != 2 or arg[1][0].type != TokenType.IDENTIFIER:
                raise_syntax_error("Invalid argument for a function", arg[0][0])
            arg_type_parsed = cpl_def_from_tokens(self.classes, arg[0])
            if arg_type_parsed is None:
                raise_syntax_error("Invalid argument type for a function", arg[0][0])
                assert False
            arg_name = arg[1][0].value
            if arg_name in arg_names:
                raise_syntax_error("Argument name is already used", arg[1][0])
            arguments.append(
                FunctionArgument(
                    arg_name,
                    _type_to_cpl(
                        arg[0][0],
                        CplDefArray(arg_type_parsed) if store_via in {"stack", "macro"} else arg_type_parsed,
                        score_loc="",
                        nbt_loc=f"storage temp _fn_args.{get_uuid()}",
                        force_nbt=True
                    ),
                    store_via=store_via
                )
            )
        return arguments

    def proc_cmd(self, ctx: TranspilerContext, cmd: str, base):
        cmd_str = ""
        i = 0
        has_repl = False
        repl_i = 0
        cmd = " ".join(s[:-1] if s[-1] == "\\" else s for s in cmd.strip().split("\n"))
        before_index = len(ctx.file)
        while i < len(cmd):
            if cmd[i: i + 3] == "\\$(":
                if self.pack_format < 18:
                    raise_syntax_error_t(
                        f"Macros are not supported in the pack format {self.pack_format}. "
                        f"Consider using a version > (17 or 1.20.1 or 23w35a)",
                        cmd,
                        i,
                        i + 3,
                    )
                cmd_str += "$("
                i += 3
                continue
            if (
                    cmd[i: i + 2] == "$("
                    or cmd[i: i + 5] == "$str("
                    or cmd[i: i + 6] == "$jstr("
                    or cmd[i: i + 6] == "$dstr("
                    or cmd[i: i + 5] == "$dat("
                    or cmd[i: i + 5] == "$loc("
            ):
                si = i
                k = 0
                fp = 0
                while i < len(cmd):
                    i += 1
                    c = cmd[i]
                    if c == "(":
                        if fp == 0:
                            fp = i
                        k += 1
                    if c == ")":
                        k -= 1
                        if k == 0:
                            break
                if cmd[i] != ")":
                    raise_syntax_error_t("Unterminated parentheses", cmd, si, i + 1)
                repl = cmd[fp + 1: i]
                (expr_tokens, _) = tokenize(repl)
                expr_tokens = expr_tokens[:-1]
                try:
                    val = self.tokens_to_cpl(ctx, expr_tokens)
                except RadonError as e:
                    raise_syntax_error(e.text,
                                       Token(base.code, TokenType.POINTER, base.start + si, base.start + i))
                    raise e

                if cmd[si + 1] == "(":
                    cmd_str += f"$(_{repl_i})"
                    val.cache(
                        ctx, nbt_loc=f"storage temp _cmd_mem._{repl_i}", force="nbt"
                    )

                    has_repl = True
                    repl_i += 1

                elif cmd[si + 1] == "s":
                    cmd_str += val.tellraw_object_str(ctx)
                elif cmd[si + 1:si + 1] == "ds":
                    py_val = val.get_py_value()
                    if py_val is not None:
                        cmd_str += str(py_val)
                    else:
                        cmd_str += val.tellraw_object_str(ctx)
                elif cmd[si + 1] == "j":
                    cmd_str += val.tellraw_object_str(ctx)
                elif cmd[si + 1:si + 2] == "da":
                    cmd_str += val.get_data_str(ctx)
                elif cmd[si + 1] == "l":
                    if not isinstance(val, CplScore) and not isinstance(val, CplNBT):
                        val = val.cache(ctx, force="nbt")
                    cmd_str += val.location

                i += 1
                continue
            cmd_str += cmd[i]
            i += 1
        if has_repl:
            ctx.file.insert(before_index, "data modify storage temp _cmd_mem set value {}")

        return cmd_str, has_repl

    def run_cmd(self, ctx: TranspilerContext, pointer: Token, score_loc=None, type="result") -> Cpl:
        file = ctx.file
        cmd = pointer.value
        cmd_str, has_repl = self.proc_cmd(ctx, cmd, pointer)

        eid = score_loc or "int_" + str(get_uuid()) + " __temp__"
        eid_val = CplScore(pointer, eid, "int")

        if not has_repl:
            file.append(f"execute store {type} score {eid} run {cmd_str}")
            return eid_val

        cmd_file = []
        file_name = None
        '''for i in self.files:
            if "\n".join(self.files[i]) == "\n".join(cmd_file) and i.startswith("__cmd__/"):
                file_name = i
                break'''
        if file_name is None:
            cmd_id = get_uuid()
            file_name = f"__cmd__/{cmd_id}"
        self.files[file_name] = cmd_file
        cmd_file.append("$return run " + cmd_str)
        file.append(
            f"execute store {type} score {eid} run function {self.pack_namespace}:{file_name} with storage temp _cmd_mem"
        )
        return eid_val

    def _nbt_var_loc(self, ctx: TranspilerContext, token: Token) -> str:
        if isinstance(token, SelectorIdentifierToken):
            return f"entity {token.selector.value} {token.name.value}"
        if isinstance(token, BlockIdentifierToken):
            spl = split_tokens(token.block.children, ",")
            if len(spl) != 3:
                raise_syntax_error("Invalid block identifier", token)
                raise ValueError("")
            pos: List[str] = []
            for s in spl:
                sym = ""
                if s[0].value in {"~", "^"}:
                    sym = s[0].value
                    s = s[1:]
                cpl = self.tokens_to_cpl(ctx, s)
                if not isinstance(cpl, CplInt) and not isinstance(cpl, CplFloat):
                    raise_syntax_error(
                        f"Block identifier's position has to have 3 literal numbers", token
                    )
                    assert False
                pos.append(sym + str(cpl.value))
            return f"block {' '.join(pos)} {token.name.value}"
        raise ValueError("Invalid token")

    def _var_to_cpl_or_none(
            self, ctx: TranspilerContext, t: Token
    ) -> Union[Cpl, None]:
        if isinstance(t, SelectorIdentifierToken):
            name = t.name.value
            if name in ENTITIES_OBJ.content:
                return _type_to_cpl(
                    t,
                    ENTITIES_OBJ.content[name],
                    score_loc="",
                    nbt_loc=self._nbt_var_loc(ctx, t),
                    force_nbt=True
                )
            if name not in self.variables:
                return None
            var = self.variables[name]
            var_type = var.type
            return _type_to_cpl(
                t,
                var_type,
                score_loc=f"{t.selector.value} {name}",
                nbt_loc=self._nbt_var_loc(ctx, t),
                force_nbt=False
            )
        if isinstance(t, BlockIdentifierToken):
            name = t.name.value
            if name not in self.variables:
                return None
            var = self.variables[name]
            var_type = var.type
            val = _type_to_cpl(
                t,
                var_type,
                score_loc="",
                nbt_loc=self._nbt_var_loc(ctx, t),
                force_nbt=True
            )
            return val
        if t.type == TokenType.IDENTIFIER:
            name = t.value
            if name not in self.variables and name not in builtin_vars:
                if self.fn_exists(name):
                    return CplString(t, name, is_fn_reference=True)
                if ctx.function and ctx.class_name is not None and name == "this":
                    return CplObjectNBT(t, f"storage variables this[-1]",
                                        self.classes[ctx.class_name].sample.unique_type)
                if ctx.function:
                    fn = ctx.function
                    found = None
                    for arg in fn.arguments:
                        if arg.name == name:
                            found = arg
                            break
                    if found:
                        if found.store_via in {"stack", "macro"}:
                            return val_nbt(found.store.token, found.store.location + "[-1]",
                                           found.store.unique_type.content)
                        return found.store
                return None
            var = self.variables[name] if name in self.variables else builtin_vars[name]
            if var.init_func is not None:
                var.init_func(ctx)
            if var.get_func is not None:
                return var.get_func(ctx)
            var_type = var.type
            if var.constant:
                return var.value
            return _type_to_cpl(
                t,
                var_type,
                score_loc=f"{name} global",
                nbt_loc=f"storage variables {name}",
            )
        assert False

    def variable_token_to_cpl(
            self, ctx: TranspilerContext, t: Token
    ) -> Cpl:
        res = self._var_to_cpl_or_none(ctx, t)
        if res is None:
            raise_syntax_error(f"Variable '{t.value}' was not found", t)
            assert False
        return res

    def chain_to_cpl(self, ctx: TranspilerContext, t: List[Token]) -> Cpl:
        cpl = self.token_to_cpl(ctx, t[0])
        for token in t[1:]:
            if token.type == TokenType.IDENTIFIER:
                cpl = cpl.get_index(ctx, CplString(token, token.value), token)
                continue
            if token.type == TokenType.INT_LITERAL:
                cpl = cpl.get_index(ctx, CplInt(token, token.value), token)
                continue
            if isinstance(token, GroupToken):
                if token.open.value == "[":
                    if len(token.children) == 0:
                        raise_syntax_error("A bracket index has to include an expression inside.", token)
                        assert False
                    if len(token.children) == 1:
                        t0 = token.children[0]
                        if (isinstance(t0, GroupToken)
                                and t0.open.value == "{"
                                and isinstance(cpl, CplArrayNBT)):
                            cpl = val_nbt(t0, cpl.location + token.value, cpl.unique_type.content)
                            continue

                    spl = list(map(lambda x: self.tokens_to_cpl(ctx, x) if len(x) > 0 else None,
                                   split_tokens(token.children, ":", comma_errors=False)))
                    if len(spl) == 1:
                        cpl = cpl.get_index(ctx, spl[0], token)
                        continue
                    if len(spl) > 3:
                        raise_syntax_error("A slice can only include two colons.", token)
                        assert False
                    while len(spl) < 3:
                        spl.append(None)
                    if spl[0] is None:
                        spl[0] = CplInt(token, 0)
                    if spl[1] is None:
                        spl[1] = CplInt(token, -1)
                    if spl[2] is None:
                        spl[2] = CplInt(token, 1)

                    cpl = cpl.get_slice(ctx, spl[0], spl[1], spl[2], token)
                    continue
                if token.func:
                    cpl = cpl.call_index(ctx, token.func.value, token, token)
                    continue
                if token.open.value == "(":
                    cpl = cpl.call(ctx, token, token)
                    continue
            raise_syntax_error(f"Cannot index with the token", token)
        return cpl

    def arg_tokens_to_cpl(self, ctx: TranspilerContext, tokens: List[Token]):
        return list(map(lambda x: self.chains_to_cpl(ctx, chain_tokens(x)), split_tokens(tokens, ",")))

    def token_to_cpl(self, ctx: TranspilerContext, t: Token, variable_error=True):
        if t.type == TokenType.INT_LITERAL:
            if abs(int(t.value)) > INT_LIMIT:
                raise_syntax_error(
                    f"An integer literal cannot be larger than {INT_LIMIT}", t
                )
            return CplInt(t, t.value)
        if t.type == TokenType.FLOAT_LITERAL:
            if abs(float(t.value)) > get_float_limit():
                raise_syntax_error(
                    f"A float literal cannot be larger than {get_float_limit()}", t
                )
            return CplFloat(t, t.value)
        if t.type == TokenType.STRING_LITERAL:
            return CplString(t, t.value[1:-1])
        if t.type == TokenType.SELECTOR:
            return CplSelector(t, t.value)
        if (
                t.type == TokenType.SELECTOR_IDENTIFIER
                or t.type == TokenType.BLOCK_IDENTIFIER
                or t.type == TokenType.IDENTIFIER
        ):
            if not variable_error:
                return self._var_to_cpl_or_none(ctx, t)
            return self.variable_token_to_cpl(ctx, t)
        if isinstance(t, GroupToken) and t.func:
            return self.run_function(ctx, t)
        if isinstance(t, GroupToken) and t.open.value == "(":
            return t.cpl(ctx)
        if isinstance(t, GroupToken) and t.open.value == "[":
            if len(t.children) == 0:
                return CplTuple(t, [])
            cpl_list = list(map(
                lambda tk_list: self.tokens_to_cpl(ctx, tk_list), split_tokens(t.children)
            ))
            for cpl in cpl_list:
                if cpl.get_py_value() is None:
                    break
                if cpl.unique_type != cpl_list[0].unique_type:
                    return CplTuple(t, cpl_list)
            return CplArray(t, cpl_list)
        if isinstance(t, GroupToken) and t.open.value == "{":
            d = dict()

            for tl in split_tokens(list(filter(lambda x: x.type not in ENDERS, t.children))):
                if len(tl) < 3:
                    raise_syntax_error("Invalid object", t)
                key_token = tl[0]
                colon_token = tl[1]
                if colon_token.value != ":":
                    raise_syntax_error("Expected a colon", colon_token)
                expr = tl[2:]
                if key_token.type != TokenType.STRING_LITERAL:
                    raise_syntax_error("Expected an object key", key_token)
                    return CplInt(t, 0)
                k = key_token.value[1:-1]
                d[k] = self.tokens_to_cpl(ctx, expr)

            return CplObject(t, d, class_name=None)
        if isinstance(t, LambdaFunctionToken):
            name = f"__lambda__/{get_uuid()}"
            self._transpile_statement(ctx, DefineFunctionStatement(
                t.code,
                t.start,
                t.end,
                Token(name, TokenType.IDENTIFIER, 0, len(name)),
                parse(t.body.children, self.macros, list(self.classes.keys())),
                list(map(lambda x: chain_tokens(x), split_tokens(t.arguments.children, ","))),
                "auto"
            ))
            return CplString(t, name, is_fn_reference=True)
        raise_syntax_error("Unexpected token", t)
        raise ValueError("")

    def chains_to_cpl(
            self, ctx: TranspilerContext, chains: List[List[Token]]
    ) -> Cpl:
        if len(chains) == 0:
            return CplInt(None, 0)
        if len(chains) == 1:
            return self.chain_to_cpl(ctx, chains[0])
        t0 = chains[0]
        t1 = chains[1]
        if t0[0].value in COMMANDS:
            # TODO: use tokens
            return self.run_cmd(ctx, Token(t0[0].code, TokenType.POINTER, t0[0].start, chains[-1][-1].end))
        if t0[0].value in {"not", "!"}:
            # Basically, 1 - x
            variable_cpl = self.chain_to_cpl(ctx, t1)
            if variable_cpl.unique_type.type not in {"int", "float"}:
                return CplInt(t1[0], 0)
            if isinstance(variable_cpl, CplInt) or isinstance(variable_cpl, CplFloat):
                return CplInt(t1[0], 1 - variable_cpl.value)

            temp_score = CplScore(t1[0], f"int_{get_uuid()}")
            temp_score.compute(ctx, "=", CplInt(None, 1), t1[0])
            variable_cpl.compute(ctx, "-=", temp_score, t1[0])

            return temp_score
        if t0[0].value in INC_OP:
            variable_cpl = self.chain_to_cpl(ctx, t1)

            variable_cpl.compute(ctx, t0[0].value[0] + "=", CplInt(None, 1), t1[0])

            return variable_cpl
        if t1[0].value in INC_OP:
            variable_cpl = self.chain_to_cpl(ctx, t0)

            cached = variable_cpl.cache(ctx)
            variable_cpl.compute(ctx, t1[0].value[0] + "=", CplInt(None, 1), t0[0])

            return cached
        constant_keyword = False
        if t0[0].value == "const":
            constant_keyword = True
            t0 = t1
            if len(chains) < 3:
                raise_syntax_error("Expected an expression for the assignment", t1[0])
            t1 = chains[2]
            chains = chains[1:]

        front_type = cpl_def_from_tokens(self.classes, t0)
        if front_type is not None:
            t0 = t1
            if len(chains) < 3:
                raise_syntax_error("Expected an expression for the assignment", t1[0])
            t1 = chains[2]
            chains = chains[1:]

        if t1[0].value in SET_OP:
            is_constant = constant_keyword
            # TODO: [a, b, c] = [1, 2, 3] syntax and also for objects
            if len(chains) < 3:
                raise_syntax_error("Expected an expression for the assignment", t1[0])
            cpl = self.chains_to_cpl(ctx, chains[2:])
            if t0[0].value == "this" and not ctx.class_name:
                raise_syntax_error("Cannot access 'this' keyword outside of a class", t0[0])
            variable = self.token_to_cpl(ctx, t0[0], False)

            var_name_token = t0[0]
            var_name = var_name_token.value
            if isinstance(var_name_token, SelectorIdentifierToken) or isinstance(var_name_token, BlockIdentifierToken):
                var_name = var_name_token.name.value

            is_init = variable is None

            if is_init:
                if t1[0].value != "=" or len(t0) != 1:
                    raise_syntax_error(f"Variable '{t0[0].value}' was not found", t0[0])
                if t0[0] == "this":
                    raise_syntax_error("Cannot assign 'this' keyword to something", t0[0])

                if cpl.unique_type.type in {"int", "float"}:
                    self.load_file.append(
                        f'scoreboard objectives add {var_name} dummy "{var_name}"'
                    )
                if isinstance(cpl, CplTuple) and (not front_type or len(cpl.value) > 0):
                    if len(cpl.value) == 0:
                        show_warning(f"Did you mean to initialize an array instead of a tuple? "
                                     f"Use this syntax: int[] <variable_name> = []", t1[0])
                    is_constant = True
                last_var_type = cpl
                if front_type and front_type != cpl.unique_type:
                    if isinstance(front_type, CplDefArray) and isinstance(cpl, CplTuple) and len(cpl.value) == 0:
                        last_var_type = _type_to_cpl(t0[0], front_type, "", f"storage variables {var_name}")
                    else:
                        raise_syntax_error(f"Variable was defined as a {front_type} but got a {cpl.unique_type}", t0[0])
                if isinstance(cpl, CplSelector):
                    last_var_type = INT_TYPE
                self.variables[var_name] = VariableDeclaration(var_name, last_var_type, is_constant)
                if is_constant:
                    if (isinstance(var_name_token, SelectorIdentifierToken)
                            or isinstance(var_name_token, BlockIdentifierToken)):
                        nbt_loc = self._nbt_var_loc(ctx, var_name_token)
                        ctx.file.append(f"data modify {nbt_loc} set value {json.dumps(cpl.get_py_value())}")
                        del self.variables[var_name]
                    return cpl
            else:
                if front_type:
                    raise_syntax_error("Already defined variables cannot be assigned to a type", t1[0])
                if var_name in self.variables and self.variables[var_name].constant:
                    raise_syntax_error("Variable is constant", t0[0])
                    raise ValueError("")
                if is_constant:
                    raise_syntax_error("Already defined variables cannot be assigned with the 'const' keyword", t0[0])

            variable_cpl = self.chain_to_cpl(ctx, t0)

            if variable_cpl.unique_type.type in {"int", "float"} and isinstance(cpl, CplSelector):
                variable_cpl._set(ctx, CplInt(t0[0], 0))
                if isinstance(variable_cpl, CplScore):
                    set_1 = f"scoreboard players set {variable_cpl.location} 1"
                elif isinstance(variable_cpl, CplNBT):
                    set_1 = f"data modify storage variables {variable_cpl.location} set value 1"
                else:
                    raise ValueError("")
                ctx.file.append(f"execute if entity {cpl.value} run {set_1}")
                return variable_cpl

            if variable_cpl.unique_type == "int" and cpl.unique_type == "float":
                show_warning("Losing float precision by assigning a float to an int.", t1[0])

            variable_cpl.compute(ctx, t1[0].value, cpl, t0[0])

            return cpl

        expr = make_expr(chains)

        stack: List[Cpl] = []

        for chain in expr:
            if chain[0].type != TokenType.OPERATOR:
                stack.append(self.chain_to_cpl(ctx, chain))
                continue

            right = stack.pop()
            if len(stack) == 0:
                raise_syntax_error("Expected a value after an operator", chain[0])
            left = stack.pop()

            score = left.compute(ctx, chain[0].value, right, t0[0])

            stack.append(score)

        return stack[-1]

    def tokens_to_cpl(
            self, ctx: TranspilerContext, tokens: List[Token]
    ) -> Cpl:
        if len(tokens) == 0:
            return CplInt(None, 0)
        chains = chain_tokens(tokens)
        return self.chains_to_cpl(ctx, chains)

    def run_function_with_cpl(
            self,
            ctx: TranspilerContext,
            name: str,
            args: List[Cpl],
            base: Union[Token, None] = None,
            class_name: str | None = None
    ) -> Cpl:
        exists_fn = self.fn_exists(name)

        if not exists_fn and name not in builtin_fns:
            for var in builtin_vars:
                val = builtin_vars[var].value
                if isinstance(val, CustomCplObject):
                    if name in val.obj:
                        raise_syntax_error(f"Undefined function. Did you mean to use built-in function {var}.{name}()?",
                                           base)
            raise_syntax_error(f"Undefined function: {name}", base)
            return CplInt(base, 0)

        if name not in self.classes and not exists_fn and name in builtin_fns:
            built = builtin_fns[name]
            if len(built) == 1 and built[0].type == "python-raw":
                if not base:
                    raise_syntax_error("Cannot run a raw function without tokens", base)
                if not isinstance(base, GroupToken):
                    raise_syntax_error("Cannot run a raw function without group token", base)
                    raise ValueError("")
                return built[0].function(ctx, raw_group_args(base), base) or built[0].returns
            if len(built) == 1 and built[0].type == "python-cpl":
                return built[0].function(ctx, args, base)
            self.functions.extend(built)

        if exists_fn and exists_fn.type == "python-cpl-imported":
            try:
                if exists_fn.function.__code__.co_argcount == 2:
                    return exists_fn.function(ctx, args) or CplInt(base, 0)
                return exists_fn.function(ctx, args, base)
            except RadonError as e:
                raise e
            except Exception as _:
                stack_trace = traceback.format_exc()
                raise Exception(stack_trace)

        found_fn = None
        available = []
        for f in self.functions:
            if f.name == name:
                available.append(f"{f.name}({', '.join(str(arg.unique_type) + ' ' + arg.name for arg in f.arguments)})")
                if self.check_args(f.arguments, args, base):
                    found_fn = f
                    break
        if found_fn is None:
            raise_syntax_error(f"Invalid arguments. Available usages:\n{'\n'.join(available)}", base)
            assert False

        returns = found_fn.returns
        fn_args = found_fn.arguments

        if found_fn.type == "python-cpl":
            return found_fn.function(ctx, args, base)

        has_any_macro_argument = False
        index_left = len(ctx.file)

        for index, arg in enumerate(fn_args):
            store_at = arg.store
            val = args[index]
            if arg.store_via == "stack":
                if isinstance(val, CplScore):
                    ctx.file.append(
                        f"data modify {store_at.location} append value {store_at.unique_type.content.get_sample_value()}")
                    val.cache(ctx, nbt_loc=f"{store_at.location}[-1]", force="nbt")
                else:
                    ctx.file.append(f"data modify {store_at.location} append {val.get_data_str(ctx)}")
            elif arg.store_via == "macro":
                val.cache(ctx, nbt_loc=f"storage temp _fn_args_macro.{found_fn.id}.{arg.name}", force="nbt")
                has_any_macro_argument = True
            else:
                store_at._set(ctx, val)

        if found_fn.type == "mcfunction-imported":
            eid = f"int_{get_uuid()} __temp__"
            ctx.file.append(
                f"execute store result score {eid} run function {self.pack_namespace}:{found_fn.file_name} with storage fn_mem")
            return CplScore(base, eid)

        if found_fn.type == "python":
            found_fn.function(ctx, found_fn, base)

        actually_returning = CplInt(base, 0)

        if found_fn.type == "radon":
            ctx.file.append(f"function {self.pack_namespace}:{found_fn.file_name}" + (
                f" with storage temp _fn_args_macro.{found_fn.id}" if has_any_macro_argument else ""
            ))
            for index, arg in enumerate(fn_args):
                if arg.store_via == "stack":
                    ctx.file.append(f"data remove {arg.store.location}[-1]")

            # the line after these comments is for the case where you call a function inside a function
            # example:
            # function test(): void {
            #   # "scoreboard players set __returned__ __temp__ 0" runs:
            #   # __returned__ = 0
            #   a()
            #   # __returned__ = 1
            #   # "scoreboard players set __returned__ __temp__ 0" runs again and:
            #   # __returned__ = 0
            #   return
            # }
            # function a(): void {
            #   test()
            #   return
            # }
            if ctx.function and returns != "void":
                ctx.file.append(f"scoreboard players set __returned__ __temp__ 0")
            if isinstance(returns, str):
                if returns == "auto":
                    raise_syntax_error(
                        "Function's return type hasn't been determined yet", base
                    )
            else:
                actually_returning = returns

        if name in self.classes:
            class_name = name

        if class_name is not None and ctx.file is not self.main_file:
            ctx.file.append(f"data modify storage temp class_this set from storage variables this[-1]")
            ctx.file.append(f"data remove storage variables this[-1]")
            actually_returning = CplObjectNBT(base, f"storage temp class_this",
                                              self.classes[class_name].sample.unique_type)

        return actually_returning

    def run_function(
            self, ctx: TranspilerContext, t: GroupToken
    ) -> Cpl:
        if not t.func:
            return CplInt(t, 0)

        raw_args = []

        if t.func.value in builtin_fns:
            built = builtin_fns[t.func.value]
            if len(built) == 1 and built[0].type == "python-raw":
                return built[0].function(ctx, raw_group_args(t), t) or built[0].returns
            if len(built) == 1 and built[0].type == "python-cpl":
                raw_args = built[0].raw_args

        separated = split_tokens(t.children, ",")
        arguments: List[Cpl] = []
        for index, arg in enumerate(separated):
            arguments.append(arg if index in raw_args else self.tokens_to_cpl(ctx, arg))

        return self.run_function_with_cpl(ctx, t.func.value, arguments, base=t)

    def compute_tokens(self, ctx: TranspilerContext, left: List[Token], op: str, right: List[Token]):
        return self.tokens_to_cpl(ctx, left).compute(ctx, op, self.tokens_to_cpl(ctx, right), left[0])

    def _cmp_to_cpl(self, ctx, ls):
        return self.compute_tokens(ctx, ls[0], ls[1].value, ls[2])


def _return_safe(ctx: TranspilerContext):
    if ctx.function:
        ctx.file.append(
            f"execute if score __returned__ __temp__ matches 1..1 run return 0"
        )
    if ctx.loop:
        if ctx.file is ctx.loop.file:
            ctx.file.append(
                f"execute if score __break__{ctx.loop.eid} __temp__ matches 1..1 run return 0"
            )
            cou = ctx.loop.continue_
            mac = ""
            if cou[0] == "$":
                mac = "$"
                cou = cou[1:]
            ctx.file.append(
                f"{mac}execute if score __continue__{ctx.loop.eid} __temp__ matches 1..1 run return run {cou}"
            )
        else:
            ctx.file.append(
                f"execute if score __break__{ctx.loop.eid} __temp__ matches 1..1 run return 0"
            )
            ctx.file.append(
                f"execute if score __continue__{ctx.loop.eid} __temp__ matches 1..1 run return 0"
            )
