Ejemplo n.º 1
0
class MappingVisitor(MineScriptVisitor):
    def __init__(self, name, filename):
        self.logger = Logger(filename)
        self.igfunctions = {}
        self.igmemory = {}
        self.igfunc = None
        
    def visitFunctionDeclaration(self, ctx):
        type_ = ctx.type_.text
        name = ctx.WORD().getText()
        
        if self.igfunc is not None:
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(f"Nested functions not supported ('{name}' inside '{self.igfunc}')", line, char, "error")
            raise Exception()
        
        if name in self.igfunctions:
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(f"Multiple definitions of function '{name}'", line, char, "error")
            raise Exception()
        
        self.igfunctions[name] = {
            "code" : [],
            "args": []
        }
        if type_ != "void": 
            self.igmemory[f"_f_{name}"] = type_
            self.igfunctions[name]["return"] = f"_f_{name}"
        
        for functionArg in ctx.functionArg():
            arg_type = functionArg.type_.text
            arg_name = functionArg.WORD().getText()
            if len(list(filter(lambda i: i[0] == arg_name, self.igfunctions[name]["args"]))) != 0:
                line = functionArg.start.line
                char = functionArg.start.column
                self.logger.log(f"Multiple definitions of variable '{arg_name}'", line, char, "error")
                raise MappingException()
            self.igfunctions[name]["args"].append((arg_name, arg_type))
            
        if (name == "load" or name == "tick") and len(self.igfunctions[name]["args"]) != 0:
            line = ctx.functionArg(0).start.line
            char = ctx.functionArg(0).start.column
            self.logger.log(f"The built-in function '{name}' takes no args", line, char, "error")
            raise MappingException()

        self.igfunc = name
        self.visitChildren(ctx)            
        self.igfunc = None        
