diff --git a/mlogpp/arrows.py b/mlogpp/arrows.py index 92d9936..9cca011 100644 --- a/mlogpp/arrows.py +++ b/mlogpp/arrows.py @@ -1,2 +1,5 @@ def generate(pos: int, len_: int): + """ + generate error arrows + """ return (" " * pos) + ("^" * len_) diff --git a/mlogpp/cli.py b/mlogpp/cli.py index 4c35676..2afc062 100644 --- a/mlogpp/cli.py +++ b/mlogpp/cli.py @@ -9,6 +9,7 @@ from .linker import Linker from . import __version__ +# input/output method class IOMethod(Enum): FILE = 0 STD = 1 @@ -40,56 +41,82 @@ class IOMethod(Enum): verbose = False +# parse arguments for k, v in vars(args).items(): if v: if k.startswith("output"): + # output method + omethod = IOMethod.FILE if k.endswith("file") else IOMethod.STD if k.endswith("stdout") else IOMethod.CLIP if omethod == IOMethod.FILE: ofile = v elif k.startswith("optimize"): + # optimization level + optlevel = int(k[-1]) elif k == "verbose": + # verbose + verbose = v +# check if files exist for fn in args.file: + # exists or is `@clip` if not os.path.isfile(fn) and fn != "@clip": print(f"Error: input file \"{fn}\" does not exist") sys.exit(1) +# read files datas = [] for fn in args.file: if fn == "@clip": + # clipboard + datas.append((fn, pyperclip.paste())) + else: + # file + with open(fn, "r") as f: datas.append((fn, f.read())) +# optimization level presets to pass to the optimizer optimization_levels = { 0: {"enable": False}, 1: {"unused": False}, 2: {} } +# compile all files outs = [] for data in datas: - if data[0].endswith(".mind"): + # skip if already compiler + if data[0].endswith(".mind") or data[0].endswith(".masm"): outs.append(data[1]) continue out = Lexer.preprocess(Lexer.lex(Lexer.resolve_includes(data[1]))) out = Parser().parse(out) out = Generator().generate(out, optimization_levels[optlevel]) + # add to compiled files outs.append(out) +# link the compiled files if len(outs) > 1: out = Linker.link(outs).strip() else: out = outs[0].strip() if omethod == IOMethod.FILE: + # output to file + with open(ofile, "w+") as f: f.write(out) + elif omethod == IOMethod.STD: + # output to stdout + + # check if line numbers should be prined if vars(args)["lines"]: lines = out.splitlines() maxline = len(str(len(lines))) @@ -100,7 +127,10 @@ class IOMethod(Enum): if verbose: print() + elif omethod == IOMethod.CLIP: + # output to clipboard + pyperclip.copy(out) if verbose: diff --git a/mlogpp/error.py b/mlogpp/error.py index 9bdba2f..097418b 100644 --- a/mlogpp/error.py +++ b/mlogpp/error.py @@ -4,10 +4,16 @@ from . import arrows from .formatting import Format +# debug flags PARSE_ERROR_DEBUG = False LINK_ERROR_DEBUG = False def parse_error(tok: Token, msg: str) -> None: + """ + raise parser error + """ + + # debug if PARSE_ERROR_DEBUG: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) @@ -19,6 +25,11 @@ def parse_error(tok: Token, msg: str) -> None: sys.exit(1) def link_error(pos: Position, msg: str) -> None: + """ + raise linker error + """ + + # debug if LINK_ERROR_DEBUG: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) diff --git a/mlogpp/formatting.py b/mlogpp/formatting.py index 9ba74a8..9484f89 100644 --- a/mlogpp/formatting.py +++ b/mlogpp/formatting.py @@ -1,4 +1,8 @@ class Format: + """ + formatting codes + """ + RED = "\033[91m" BLUE = "\033[94m" GREEN = "\033[92m" diff --git a/mlogpp/functions.py b/mlogpp/functions.py index ccf3acd..751b7e3 100644 --- a/mlogpp/functions.py +++ b/mlogpp/functions.py @@ -1,6 +1,10 @@ def gen_signature(name: str, params: list): + """ + generate a function signature + """ return f"{name}:{len(params)}" +# native functions native = [ "read", "write", "draw", "drawflush", @@ -15,6 +19,7 @@ def gen_signature(name: str, params: list): "ubind", "ucontrol", "uradar", "ulocate" ] +# number of parameters to native functions native_params = { "read": 3, "write": 3, "draw": 7, "drawflush": 1, @@ -29,6 +34,7 @@ def gen_signature(name: str, params: list): "ubind": 1, "ucontrol":6, "uradar": 6, "ulocate": 8 } +# native subcommands native_sub = { "draw": { "clear": 3, @@ -77,6 +83,7 @@ def gen_signature(name: str, params: list): } } +# builtin operators builtin = [ "mod", "pow", @@ -91,6 +98,7 @@ def gen_signature(name: str, params: list): "len" ] +# number of parameters to builtin operators builtin_params_default = 1 builtin_params = { "mod": 2, @@ -103,6 +111,8 @@ def gen_signature(name: str, params: list): "len": 2 } +# special keywords keywords = ["if", "else", "while", "for", "function", "repeat"] +# special identifiers special = native + builtin + keywords diff --git a/mlogpp/generator.py b/mlogpp/generator.py index 31a654f..5cfdc96 100644 --- a/mlogpp/generator.py +++ b/mlogpp/generator.py @@ -4,36 +4,55 @@ from .gerror import gen_error, gen_undefined_error from . import functions, gerror +# generator regexes GEN_REGEXES = { + # label "LABEL": re.compile(r"^<[a-zA-Z_@][a-zA-Z_0-9]*$"), + # jump "JUMP": re.compile(r"^>[a-zA-Z_@][a-zA-Z_0-9]*$"), + # conditional jump "CJMP": re.compile(r"^>[a-zA-Z_@][a-zA-Z_0-9]* \!?[a-zA-Z_0-9@]+$"), + # operator jump "EJMP": re.compile(r"^>[a-zA-Z_@][a-zA-Z_0-9]* [a-zA-Z_0-9@\.]+ ((==)|(\!=)|(>=)|(<=)|>|<) [a-zA-Z_0-9@\.]+$"), + # setting temporary variable "TMPS": re.compile(r"^set __tmp[0-9]+ .+$"), + # sensor to temporary variable "TMPSE": re.compile(r"^sensor __tmp[0-9]+ \S+ \S+$"), + # copying temporary variable to variable "TMPSA": re.compile(r"^set [a-zA-Z_@][a-zA-Z_0-9]* __tmp[0-9]+$"), + # operation "TMPOP": re.compile(r"^op [a-zA-Z]+( [a-zA-Z_@][a-zA-Z_0-9]*){2} .+$"), + # native jump "MJUMP": re.compile(r"^jump [0-9]+ [a-zA-Z]+ [a-zA-Z_0-9@]+ [a-zA-Z_0-9@]+$"), + # precalculatable operation "MCALC": re.compile(r"^op [a-zA-Z]+ [a-zA-Z_0-9@]+ [0-9]+(\.[0-9]+)? [0-9]+(\.[0-9]+)?$"), + # variable assignment "VARA": re.compile(r"^(set \S+ \S+)|(op [a-zA-Z]+( \S+){3})$"), + # number "NUM": re.compile(r"^[0-9]+(\.[0-9]+)?$"), + # variable with attribute "IATTR": re.compile(r"^[a-zA-Z_@][a-zA-Z_0-9]*\.[a-zA-Z_@][a-zA-Z_0-9]*$"), + # temporary comparation "TCOMP": re.compile(r"^op ((equal)|(notEqual)|(greaterThan)|(lessThan)|(greaterThanEq)|(lessThanEq)) __tmp[0-9]+ \S+ \S+$"), + # temporary jump "TJUMP": re.compile(r"^>__mpp[0-9]+ !__tmp[0-9]+$"), + # variable that can be undefined "VARU": re.compile(r"^[a-zA-Z_][a-zA-Z_0-9]*$"), + # subcall function name "SFUNC": re.compile(r"^[a-z]+\.[a-z]+$") } +# precalculation functions PRECALC = { "add": lambda a, b: a + b, "sub": lambda a, b: a - b, @@ -67,6 +86,7 @@ # equal and notEqual are not implemented because they use type conversion } +# native function return positions NATIVE_RETURN_POS = { "set": 0, "op": 1, @@ -76,6 +96,7 @@ "lookup": 1 } +# native subcommands return positions NATIVE_SUB_RETURN_POS = { "ucontrol.getBlock": 2, "ucontrol.within": 3, @@ -86,6 +107,7 @@ "lookup.liquid": 0 } +# native function constant inputs NATIVE_CONST_INPUTS = { "draw": [0], "control": [0], @@ -98,6 +120,7 @@ "ulocate": [0, 1] } +# parameters that cannot be temporary PARAM_NO_TMP = { "set": [0], "op": [1], @@ -113,6 +136,7 @@ "ulocate": [4, 5, 6, 7] } +# replace jump conditions for optimizations JUMP_CONDITIONS_REPLACE = { "equal": "!=", "notEqual": "==", @@ -122,6 +146,7 @@ "lessThanEq": ">" } +# jump condition names JUMP_CONDITION = { "==": "equal", "!=": "notEqual", @@ -132,47 +157,88 @@ } class Generator: + """ + generates mindustry assembly from mlog++ + """ + def generate(self, node: AST, optimize_options: dict = None) -> str: + """ + generate mindustry assembly from mlog++ + """ + + # temporary variable counter self.tmpv = 0 + # temporary label counter self.tmpl = 0 + # function stack (for returns) self.func_stack = [] + # loop stack (for break/continue) self.loop_stack = [] + # disable generation of temporary variables self.no_generate_tmp = False + # list of defined variables self.var_list = set(self._generate_var_list(node) + ["true", "false", "null"]) + # generate function list funcs = [] for v in self.var_list: + # check for function signature if ":" in v: spl = v.split(":", 1) + # check for redefinition if spl[0] in funcs: gen_error(None, f"Redefinition of function \"{spl[0]}\"") + # add to function list funcs.append(spl[0]) + # generate and join all code nodes code = self._code_node_join(node) - + + # optimize and postprocess return self.postprocess(self.optimize(code, optimize_options)) def _code_join(self, codel: list, to_pos: int = None) -> str: + """ + generate and join nodes + """ + code = "" + # iterate over nodes in a range of 0:to_pos for c in codel[:(to_pos + 1 if to_pos is not None else len(codel))]: + # generate code r = self._generate(c) + # append to code code += (r if type(r) == str else r[0]) + "\n" + # strip generated code return "\n".join([l for l in code.strip().splitlines() if l.strip()]) def _code_node_join(self, node: Node, to_pos: int = None) -> str: + """ + generate and join code nodes + """ + return self._code_join(node.code, to_pos) def optimize(self, code: str, optimize_options: dict = None) -> str: + """ + optimize generated code + """ + + # default options options = { + # enable optimizations "enable": True, + # remove unused variables "unused": True } + # override default options if optimize_options is not None: for k, v in optimize_options.items(): options[k] = v + # exit if not enabled if not options["enable"]: return code @@ -215,8 +281,12 @@ def optimize(self, code: str, optimize_options: dict = None) -> str: return tmp def _single_tmp_optimize(self, code: str, n_lines: int) -> str: - uses = {} + """ + optimize single use temporary variables + """ + # count number of uses + uses = {} for i in range(1, self.tmpv + 1): c = len(re.findall(f"__tmp{i}\\D", code)) @@ -355,23 +425,35 @@ def _negative_optimize(self, code: str) -> str: return tmp, found def postprocess(self, code: str) -> str: + """ + postprocess generated code + """ + + # exit if empty if not code.strip(): return code + # resolve native code, resolve labels, remove unnecessary `set` instructions tmp = "" labels = {} lc = 0 - for i, ln in enumerate(code.splitlines()): if ln.startswith("."): + # native code + ln = ln[1:] if GEN_REGEXES["LABEL"].fullmatch(ln): + # labels + labels[ln[1:]] = i - lc + # increase counter lc += 1 continue - + if GEN_REGEXES["VARA"]: + # unnecessary `set` instructions + spl = ln.split() if spl and spl[0] == "set": if spl[1] == spl[2]: @@ -380,6 +462,7 @@ def postprocess(self, code: str) -> str: tmp += ln + "\n" + # resolve jumps tmp2 = "" for i, ln in enumerate(tmp.splitlines()): name = "" @@ -388,14 +471,22 @@ def postprocess(self, code: str) -> str: invert = False if GEN_REGEXES["JUMP"].fullmatch(ln): + # jump + name = ln[1:] + elif GEN_REGEXES["CJMP"].fullmatch(ln): + # conditional jump + spl = ln.split() name = spl[0][1:] cond = True invert = spl[1].startswith("!") cvar = spl[1][1:] if invert else spl[1] + elif GEN_REGEXES["EJMP"].fullmatch(ln): + # operator jump + spl = ln.split() name = spl[0][1:] op1 = spl[1] @@ -420,6 +511,7 @@ def postprocess(self, code: str) -> str: else: gen_error(None, f"Unknown label \"{name}\"") + # move native jumps from end of code to 0 tmp = "" lnc = len(tmp2.strip().splitlines()) for i, ln in enumerate(tmp2.splitlines()): @@ -430,52 +522,72 @@ def postprocess(self, code: str) -> str: tmp += ln + "\n" + # remove last line of code jump to 0 if tmp.splitlines()[-1].startswith("jump 0"): tmp = "\n".join(tmp.splitlines()[:-1]) return tmp.strip() def _generate(self, node: Node): + """ + generate code for a node + """ + t = type(node) if t == Node: gen_error(node, "Invalid node") + elif t == CodeNode: return "\n".join([str(self._generate(c)) for c in node.code]) + elif t == ValueNode: + # obtain output variable if self.no_generate_tmp: var = str(node.value) else: var = self.get_tmp_var() + # check if variable with attribute if GEN_REGEXES["IATTR"].fullmatch(node.value): spl = node.value.split(".") return f"sensor {var} {spl[0]} @{spl[1]}", var + # check if undefined if type(node.value) == str and not (node.value.startswith("\"") and node.value.endswith("\"")) and node.value != "_" and node.value[-1] not in "0123456789": if GEN_REGEXES["VARU"].fullmatch(node.value) and node.value not in self.var_list and node.value not in functions.builtin: gen_undefined_error(node, node.value) return f"set {var} {node.value}" if not self.no_generate_tmp else "", var + elif t == IndexNode: var = self.get_tmp_var() tmp, var_ = self._generate(node.index) return f"{tmp}\nread {var} {node.var} {var_}", var + elif t == AtomNode: return self._generate(node.value) + elif t == AssignmentNode: if node.atype == "=": + # direct assignment + tmp, var = self._generate(node.right) + # check if variable with attribute if GEN_REGEXES["IATTR"].fullmatch(node.left): spl = node.left.split(".", 1) return f"{tmp}\ncontrol {spl[1]} {spl[0]} {var} _ _ _" return f"{tmp}\nset {node.left} {var}" + elif node.atype in ["+=", "-=", "*=", "/="]: + # changing assignment + + # determine operator at = node.atype op = "add" if at == "+=" else "sub" if at == "-=" else "mul" if at == "*=" else "div" if at == "/=" else "" if op == "": @@ -483,16 +595,23 @@ def _generate(self, node: Node): tmp, var = self._generate(node.right) return f"{tmp}\nop {op} {node.left} {node.left} {var}" + elif t == IndexAssignNode: if node.atype == "=": + # direct assignment + itmp, ivar = self._generate(node.index) vtmp, vvar = self._generate(node.val) return f"{itmp}\n{vtmp}\nwrite {vvar} {node.var} {ivar}" + elif node.atype in ["+=", "-=", "*=", "/="]: + # changing assignment + itmp, ivar = self._generate(node.index) vtmp, vvar = self._generate(node.val) + # determine operator at = node.atype op = "add" if at == "+=" else "sub" if at == "-=" else "mul" if at == "*=" else "div" if at == "/=" else "" if op == "": @@ -501,6 +620,7 @@ def _generate(self, node: Node): tval = self.get_tmp_var() return f"{itmp}\n{vtmp}\nread {tval} {node.var} {ivar}\nop {op} {tval} {tval} {vvar}\nwrite {tval} {node.var} {ivar}" + elif t == ExpressionNode: tmp, var = self._generate(node.left) @@ -514,6 +634,7 @@ def _generate(self, node: Node): tmp += f"\n{tmp2}\nop {op} {var} {var} {var2}" return tmp, var + elif t == CompExpressionNode: tmp, var = self._generate(node.left) @@ -528,6 +649,7 @@ def _generate(self, node: Node): tmp += f"\n{tmp2}\nop {op} {var} {var} {var2}" return tmp, var + elif t == ArithExpNode: tmp, var = self._generate(node.left) @@ -541,6 +663,7 @@ def _generate(self, node: Node): tmp += f"\n{tmp2}\nop {op} {var} {var} {var2}" return tmp, var + elif t == TermNode: tmp, var = self._generate(node.left) @@ -554,6 +677,7 @@ def _generate(self, node: Node): tmp += f"\n{tmp2}\nop {op} {var} {var} {var2}" return tmp, var + elif t == FactorNode: tmp, var = self._generate(node.left) @@ -563,26 +687,37 @@ def _generate(self, node: Node): tmp += f"\nop not {var} {var} _" return tmp, var + elif t == CallNode: if node.is_call: + # is a function call + + # resolve function name if type(node.function) == AtomNode: node.function = str(node.function.value.value) - if type(node.function) != str: node.function = self._generate(node.function)[0].split()[-1] + # check if native is_native = node.function in functions.native + # check if builtin is_builtin = node.function in functions.builtin if GEN_REGEXES["SFUNC"].fullmatch(node.function): + # subcommand function + + # convert to SubCallNode scn = SubCallNode(node.pos, node.function, node.params) self.var_list |= set(self._generate_var_list(scn)) + return self._generate(scn) if not is_native and not is_builtin: + # user created function + + # generate signature and check if exists signature = functions.gen_signature(node.function, node.params) found = False - for v in self.var_list: if v == signature: found = True @@ -596,6 +731,7 @@ def _generate(self, node: Node): if not found: gen_error(node, f"Undefined function \"{node.function}\"") + # resolve parameters tmp = "" params = [] for i, arg in enumerate(node.params): @@ -625,32 +761,46 @@ def _generate(self, node: Node): tmp += f"{tmp2}\nset __f_{node.function}_arg_{i} {var}\n" if is_native: + # native function + + # fill in parameters until enough req = functions.native_params[node.function] while len(params) < req: params.append("_") + # get return position retpos = NATIVE_RETURN_POS.get(node.function, -1) retvar = "null" if retpos != -1: retvar = params[retpos] return f"{tmp}{node.function} {' '.join(params)}", retvar + elif is_builtin: + # builtin function + + # check number of parameters nparams = functions.builtin_params.get(node.function, functions.builtin_params_default) if nparams != len(params): gen_error(node, f"Incorrect number of parameters to function (expected {nparams}, got {len(params)})") return f"{tmp}op {node.function} __f_{node.function}_retv {params[0] if nparams >= 1 else '_'} {params[1] if nparams >= 2 else '_'}", f"__f_{node.function}_retv" + else: return f"{tmp}op add __f_{node.function}_ret @counter 1\nset @counter __f_{node.function}", f"__f_{node.function}_retv" + else: + # not a function call + tmp, var = self._generate(node.function) return tmp, var + elif t == SubCallNode: spl = node.function.split(".") req = functions.native_params[spl[0]] + # resolve parameters tmp = "" params = [] for i, arg in enumerate(node.params): @@ -659,25 +809,33 @@ def _generate(self, node: Node): tmp += f"{tmp_}\n" params.append(var) + # fill in parameters while len(params) < req - 1: params.append("_") + # get return position retpos = NATIVE_SUB_RETURN_POS.get(node.function, -1) retvar = "null" if retpos != -1: retvar = params[retpos] return f"{tmp}{spl[0]} {spl[1]} {' '.join(params)}", retvar + elif t == IfNode: tmp2, var = self._generate(node.condition) if not node.elsecode: + # does not have `else` statement + l_e = self.get_tmp_label() tmp = f"{tmp2}\n>{l_e} !{var}\n" tmp += self._code_node_join(node) tmp += f"\n<{l_e}" + else: + # has `else` statement + l_s = self.get_tmp_label() l_e = self.get_tmp_label() @@ -688,6 +846,7 @@ def _generate(self, node: Node): tmp += f"\n<{l_e}" return tmp + elif t == WhileNode: tmp2, var = self._generate(node.condition) @@ -703,6 +862,7 @@ def _generate(self, node: Node): self.loop_stack.pop() return tmp + elif t == ForNode: itmp = self._generate(node.init) ctmp, cvar = self._generate(node.condition) @@ -726,9 +886,11 @@ def _generate(self, node: Node): self.loop_stack.pop() return tmp + elif t == FunctionNode: l_e = self.get_tmp_label() + # generate variables for arguments args = {a: f"__f_{node.name}_arg_{i}" for i, a in enumerate(node.args)} self.func_stack.append(node.name) @@ -740,6 +902,7 @@ def _generate(self, node: Node): break tmp = f"op add __f_{node.name} @counter 1\n>{l_e}\n" + # rename variables in code to function specific ones tmp += self._code_node_join(node.rrename(args), to_pos) if to_pos is None: @@ -750,6 +913,7 @@ def _generate(self, node: Node): self.func_stack.pop() return tmp + elif t == RepeatNode: if node.amount == 0: return "" @@ -763,14 +927,17 @@ def _generate(self, node: Node): tmp += gen tmp += f"\n>{l_e} {i} == {node.amount}\n>{l_v}\n<{l_e}" + # unwind if it results in smaller code if len(self.optimize(tmp).strip().splitlines()) - 2 >= node.amount * len(self.optimize(gen).strip().splitlines()): tmp = gen for _ in range(node.amount - 1): tmp += "\n" + gen return tmp + elif t == NativeNode: return f".{node.code}" + elif t == ReturnNode: if len(self.func_stack) < 1: gen_error(node, "Cannot return when not in a function") @@ -782,6 +949,7 @@ def _generate(self, node: Node): else: tmp, var = self._generate(node.value) return f"{tmp}\nset {rvar} {var}\nset @counter __f_{fname}_ret", rvar + elif t == LoopActionNode: if len(self.loop_stack) < 1: gen_error(node, "Cannot break or continue when not in a loop") @@ -793,33 +961,48 @@ def _generate(self, node: Node): elif node.action == "continue": return f"{self._generate(loop[2])}\n>{loop[0]}" - raise RuntimeError(f"Invalid AST") + raise RuntimeError(f"Invalid AST (loop action: \"{node.action}\")") + elif t == ExternNode: return "" gen_error(node, f"Unknown node ({node})") def _var_list_join(self, lists: list) -> list: + """ + join variable lists + """ + tmp = [] for l in lists: tmp += l return tmp def _generate_var_list(self, node: Node) -> list: + """ + generate variable list + """ + t = type(node) if t in [AST, CodeNode]: return self._var_list_join([self._generate_var_list(n) for n in node.code]) + elif t == ValueNode: return [node.value] if type(node.value) == str and not (node.value.startswith("\"") and node.value.endswith("\"")) else [] + elif t == IndexNode: return self._generate_var_list(node.index) + elif t == AtomNode: return self._generate_var_list(node.value) + elif t == AssignmentNode: return [node.left] if node.atype == "=" else [] + self._generate_var_list(node.right) + elif t == IndexAssignNode: return [] + elif t in [ExpressionNode, CompExpressionNode, ArithExpNode, TermNode]: tmp = [] tmp += self._generate_var_list(node.left) @@ -827,8 +1010,10 @@ def _generate_var_list(self, node: Node) -> list: for _, e in node.right: tmp += self._generate_var_list(e) return tmp + elif t == FactorNode: return self._generate_var_list(node.left) + elif t == CallNode: if node.is_call: tmp = [] @@ -839,39 +1024,60 @@ def _generate_var_list(self, node: Node) -> list: return [f"__f_{node.function}_retv"] + tmp else: return self._generate_var_list(node.function) + elif t == SubCallNode: if node.function in NATIVE_SUB_RETURN_POS: return self._generate_var_list(node.params[NATIVE_SUB_RETURN_POS[node.function]]) return [] + elif t in [IfNode, WhileNode, ForNode, RepeatNode, FunctionNode]: tmp = self._var_list_join([self._generate_var_list(n) for n in node.code]) + if t in [IfNode, WhileNode, ForNode]: tmp += self._generate_var_list(node.condition) + if t == IfNode and node.elsecode is not None: tmp += self._var_list_join([self._generate_var_list(n) for n in node.elsecode]) + if t == ForNode: tmp += self._generate_var_list(node.init) tmp += self._generate_var_list(node.action) + if t == FunctionNode: tmp += [f"__f_{node.name}_arg_{i}" for i, _ in enumerate(node.args)] tmp += [functions.gen_signature(node.name, node.args)] + return tmp + elif t == NativeNode: return [] + elif t == ReturnNode: return self._generate_var_list(node.value) + elif t == LoopActionNode: return [] + elif t == ExternNode: return [node.name] gen_error(node, f"Unknown node ({node})") def get_tmp_var(self) -> str: + """ + generate temporary variable name + """ + + # increase counter self.tmpv += 1 return f"__tmp{self.tmpv}" def get_tmp_label(self) -> str: + """ + generate temporary label name + """ + + # increase counter self.tmpl += 1 return f"__mpp{self.tmpl}" diff --git a/mlogpp/gerror.py b/mlogpp/gerror.py index 0db6f28..222ee9c 100644 --- a/mlogpp/gerror.py +++ b/mlogpp/gerror.py @@ -5,6 +5,7 @@ from .formatting import Format from .parser_ import Node +# debug flags GEN_ERROR_DEBUG = False GEN_U_ERROR_DEBUG = False GEN_F_ERROR_DEBUG = False @@ -12,12 +13,25 @@ _undefined_stack = [] def push_undefined(value: bool) -> None: + """ + push a value to the undefined stack + """ + _undefined_stack.append(value) def pop_undefined() -> bool: + """ + pop a value from the undefined stack + """ + return _undefined_stack.pop() def gen_error(node: Node, msg: str) -> None: + """ + raise generator error + """ + + # debug if GEN_ERROR_DEBUG: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) @@ -32,9 +46,14 @@ def gen_error(node: Node, msg: str) -> None: sys.exit(1) def gen_undefined_error(node: Node, var: str) -> None: + """ + raise undefined variable error + """ + if len(_undefined_stack) > 0 and not _undefined_stack[-1]: return + # debug if GEN_U_ERROR_DEBUG: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) @@ -49,6 +68,11 @@ def gen_undefined_error(node: Node, var: str) -> None: sys.exit(1) def gen_undefinedf_error(node: Node, name: str) -> None: + """ + raise undefined function error + """ + + # debug if GEN_F_ERROR_DEBUG: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2)