Ejemplo n.º 2
0
class Visitor(MineScriptVisitor):
    def __init__(self, name, filename):
        self.logger = Logger(filename)
        self.name = name

        self.memory = {}
        self.localmemory = {}
        self.functionargs = {}
        self.func = None
        self.r_value = None

        self.igmemory = {}
        self.local = {}
        self.igfunctions = {}
        self.igfunc = None
        self.igfuncinfo = None

        self.igloops = {}

        self.usedvars = set()
        self.tempvars = set()
        self.prefixes = []
        self.loop = []
        self.break_var = []
        self.loops = 0
        self.tags = 0

    def get_value(self, obj):
        if isinstance(obj, Literal):
            if obj.type == "char[]":
                print(obj.value)
                return ''.join(obj.value)
            if obj.type == "char":
                return chr(obj.value)
            return obj.value
        else:
            return self.get_value(self.memory[obj])

    def at_compile_time(self, obj):
        if isinstance(obj, Literal):
            return True
        if obj.startswith("$"):
            return True
        return False

    def is_used(self, ctx):
        aux = ctx.parentCtx
        while aux is not None and not isinstance(aux,
                                                 MineScriptParser.StatContext):
            if not isinstance(aux, MineScriptParser.IgnoreContext):
                return True
            aux = aux.parentCtx
        return False

    def is_used_on_condition(self, ctx):
        aux = ctx.parentCtx
        while aux is not None and not isinstance(aux,
                                                 MineScriptParser.StatContext):
            for context in CONTROL_FLOW_CONTEXTS:
                if isinstance(aux, context):
                    return True
            aux = aux.parentCtx
        return False

    def is_defined(self, name):
        if name.startswith("$"):
            return name in self.memory
        if self.igfunc is None:
            return name in self.igmemory
        else:
            name = name.replace("+local", "")
            if name in self.local[self.igfunc]:
                return True
            return name in self.igmemory

    def assert_is_defined(self, name, ctx):
        if not self.is_defined(name):
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(f"Undeclared variable '{name}'", line, char,
                            "error")
            raise CompileTimeException()

    def get_type(self, name):
        if isinstance(name, Literal):
            return name.type
        else:
            name = name.replace("+local", "")
            if name.startswith("$"):
                return self.memory[name].type
            if self.igfunc is None:
                return self.igmemory[name]
            else:
                if name in self.local[self.igfunc]:
                    return self.local[self.igfunc][name]
                else:
                    return self.igmemory[name]

    def assert_types_match(self, correct, given, ctx):
        if self.get_type(correct) != self.get_type(given):
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(
                f"Mismatching types: '{self.get_type(correct)}' and '{self.get_type(given)}'",
                line, char, "error")
            raise CompileTimeException()

    def add_var(self, name, type_, ctx=None):
        if name.startswith("$"):
            self.memory[name] = Literal(None, type_)
        else:
            if self.igfunc is None or name.startswith("_"):
                if name not in self.igmemory or ctx is None:
                    self.igmemory[name] = type_
                else:
                    line = ctx.start.line
                    char = ctx.start.column
                    self.logger.log(
                        f"Multiple definitions of variable '{name}'", line,
                        char, "error")
                    raise CompileTimeException()
            else:
                if name not in self.local[self.igfunc] or ctx is None:
                    self.local[self.igfunc][name] = type_
                else:
                    line = ctx.start.line
                    char = ctx.start.column
                    self.logger.log(
                        f"Multiple definitions of variable '{name}'", line,
                        char, "error")
                    raise CompileTimeException()

    def set_var(self, name, value, ctx):
        if isinstance(value, Literal):
            if not name.startswith("$"):
                if not self.get_type(value).endswith("[]"):
                    self.add_cmd(
                        f"scoreboard players set #MineScript {name} {value.value}",
                        ctx)
                else:
                    list_value = ""
                    for item in value.value:
                        if self.get_type(value) == "char[]":
                            list_value += f'{ord(item)},'
                        elif self.get_type(value) == "int[]":
                            list_value += str(item.value) + ","
                    list_value = "{value:" + f"[{list_value[:-1]}]," + f"size:{str(len(value.value))}" + "}"
                    self.add_cmd(
                        f"data modify storage {self.name}:minescript {name} set value {list_value}",
                        ctx)
            else:
                self.memory[name] = value
        else:
            if name.startswith("$"):
                line = ctx.start.line
                char = ctx.start.column
                self.logger.log(
                    "Compile-time variable can't be assigned to an in-game variable",
                    line, char, "error")
                raise CompileTimeException()
            if not self.get_type(value).endswith("[]"):
                self.add_cmd(
                    f"scoreboard players operation #MineScript {name} = #MineScript {value}",
                    ctx)
            else:
                self.add_cmd(
                    f"data modify storage {self.name}:minescript {name} set from storage {self.name}:minescript {value}",
                    ctx)

    def get_arr_element(self, name, element, ctx):
        if self.get_type(element) != "int":
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(
                f"List indexes must be intergers (was {self.get_type(element)})",
                line, char, "error")
            raise CompileTimeException()
        if not name.startswith("$"):
            if isinstance(element, Literal):
                temp_result = self.get_temp_var(self.get_type(name)[:-2])
                self.add_cmd(
                    f"execute store result score #MineScript {temp_result} run "
                    f"data get storage {self.name}:minescript {name}.value[{element.value}]",
                    ctx)
                return temp_result
            else:
                temp_list = self.get_temp_var(self.get_type(name))
                count = self.get_temp_var("int")
                #size = self.get_temp_var("int")
                temp_result = self.get_temp_var(self.get_type(name)[:-2])
                #self.add_cmd(f"execute store result score #MineScript {size} run "
                #             f"data get storage {self.name}:minescript {name}.size", ctx)
                self.set_var(count, Literal(0, "int"), ctx)
                self.set_var(temp_list, name, ctx)
                name = f"_loop{self.loops}"
                self.add_cmd(f"function {self.name}:{name}", ctx)

                self.start_loop(name, None)
                self.add_cmd(f"scoreboard players add #MineScript {count} 1",
                             ctx)
                self.add_cmd(
                    f"execute store result score #MineScript {temp_result} run "
                    f"data get storage {self.name}:minescript {temp_list}.value[0]",
                    ctx)
                self.add_cmd(
                    f"data remove storage {self.name}:minescript {temp_list}.value[0]",
                    ctx)
                self.add_cmd(
                    f"execute unless score #MineScript {count} > #MineScript {element} run "
                    f"function {self.name}:{name}", ctx)
                self.end_loop()

                self.mark_unused(temp_list)
                self.mark_unused(count)
                #self.mark_unused(size)

                return temp_result
        else:
            if isinstance(element, Literal):
                return self.memory[name][element.value]
            elif element.startswith("$"):
                return self.memory[name][self.memory[element].value]

    def set_arr_element(self, name, element, value, ctx):
        if self.get_type(element) != "int":
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(
                f"List indexes must be intergers (was {self.get_type(element)})",
                line, char, "error")
            raise CompileTimeException()
        if not name.startswith("$"):
            if isinstance(element, Literal):
                if isinstance(value, Literal):
                    self.add_cmd(
                        f"data modify storage {self.name}:minescript {name}.value[{element.value}] value {value.value}",
                        ctx)
                else:
                    self.add_cmd(
                        f"execute store result storage {self.name}:minescript {name}.value[{element.value}] run "
                        f"scoreboard objectives get #MineScript {value}", ctx)
            else:
                temp_list = self.get_temp_var(self.get_type(name))
                count = self.get_temp_var("int")
                done = self.get_temp_var("int")
                size = self.get_temp_var("int")
                self.add_cmd(
                    f"execute store result score #MineScript {size} run "
                    f"data get storage {self.name}:minescript {name}.size",
                    ctx)
                self.set_var(count, Literal(0, "int"), ctx)
                self.set_var(done, Literal(0, "int"), ctx)
                self.set_var(temp_list, Literal([], self.get_type(name)), ctx)
                lname = f"_loop{self.loops}"
                self.add_cmd(f"function {self.name}:{lname}", ctx)

                self.start_loop(lname, None)
                self.add_cmd(
                    f"execute unless score #MineScript {count} = #MineScript {element} run "
                    f"data modify storage {self.name}:minescript {temp_list}.value append from storage "
                    f"{self.name}:minescript {name}.value[0]", ctx)
                if isinstance(value, Literal):
                    self.add_cmd(
                        f"execute if score #MineScript {count} = #MineScript {element} "
                        f"if score #MineScript {done} matches 0 run "
                        f"data modify storage {self.name}:minescript {temp_list}.value append "
                        f"value {value.value}", ctx)
                else:
                    self.add_cmd(
                        f"execute if score #MineScript {count} = #MineScript {element} "
                        f"if score #MineScript {done} matches 0 run "
                        f"data modify storage {self.name}:minescript {temp_list}.value append value 0",
                        ctx)
                    self.add_cmd(
                        f"execute if score #MineScript {count} = #MineScript {element} "
                        f"if score #MineScript {done} matches 0 run "
                        f"execute store result storage {self.name}:minescript {temp_list}.value[-1] int 1 run "
                        f"scoreboard players get #MineScript {value}", ctx)
                self.add_cmd(
                    f"execute if score #MineScript {count} = #MineScript {element} "
                    f"if score #MineScript {done} matches 0 run "
                    f"scoreboard players set #MineScript {done} 1", ctx)
                self.add_cmd(
                    f"data remove storage {self.name}:minescript {name}.value[0]",
                    ctx)
                self.add_cmd(f"scoreboard players add #MineScript {count} 1",
                             ctx)
                self.add_cmd(
                    f"execute unless score #MineScript {count} >= #MineScript {size} run "
                    f"function {self.name}:{lname}", ctx)
                self.end_loop()

                self.add_cmd(
                    f"data modify storage {self.name}:minescript {name}.value set from "
                    f"storage {self.name}:minescript {temp_list}.value", ctx)
                self.mark_unused(temp_list)
                self.mark_unused(count)
                self.mark_unused(size)
                self.mark_unused(done)
                if isinstance(value, str):
                    self.mark_unused(value)
        else:
            if self.at_compile_time(value):
                if self.at_compile_time(element):
                    self.memory[name].value[self.get_value(
                        element)] = self.get_value(value)
                else:
                    line = ctx.start.line
                    char = ctx.start.column
                    self.logger.log(
                        f"List index must be evaluated at compile-time", line,
                        char, "error")
                    raise CompileTimeException()
            else:
                line = ctx.start.line
                char = ctx.start.column
                self.logger.log(
                    f"Assigned value must be evaluated at compile-time", line,
                    char, "error")
                raise CompileTimeException()

    def get_temp_var(self, type_):
        n = None
        for i in range(len(self.tempvars)):
            if f"_var{i}" not in self.tempvars:
                n = i
                break
        if n is None: n = len(self.tempvars)
        name = f"_var{n}"
        self.add_var(name, type_)
        self.tempvars.add(name)
        self.usedvars.add(name)
        return name

    def mark_unused(self, name):
        if name.startswith("_var") and name in self.tempvars:
            self.tempvars.remove(name)

    def add_cmd(self, command, ctx):
        if len(self.prefixes) != 0:
            command = "execute " + " ".join(self.prefixes) + " run " + command
        if self.loop != []:
            self.igloops[self.loop[-1]].append(command)
        elif self.igfunc is not None:
            self.igfunctions[self.igfunc]["code"].append(command)
        else:
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log("All code must reside inside a function", line,
                            char, "error")
            raise CompileTimeException()

    def start_loop(self, name, break_var):
        self.igloops[name] = []
        self.loop.append(name)
        self.break_var.append(break_var)
        if break_var is not None:
            self.prefixes.append(
                f"unless score #MineScript {break_var} matches 0")
        self.loops += 1

    def end_loop(self):
        self.loop.pop(-1)
        bv = self.break_var.pop(-1)
        if bv is not None:
            self.prefixes.pop(-1)
            self.mark_unused(bv)

    def compare(self, expr1, expr2, op, ctx):
        self.assert_types_match(expr1, expr2, ctx)
        if self.at_compile_time(expr1) and self.at_compile_time(expr2):
            return Literal(
                eval(f"self.get_value(expr1){op}self.get_value(expr2)"), "int")
        elif isinstance(expr1, str) and self.at_compile_time(expr2):
            temp_result = self.get_temp_var("int")
            self.set_var(temp_result, Literal(0, "int"), ctx)
            if op == "==":
                self.add_cmd(
                    f"execute if score #MineScript {expr1} matches {self.get_value(expr2)} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            elif op == "<=":
                self.add_cmd(
                    f"execute if score #MineScript {expr1} matches ..{self.get_value(expr2)} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            elif op == ">=":
                self.add_cmd(
                    f"execute if score #MineScript {expr1} matches {self.get_value(expr2)}.. run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            elif op == "<":
                self.add_cmd(
                    f"execute unless score #MineScript {expr1} matches {self.get_value(expr2)}.. run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            elif op == ">":
                self.add_cmd(
                    f"execute unless score #MineScript {expr1} matches ..{self.get_value(expr2)} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            elif op == "!=":
                self.add_cmd(
                    f"execute if score #MineScript {expr1} matches {self.get_value(expr2)} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            self.mark_unused(expr1)
            return temp_result

        elif isinstance(expr2, str) and self.at_compile_time(expr1):
            if op == "==" or op == "!=": newop = op
            elif op == ">=": newop = "<="
            elif op == "<=": newop = ">="
            elif op == ">": newop = "<"
            elif op == "<": newop = ">"
            return self.compare(expr2, expr1, newop, ctx)

        elif isinstance(expr1, str) and isinstance(expr2, str):
            temp_result = self.get_temp_var("int")
            self.set_var(temp_result, Literal(0, "int"), ctx)
            if op != "==" and op != "!=":
                self.add_cmd(
                    f"execute if score #MineScript {expr1} {op} #MineScript {expr2} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            elif op == "==":
                self.add_cmd(
                    f"execute if score #MineScript {expr1} = #MineScript {expr2} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            else:
                self.add_cmd(
                    f"execute unless score #MineScript {expr1} = #MineScript {expr2} run scoreboard players set #MineScript {temp_result} 1",
                    ctx)
            self.mark_unused(expr1)
            self.mark_unused(expr2)
            return temp_result

    def operate(self, expr1, expr2, op, ctx):
        self.assert_types_match(expr1, expr2, ctx)

        if self.at_compile_time(expr1) and self.at_compile_time(expr2):
            return Literal(
                eval(f"self.get_value(expr1){op}self.get_value(expr2)"),
                self.get_type(expr1))

        elif isinstance(expr1, str) and self.at_compile_time(expr2):
            temp_result = self.get_temp_var(self.get_type(expr2))
            if op == "+":
                self.set_var(temp_result, expr1, ctx)
                self.add_cmd(
                    f"scoreboard players add #MineScript {temp_result} {self.get_value(expr2)}",
                    ctx)
            elif op == "-":
                self.set_var(temp_result, expr1, ctx)
                self.add_cmd(
                    f"scoreboard players remove #MineScript {temp_result} {self.get_value(expr2)}",
                    ctx)
            elif op == "*":
                self.set_var(temp_result, expr2, ctx)
                self.add_cmd(
                    f"scoreboard players operation #MineScript {temp_result} *= #MineScript {expr1}",
                    ctx)
            elif op == "/":
                temp_var = self.get_temp_var(self.get_type(expr2))
                self.set_var(temp_var, expr2, ctx)
                self.set_var(temp_result, expr1, ctx)
                self.add_cmd(
                    f"scoreboard players operation #MineScript {temp_result} /= #MineScript {temp_var}",
                    ctx)
                self.mark_unused(temp_var)
            elif op == "%":
                temp_var = self.get_temp_var(self.get_type(expr2))
                self.set_var(temp_var, expr2, ctx)
                self.set_var(temp_result, expr1, ctx)
                self.add_cmd(
                    f"scoreboard players operation #MineScript {temp_result} %= #MineScript {temp_var}",
                    ctx)
                self.mark_unused(temp_var)
            self.mark_unused(expr1)
            return temp_result

        elif isinstance(expr2, str) and self.at_compile_time(expr1):
            if op == "+" or op == "*":
                return self.operate(expr2, expr1, op, ctx)
            else:
                temp_result = self.get_temp_var(self.get_type(expr1))
                if op == "-":
                    self.set_var(temp_result, expr1, ctx)
                    self.add_cmd(
                        f"scoreboard players operation #MineScript {temp_result} -= #MineScript {expr2}",
                        ctx)
                elif op == "/":
                    temp_var = self.get_temp_var(self.get_type(expr1))
                    self.set_var(temp_var, expr1, ctx)
                    self.set_var(temp_result, expr2, ctx)
                    self.add_cmd(
                        f"scoreboard players operation #MineScript {temp_result} /= #MineScript {temp_var}",
                        ctx)
                    self.mark_unused(temp_var)
                elif op == "%":
                    temp_var = self.get_temp_var(self.get_type(expr1))
                    self.set_var(temp_var, expr1, ctx)
                    self.set_var(temp_result, expr2, ctx)
                    self.add_cmd(
                        f"scoreboard players operation #MineScript {temp_result} %= #MineScript {temp_var}",
                        ctx)
                    self.mark_unused(temp_var)
                self.mark_unused(expr2)
                return temp_result

        elif isinstance(expr1, str) and isinstance(expr2, str):
            temp_result = self.get_temp_var(self.get_type(expr1))
            self.set_var(temp_result, expr1, ctx)
            self.add_cmd(
                f"scoreboard players operation #MineScript {temp_result} {op}= #MineScript {expr2}",
                ctx)
            self.mark_unused(expr1)
            self.mark_unused(expr2)
            return temp_result

    def visitParentheses(self, ctx):
        return self.visit(ctx.expr())

    def visitVariableDeclaration(self, ctx):
        type_ = ctx.type_.text
        declarations = ctx.variableAssignement()
        for dec in declarations:
            name = dec.WORD().getText()

            suffix = "" if self.igfunc is None or dec.PREFIX(
            ) is not None else "+local"
            if dec.PREFIX() is not None: name = "$" + name

            self.add_var(name, type_ if dec.arr() is None else type_ + "[]",
                         ctx)
            l = dec.expr()
            if l is not None:
                value = self.visit(l)
                self.assert_types_match(value, name, l)
                self.set_var(name + suffix, value, ctx)
                if isinstance(value, str):
                    self.mark_unused(value)

    def visitVariableAssignement(self, ctx):
        name = ctx.WORD().getText()
        if ctx.PREFIX() is not None: name = "$" + name
        self.assert_is_defined(name, ctx)

        suffix = "" if self.igfunc is None or name not in self.local[
            self.igfunc] else "+local"
        if ctx.expr() is not None:
            value = self.visit(ctx.expr())
            if ctx.arr() is None:
                self.assert_types_match(name, value, ctx)
                self.set_var(name + suffix, value, ctx)
            else:
                element = self.visit(ctx.arr().expr())
                if not name.startswith("$"):
                    self.igmemory["_temp"] = self.get_type(name)[:-2]
                    self.assert_types_match("_temp", element, ctx)
                    self.set_arr_element(name + suffix, element, value, ctx)
                    del self.igmemory["_temp"]
                    if isinstance(element, str):
                        self.mark_unused(element)
                else:
                    self.assert_types_match(Literal(0, "int"), element, ctx)
                    self.set_arr_element(name, element, value, ctx)

            if isinstance(value, str):
                self.mark_unused(value)

        if not name.startswith("$"):
            if ctx.arr() is not None and self.is_used(ctx):
                return self.get_arr_element(name + suffix,
                                            self.visit(ctx.arr().expr()), ctx)
            elif ctx.arr() is None and self.is_used_on_condition(ctx):
                temp_result = self.get_temp_var(self.get_type(name))
                self.set_var(temp_result, name + suffix, ctx)
                return temp_result
            else:
                return name + suffix
        else:
            return name

    def visitArray(self, ctx):
        arr = []
        arr_type = None
        for expr in ctx.expr():
            value = self.visit(expr)
            if arr_type is None:
                arr_type = value
            else:
                self.assert_types_match(arr_type, value, expr)
            arr.append(value)
        if self.is_used(ctx):
            temp_result = self.get_temp_var(self.get_type(arr_type) + "[]")
            self.set_var(temp_result,
                         Literal(arr,
                                 self.get_type(arr_type) + "[]"), ctx)
            return temp_result

    def visitFunctionDeclaration(self, ctx):
        name = ctx.WORD().getText()
        self.igfunc = name
        self.local[self.igfunc] = {}

        for arg in self.igfunctions[name]["args"]:
            self.add_var(arg[0], arg[1])

        self.add_var(f"_break_{name}", "int")
        self.igfuncinfo = {"break": f"_break_{name}"}
        self.set_var(f"_break_{name}", Literal(0, "int"), ctx)

        self.prefixes.append(
            f"unless score #MineScript {self.igfuncinfo['break']} matches 1")
        self.visit(ctx.stat())
        self.prefixes.pop(-1)
        self.igfuncinfo = None
        self.igfunc = None

    def visitFunctionCall(self, ctx):
        name = ctx.WORD().getText()
        if name not in self.igfunctions:
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log(f"Undefined function '{name}'", line, char,
                            "error")
            raise CompileTimeException()
        else:
            args = []
            for expr in ctx.expr():
                args.append(
                    (self.visit(expr), expr.start.line, expr.start.column))
            fargs = self.igfunctions[name]["args"]
            if len(args) != len(fargs):
                line = ctx.start.line
                char = ctx.stop.column
                msg = (
                    f"Function '{name}' takes {len(fargs)} arguments, "
                    f"but {len(args)} {'was' if len(args) == 1 else 'were'} given"
                )
                self.logger.log(msg, line, char, "error")
                raise CompileTimeException()
            else:
                for i in range(len(args)):
                    if not self.get_type(args[i][0]) == fargs[i][1]:
                        msg = (
                            f"Argument '{fargs[i][0]}' is of type '{fargs[i][1]}', "
                            f"but '{self.get_type(args[i][0])}' was provided.")
                        self.logger.log(msg, args[i][1], args[i][2], "error")
                        raise CompileTimeException()
                    else:
                        self.set_var(fargs[i][0] + "+local", args[i][0], ctx)
                self.add_cmd(f"function {self.name}:{name}", ctx)

            if "return" in self.igfunctions[name]:
                return self.igfunctions[name]["return"]

    def visitReturnStatement(self, ctx):
        if self.igfunc is None:
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log("Return outside function", line, char, "error")
            raise CompileTimeException()
        else:
            if "return" in self.igfunctions[self.igfunc]:
                if ctx.expr() is None:
                    line = ctx.stop.line
                    char = ctx.stop.column
                    self.logger.log("No return value for non-void function",
                                    line, char, "error")
                    raise CompileTimeException()
                else:
                    value = self.visit(ctx.expr())
                    self.assert_types_match(
                        self.igfunctions[self.igfunc]["return"], value,
                        ctx.expr())

                    self.set_var(self.igfunctions[self.igfunc]["return"],
                                 value, ctx)
                    self.add_cmd(
                        f"scoreboard players set #MineScript {self.igfuncinfo['break']} 1",
                        ctx)

                    if isinstance(value, str):
                        self.mark_unused(value)
            else:
                if ctx.expr() is not None:
                    line = ctx.stop.line
                    char = ctx.stop.column
                    self.logger.log("Void function returns a value", line,
                                    char, "error")
                    raise CompileTimeException()
                else:
                    self.add_cmd(
                        f"scoreboard players set #MineScript {self.igfuncinfo['break']} 1",
                        ctx)

    def visitLiteral(self, ctx):
        if ctx.CHAR() is not None:
            return Literal(eval(f"ord({ctx.CHAR().getText()})"), "char")
        elif ctx.NUMBER() is not None:
            return Literal(int(ctx.NUMBER().getText()), "int")
        elif ctx.STRING() is not None:
            return Literal(list(ctx.STRING().getText()[1:-1]), "char[]")

    def visitVariableIncrementPos(self, ctx):
        name = ctx.WORD().getText()
        if ctx.PREFIX(): name = "$" + name
        self.assert_is_defined(name, ctx)

        suffix = "" if self.igfunc is None or name not in self.local[
            self.igfunc] else "+local"
        used = self.is_used(ctx)
        if used:
            temp_result = self.get_temp_var(self.get_type(name))
            self.set_var(temp_result, name + suffix, ctx)
        self.add_cmd(f"scoreboard players add #MineScript {name}{suffix} 1",
                     ctx)
        if used:
            return temp_result

    def visitVariableIncrementPre(self, ctx):
        name = ctx.WORD().getText()
        if ctx.PREFIX(): name = "$" + name
        self.assert_is_defined(name, ctx)

        suffix = "" if self.igfunc is None or name not in self.local[
            self.igfunc] else "+local"
        self.add_cmd(f"scoreboard players add #MineScript {name}{suffix} 1",
                     ctx)
        return name + suffix

    def visitVariableDecrementPos(self, ctx):
        name = ctx.WORD().getText()
        if ctx.PREFIX(): name = "$" + name
        self.assert_is_defined(name, ctx)

        suffix = "" if self.igfunc is None or name not in self.local[
            self.igfunc] else "+local"
        used = self.is_used(ctx)
        if used:
            temp_result = self.get_temp_var(self.get_type(name))
            self.set_var(temp_result, name + suffix, ctx)
        self.add_cmd(f"scoreboard players remove #MineScript {name}{suffix} 1",
                     ctx)
        if used:
            return temp_result

    def visitVariableDecrementPre(self, ctx):
        name = ctx.WORD().getText()
        if ctx.PREFIX(): name = "$" + name
        self.assert_is_defined(name, ctx)

        suffix = "" if self.igfunc is None or name not in self.local[
            self.igfunc] else "+local"
        self.add_cmd(f"scoreboard players remove #MineScript {name}{suffix} 1",
                     ctx)
        return name

    def visitVariableComparison(self, ctx):
        expr1, expr2 = ctx.expr()
        expr1 = self.visit(expr1)
        expr2 = self.visit(expr2)
        if self.is_used(ctx):
            return self.compare(expr1, expr2, ctx.type_.text, ctx)

    def visitVariableOperation(self, ctx):
        expr1, expr2 = ctx.expr()
        expr1 = self.visit(expr1)
        expr2 = self.visit(expr2)
        if self.is_used(ctx):
            return self.operate(expr1, expr2, ctx.type_.text, ctx)

    def visitIfStatement(self, ctx):
        condition = self.visit(ctx.expr())
        if isinstance(condition, str):
            self.prefixes.append(f"if score #MineScript {condition} matches 1")
            self.visit(ctx.stat(0))
            self.prefixes.pop(-1)
            if len(ctx.stat()) > 1:
                self.prefixes.append(
                    f"unless score #MineScript {condition} matches 1")
                self.visit(ctx.stat(1))
                self.prefixes.pop(-1)
            self.mark_unused(condition)
        else:
            if condition.value:
                self.visit(ctx.stat(0))
            elif len(ctx.stat()) > 1:
                self.visit(ctx.stat(1))

    def visitForStatement(self, ctx):
        if len(ctx.expr()) == 3:
            init, condition, update = ctx.expr()
        else:
            init = ctx.variableDeclaration()
            condition, update = ctx.expr()
        init_value = self.visit(init)
        name = f"_loop{self.loops}"
        condition_value = self.visit(condition)

        break_var = self.get_temp_var("int")
        self.set_var(break_var, Literal(1, "int"), ctx)

        always_true = False
        if isinstance(condition_value, str):
            self.add_cmd(
                f"execute unless score #MineScript {condition_value} matches 0 run function {self.name}:{name}",
                ctx)
        elif isinstance(condition_value, Literal):
            if not condition_value.value:
                line = condition.start.line
                char = condition.start.column
                self.logger.log("Condition is always false", line, char,
                                "warning")
                self.mark_unused(break_var)
                return
            line = condition.start.line
            char = condition.start.column
            self.logger.log("Condition is always true", line, char, "warning")
            self.add_cmd(f"function {self.name}:{name}", ctx)
            always_true = True

        self.start_loop(name, break_var)
        self.visit(ctx.stat())
        update_value = self.visit(update)
        if always_true:
            self.add_cmd(f"function {self.name}:{name}", ctx)
        else:
            if isinstance(condition_value, str):
                self.mark_unused(condition_value)
            condition_value = self.visit(condition)
            self.add_cmd(
                f"execute unless score #MineScript {condition_value} matches 0 run function {self.name}:{name}",
                ctx)
        self.end_loop()

        if isinstance(init_value, str):
            self.mark_unused(init_value)
        if isinstance(condition_value, str):
            self.mark_unused(condition_value)
        if isinstance(update_value, str):
            self.mark_unused(update_value)

    def visitWhileStatement(self, ctx):
        condition = ctx.expr()
        condition_value = self.visit(condition)
        name = f"_loop{self.loops}"

        break_var = self.get_temp_var("int")
        self.set_var(break_var, Literal(1, "int"), ctx)

        always_true = False
        if isinstance(condition_value, Literal):
            if condition_value.value:
                self.add_cmd(f"function {self.name}:{name}", ctx)
                always_true = True
            else:
                line = condition.start.line
                char = condition.start.column
                self.logger.log("Condition is always false", line, char,
                                "warning")
                return
        else:
            self.add_cmd(
                f"execute unless score #MineScript {condition_value} matches 0 run function {self.name}:{name}",
                ctx)

        self.start_loop(name, break_var)
        self.visit(ctx.stat())
        if always_true:
            self.add_cmd(f"function {self.name}:{name}", ctx)
        else:
            if isinstance(condition_value, str):
                self.mark_unused(condition_value)
            condition_value = self.visit(condition)
            self.add_cmd(
                f"execute unless score #MineScript {condition_value} matches 0 run function {self.name}:{name}",
                ctx)
        self.end_loop()

        if isinstance(condition_value, str):
            self.mark_unused(condition_value)

    def visitBreakStatement(self, ctx):
        if len(self.loop) == 0:
            line = ctx.start.line
            char = ctx.start.column
            self.logger.log("Break statement is outside of a loop", line, char,
                            "error")
            raise CompileTimeException()

        self.set_var(self.break_var[-1], Literal(0, "int"), ctx)

    def visitPrintStatement(self, ctx):
        if len(ctx.expr()) < 3:
            line = ctx.start.line
            char = ctx.stop.column
            msg = (
                f"Built-in function 'print' takes at least 3 arguments, "
                f"but only {len(ctx.expr())} {'was' if len(ctx.expr()) == 1 else 'were'} given"
            )
            self.logger.log(msg, line, char, "error")
            raise CompileTimeException()
        selector, color, *args = ctx.expr()
        selector_value = self.visit(selector)
        color_value = self.visit(color)
        if self.at_compile_time(selector_value) and self.get_type(
                selector_value) == "char[]":
            if self.at_compile_time(color_value) and self.get_type(
                    color_value) == "char[]":
                pass
            else:
                line = color.start.line
                char = color.stop.column
                self.logger.log(
                    "The second argument of 'print' must be a string "
                    "evaluated at compile time.", line, char, "error")
                raise CompileTimeException()
        else:
            line = selector.start.line
            char = selector.stop.column
            self.logger.log(
                "The first argument of 'print' must be a string "
                "evaluated at compile time.", line, char, "error")
            raise CompileTimeException()

        color_text = f"\"color\":\"{self.get_value(color_value)}\""
        command = ""
        for arg in args:
            arg_value = self.visit(arg)
            if self.at_compile_time(arg_value):
                command += ',{"text":'
                if self.get_type(arg_value) == "char":
                    command += f'"{chr(self.get_value(arg_value))}"'
                else:
                    command += f'"{str(self.get_value(arg_value))}"'
                command += ", " + color_text + "}"
            else:
                if self.get_type(arg_value) == "int":
                    command += ',{"score":{"name":"#MineScript","objective":"' + arg_value + '"}}'
                self.mark_unused(arg_value)
        self.add_cmd(f"tellraw {selector_value.value} [{command[1:]}]", ctx)

    def visitMcCommand(self, ctx):
        pass

    def visitCast(self, ctx):
        expr = self.visit(ctx.expr())
        from_type = self.get_type(expr)
        to_type = ctx.type_.text
        if from_type == "char" and to_type == "int":
            if isinstance(expr, Literal):
                expr.type = "int"
                return expr
            else:
                temp_result = self.get_temp_var("int")
                self.set_var(temp_result, expr, ctx)
                return temp_result
        elif from_type == "int" and to_type == "char":
            if isinstance(expr, Literal):
                expr.type = "char"
                expr.value %= 256
                return expr
            else:
                temp_var = self.get_temp_var("int")
                self.set_var(temp_var, Literal(255, "int"), ctx)
                temp_result = self.get_temp_var("char")
                self.set_var(temp_result, expr, ctx)
                self.add_cmd(
                    f"scoreboard players operation #MineScript {temp_result} %= #MineScript {temp_var}",
                    ctx)
                return temp_result
Ejemplo n.º 3
0
class Manager(object):
    def __init__(self, term, num_divisions=1, logfilepath=None):
        self.term = term
        self.screens = []

        self.logger = Logger(logfilepath)

        if num_divisions > 4:
            raise ValueError("Terminal space can only be divided into a maximum of 4 parts.")
        if num_divisions <= 0:
            raise ValueError("Does not make sense to divide terminal into 0 or negative parts.")
        else:
            self.num_divisions = 1

        if len(self.screens) < 4:
            num_screens_to_make = 4 - len(self.screens)
            for i in range(num_screens_to_make):
                self.screens.append(Screen(np.zeros(2, dtype=np.int64), np.array([term.width, term.height]), term))

        self.focus_cursor = None
        self.set_focus_screen(0)

        self.init_command_bar_attrs()

        self.divide_display_space()

        self.exit_signal_received = False

    def set_status(self, status):
        echo_at_cell(csr.home(self.status_bar_cursor, logger=self.logger), self.term.clear_eol)
        if status == EDITING:
            fscreen = self.focus_screen
            min_row, min_col = fscreen.origin
            max_row, max_col = fscreen.origin + fscreen.display_size - 1
            offset_row, offset_col = fscreen.offset
            fcursor = self.focus_cursor
            self.status_descriptor = "{} | ({}:{}:{}, {}:{}:{}) + ({}, {})".format(
                self.focus_screen_index,
                min_row,
                fcursor.r,
                max_row,
                min_col,
                fcursor.c,
                max_col,
                offset_row,
                offset_col,
            )
        elif status == WAITING_FOR_CMD:
            self.status_descriptor = "~"

    def init_command_bar_attrs(self):
        self.status_bar_height = 1
        self.input_bar_height = 1
        self.command_bar_height = self.status_bar_height + self.input_bar_height + 1

        self.command_bar_separator_cursor = csr.Cursor(self.term.height - self.command_bar_height, 0, self.term)

        self.status_bar_cursor = csr.Cursor(self.term.height - self.command_bar_height + 1, 0, self.term)
        self.status_bar_text = self.term.yellow(u"status: ")
        self.set_status(EDITING)

        self.reading_command = False
        self.input_bar_cursor = csr.Cursor(self.term.height - self.command_bar_height + 2, 0, self.term)
        self.input_bar_text = u""

        self.command = None

    def add_screen(self, new_screen):
        self.screens.append(new_screen)

    def delete_screen(self, screen_index):
        del self.screens[screen_index]

    def set_focus_screen(self, new_focus_index):
        if self.focus_cursor != None:
            self.cursors[self.focus_screen_index] = self.focus_cursor
            echo_remove_cursor(self.focus_cursor, self.focus_screen)

        self.focus_screen_index = new_focus_index
        self.cursors = [
            csr.Cursor(int(a_screen.origin[0]), int(a_screen.origin[1]), self.term) for a_screen in self.screens
        ]
        self.focus_cursor = self.cursors[self.focus_screen_index]
        self.focus_screen = self.screens[self.focus_screen_index]
        self.new_focus_cursor = self.focus_cursor

    def get_focus_cursor_and_screen(self):
        return self.focus_cursor, self.focus_screen

    def assign_screen_to_division(self, screen_index, division_index):
        temp_screen = self.screens[division_index]
        self.screens[division_index] = self.screens[screen_index]
        self.screens[screen_index] = temp_screen

        self.num_divisions = division_index + 1
        self.divide_display_space()

    def divide_display_space(self, num_divisions=1):
        self.num_divisions = num_divisions
        display_width = self.term.width
        display_height = self.term.height - self.command_bar_height

        screen_origins = [None] * 4
        screen_display_sizes = [None] * 4

        self.vertical_separator_col = None
        self.horizontal_separator_row = None
        self.horizontal_separator_start_col = None
        self.horizontal_separator_end_col = None

        if self.num_divisions == 1:
            screen_origins[0] = np.array([0, 0], dtype=np.int64)
            screen_display_sizes[0] = np.array([display_height, display_width], dtype=np.int64)

        if self.num_divisions == 2:
            self.vertical_separator_col = display_width // 2

            screen_origins[0] = np.array([0, 0], dtype=np.int64)
            screen_origins[1] = np.array([0, self.vertical_separator_col + 1], dtype=np.int64)

            screen_display_sizes[0] = np.array([display_height, self.vertical_separator_col], dtype=np.int64)
            screen_display_sizes[1] = np.array(
                [display_height, display_width - screen_display_sizes[0][1]], dtype=np.int64
            )

        if self.num_divisions == 3:
            self.vertical_separator_col = display_width // 2
            self.horizontal_separator_row = display_height // 2
            self.horizontal_separator_start_col = self.vertical_separator_col
            self.horizontal_separator_end_col = display_width

            screen_origins[0] = np.array([0, 0], dtype=np.int64)
            screen_origins[1] = np.array([0, self.vertical_separator_col + 1], dtype=np.int64)
            screen_origins[2] = np.array(
                [self.horizontal_separator_row + 1, self.vertical_separator_col + 1], dtype=np.int64
            )

            screen_display_sizes[0] = np.array([display_height, self.vertical_separator_col], dtype=np.int64)
            screen_display_sizes[1] = np.array(
                [self.horizontal_separator_row, display_width - screen_display_sizes[0][1] - 1], dtype=np.int64
            )
            screen_display_sizes[2] = np.array(
                [display_height - screen_display_sizes[1][0] - 1, screen_display_sizes[1][1]], dtype=np.int64
            )

        if self.num_divisions == 4:
            self.vertical_separator_col = display_width // 2
            self.horizontal_separator_row = display_height // 2
            self.horizontal_separator_start_col = 0
            self.horizontal_separator_end_col = display_width

            screen_origins[0] = np.zeros(2)
            screen_origins[1] = np.array([0, self.vertical_separator_col + 1], dtype=np.int64)
            screen_origins[2] = np.array(
                [self.horizontal_separator_row + 1, self.vertical_separator_col + 1], dtype=np.int64
            )
            screen_origins[3] = np.array([self.horizontal_separator_row + 1, 0])

            screen_display_sizes[0] = np.array(
                [self.horizontal_separator_row, self.vertical_separator_col], dtype=np.int64
            )
            screen_display_sizes[1] = np.array(
                [self.horizontal_separator_row, display_width - screen_display_sizes[0][1] - 1], dtype=np.int64
            )
            screen_display_sizes[2] = np.array(
                [display_height - screen_display_sizes[1][0] - 1, screen_display_sizes[1][1]], dtype=np.int64
            )
            screen_display_sizes[3] = np.array([screen_display_sizes[2][0], self.vertical_separator_col])

        if self.logger != None:
            self.logger.add_tag("divide_display_space")
        for i in xrange(self.num_divisions):
            origin = screen_origins[i]
            display_size = screen_display_sizes[i]

            if type(origin) == np.ndarray and type(display_size) == np.ndarray:
                if self.logger != None:
                    self.logger.log(
                        "setting screen {} geometry: origin={}, display_size={}".format(i, origin, display_size)
                    )
                self.screens[i].set_geometry(origin, display_size)
            else:
                break
        if self.logger != None:
            self.logger.remove_tag()

        self.set_focus_screen(0)
        self.draw()

    def write_cell(self, cursor, write_str, layer_index=0):
        self.screens[self.focus_screen_index].write_cell(cursor, write_str, layer_index=layer_index)

    def read_command(self):
        self.reading_command = True
        self.draw_command_bar(status=WAITING_FOR_CMD)

        if self.logger != None:
            self.logger.add_tag("read_command")

        self.draw_command_bar(status=WAITING_FOR_CMD)

        self.input_bar_cursor = csr.right_of(self.input_bar_cursor, 5)
        inp, exit_dict = readline(self.input_bar_cursor, max_input=self.term.width - 5)

        if exit_dict["enter"]:
            self.command = [substr for substr in inp.split(" ") if substr not in ["", None]]

        self.set_status(EDITING)
        self.input_bar_text = u""
        self.input_bar_cursor = csr.home(self.input_bar_cursor)
        echo_at_cell(self.input_bar_cursor, self.term.clear_eol)
        self.reading_command = False
        if self.logger != None:
            self.logger.remove_tag()

    def draw_command_bar(self, status=EDITING):
        # draw separator
        echo_at_cell(self.command_bar_separator_cursor, DOUBLE_HORIZONTAL * self.term.width)

        # write status bar
        self.set_status(status)
        if status == WAITING_FOR_CMD:
            self.input_bar_text = self.term.bold_white(u"cmd: ")
            self.input_bar_cursor = csr.home(self.input_bar_cursor)
            echo_at_cell(self.input_bar_cursor, self.input_bar_text)

        echo_at_cell(self.status_bar_cursor, self.status_bar_text + self.status_descriptor)
        echo_at_cell(self.input_bar_cursor, self.input_bar_text)

    def draw_separators(self):
        # print intersection
        if self.horizontal_separator_end_col != None and self.vertical_separator_col != None:
            if self.num_divisions == 3:
                echo_at_cell(
                    csr.Cursor(self.horizontal_separator_row, self.vertical_separator_col, self.term),
                    HEAVY_THREE_INTERSECTION,
                )
            if self.num_divisions == 4:
                echo_at_cell(
                    csr.Cursor(self.horizontal_separator_row, self.vertical_separator_col, self.term),
                    HEAVY_FOUR_INTERSECTION,
                )

        # print vertical separators
        if self.vertical_separator_col != None:
            display_height = self.term.height - self.command_bar_height
            c = self.vertical_separator_col
            reserved_r = self.horizontal_separator_row
            term = self.term

            for i in xrange(display_height):
                if i == reserved_r:
                    continue
                else:
                    echo_at_cell(csr.Cursor(i, c, term), HEAVY_VERTICAL)

        # print horizontal separators
        if self.horizontal_separator_row != None:
            reserved_c = self.vertical_separator_col
            r = self.horizontal_separator_row
            term = self.term

            for i in xrange(self.horizontal_separator_start_col, self.horizontal_separator_end_col):
                if i == reserved_c:
                    continue
                else:
                    echo_at_cell(csr.Cursor(r, i, term), HEAVY_HORIZONTAL)

    def draw_cursor(self):
        echo_cursor(self.focus_cursor, self.focus_screen)

    def draw(self, status=EDITING):
        self.draw_cursor()
        self.draw_command_bar(status=status)
        self.draw_separators()

        for i in xrange(4):
            if self.screens[i].invalid:
                self.screens[i].draw()

    def clear(self):
        echo(self.term.clear)

    def handle_command(self):
        command = self.command

        if self.logger != None:
            self.logger.add_tag("cmd")

        if type(command) == list:
            if command[0] == "numdivisions":
                if self.logger != None:
                    self.logger.log("handle_command: numdivisions {}".format(command[1]))

                self.divide_display_space(int(command[1]))
                self.invalid = True
            elif command[0] == "quit":
                self.exit_signal_received = True

                if self.logger != None:
                    self.logger.log("handle_command: exit_signal_received")

            elif command[0] == "assigndiv":
                self.assign_screen_to_division(int(command[1]), int(command[2]))
                self.invalid = True

                if self.logger != None:
                    self.logger.log("handle_command: assigndiv {} {}".format(command[1], command[2]))

            elif command[0] == "setfocus":
                self.set_focus_screen(int(command[1]))
                self.invalid = True

                if self.logger != None:
                    self.logger.log("handle_command: setfocus {}".format(command[1]))

            elif command[0] == "save":
                self.focus_screen.save(command[1])

                if self.logger != None:
                    self.logger.log("handle_command: save {}".format(command[1]))

        if self.logger != None:
            self.logger.remove_tag()
        self.command = None

    def handle_input(self, inp):
        term = self.term

        if inp == chr(3) or self.exit_signal_received:
            if self.logger != None:
                self.logger.log("handle_input: exiting")
            # ^c exits
            return -1
        elif self.command != None:
            self.handle_command()

            if self.logger != None:
                self.logger.log("handle_input: calling handle_command, command: {}".format(self.command))
            return 0

        elif inp.code == term.KEY_ESCAPE:
            self.read_command()

            if self.logger != None:
                self.logger.log("handle_input: called read_command, command: {}".format(self.command))

            return 0
        elif inp == chr(12):
            # ^l refreshes
            self.draw()

            if self.logger != None:
                self.logger.log("handle_input: refreshing, calling draw")

            return 0
        elif inp.code in [term.KEY_BACKSPACE, term.KEY_DELETE]:
            echo_remove_cursor(self.focus_cursor, self.focus_screen, logger=self.logger)
            self.focus_cursor = csr.left_of(self.focus_cursor, 1, screen=self.focus_screen, logger=self.logger)
            self.focus_screen.delete_cell(self.focus_cursor, logger=self.logger)

            return 0
        else:
            echo_remove_cursor(self.focus_cursor, self.focus_screen, logger=self.logger)
            self.focus_cursor, move_made = csr.lookup_move(
                inp.code, self.focus_cursor, screen=self.focus_screen, logger=self.logger
            )
            if move_made:
                return 0

        if input_filter(inp):
            self.logger.log("Attempting to write {} to screen.".format(str(inp)))
            self.focus_screen.write_cell(self.focus_cursor, str(inp), logger=self.logger)
            echo_remove_cursor(self.focus_cursor, self.focus_screen, logger=self.logger)
            self.focus_cursor = csr.right_of(self.focus_cursor, 1, screen=self.focus_screen, logger=self.logger)
            return 0
        else:
            self.logger.log("{} not considered input.".format(str(inp)))

        return 1