diff --git a/Makefile b/Makefile index 6f57e35d7b..c4d9f55e4b 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,8 @@ else # C $(filter %.c.o,$(OBJECTS)): $(BUILD_DIR)/%.c.o: %.c @mkdir -p $(shell dirname $@) - cpp $(CPPFLAGS) $< | tools/compile_dsl_macros.py | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) - -o $@ + cpp $(CPPFLAGS) $< | tools/compile_dsl_macros.py > $(BUILD_DIR)/$*.i + $(CC) $(CFLAGS) -o - $(BUILD_DIR)/$*.i | $(OLD_AS) $(OLDASFLAGS) - -o $@ # Assembly $(filter %.s.o,$(OBJECTS)): $(BUILD_DIR)/%.s.o: %.s diff --git a/include/map.h b/include/map.h index 2671514a12..594850b6f6 100644 --- a/include/map.h +++ b/include/map.h @@ -134,7 +134,7 @@ typedef struct StatDrop { { F16(100), F16(30), attempts, F16(40) }, \ } -#define ANIMATION(sprite, palette, anim) (sprite << 16) + (palette << 8) + anim +#define ANIMATION(sprite, palette, anim) ((sprite << 16) + (palette << 8) + anim) #define OVERRIDE_MOVEMENT_SPEED(speed) (speed * 32767) #define NO_OVERRIDE_MOVEMENT_SPEED OVERRIDE_MOVEMENT_SPEED(-1) diff --git a/include/si.h b/include/si.h index 6b464793ac..6f34ed928b 100644 --- a/include/si.h +++ b/include/si.h @@ -142,14 +142,12 @@ typedef s32 ApiStatus; #define SI_END_CHILD_THREAD() SI_CMD(0x59) #define EXIT_WALK_SCRIPT(walkDistance, exitIdx, map, entryIdx) \ - { \ - SI_GROUP(0x1B), \ - SI_CALL(UseExitHeading, walkDistance, exitIdx), \ - SI_EXEC(ExitWalk), \ - SI_CALL(GotoMap, map, entryIdx), \ - SI_WAIT_FRAMES(100), \ - SI_RETURN(), \ - SI_END(), \ - } + SCRIPT({ \ + group 0x1B; \ + UseExitHeading(walkDistance, exitIdx); \ + spawn ExitWalk; \ + GotoMap(map, entryIdx); \ + sleep 100; \ + }) #endif diff --git a/src/world/area_kmr/kmr_12/events.c b/src/world/area_kmr/kmr_12/events.c index 3a0c92088a..b05d47110a 100644 --- a/src/world/area_kmr/kmr_12/events.c +++ b/src/world/area_kmr/kmr_12/events.c @@ -3,29 +3,25 @@ Script M(ExitWest) = EXIT_WALK_SCRIPT(60, 0, "kmr_07", 1); Script M(ExitEast) = EXIT_WALK_SCRIPT(60, 1, "kmr_11", 0); -Script M(BindExits) = { - SI_BIND(M(ExitWest), TriggerFlag_FLOOR_ABOVE, 0 /* deili1 */, NULL), - SI_BIND(M(ExitEast), TriggerFlag_FLOOR_ABOVE, 3 /* deili2 */, NULL), - SI_RETURN(), - SI_END(), -}; +Script M(BindExits) = SCRIPT({ + bind M(ExitWest) to TriggerFlag_FLOOR_ABOVE 0 // deili1 + bind M(ExitEast) to TriggerFlag_FLOOR_ABOVE 3 // deili2 +}); -Script M(Main) = { - SI_SET(SI_SAVE_VAR(425), 31), - SI_CALL(SetSpriteShading, -1), - SI_CALL(SetCamPerspective, 0, 3, 25, 16, 4096), - SI_CALL(SetCamBGColor, 0, 0, 0, 0), - SI_CALL(SetCamEnabled, 0, 1), - SI_CALL(MakeNpcs, 0, M(npcGroupList)), - SI_EXEC_WAIT(M(MakeEntities)), - SI_EXEC(M(PlayMusic)), - SI_SET(SI_VAR(0), M(BindExits)), - SI_EXEC(EnterWalk), - SI_WAIT_FRAMES(1), - SI_BIND(M(ReadWestSign), TriggerFlag_WALL_INTERACT, 10, NULL), - SI_RETURN(), - SI_END(), -}; +Script M(Main) = SCRIPT({ + SI_SAVE_VAR(425) = 31 + SetSpriteShading(-1) + SetCamPerspective(0, 3, 25, 16, 4096) + SetCamBGColor(0, 0, 0, 0) + SetCamEnabled(0, 1) + MakeNpcs(0, M(npcGroupList)) + await M(MakeEntities) + spawn M(PlayMusic) + SI_VAR(0) = M(BindExits) + spawn EnterWalk + sleep 1 + bind M(ReadWestSign) to TriggerFlag_WALL_INTERACT 10 +}); NpcAISettings M(goombaAISettings) = { .moveSpeed = 1.5f, @@ -42,11 +38,9 @@ NpcAISettings M(goombaAISettings) = { .unk_2C = TRUE, }; -Script M(GoombaAI) = { - SI_CALL(DoBasicAI, &M(goombaAISettings)), - SI_RETURN(), - SI_END(), -}; +Script M(GoombaAI) = SCRIPT({ + DoBasicAI(M(goombaAISettings)) +}); NpcSettings M(goombaNpcSettings) = { .height = 20, @@ -57,102 +51,95 @@ NpcSettings M(goombaNpcSettings) = { .level = 5, }; -// *INDENT-OFF* -/// @bug The RETURN command is after the END command, so this script will never terminate. -Script M(ReadWestSign) = { - SI_GROUP(0), +/// @bug Never returns +Script M(ReadWestSign) = SCRIPT({ + group 0 // "Eat a Mushroom to regain your energy!" - SI_SUSPEND_GROUP(1), - SI_CALL(DisablePlayerInput, TRUE), - SI_CALL(ShowMessageAtScreenPos, MessageID_SIGN_MUSHROOM_GOOMBA_TRAP, 160, 40), - SI_RESUME_GROUP(1), + suspend group 1 + DisablePlayerInput(TRUE) + ShowMessageAtScreenPos(MessageID_SIGN_MUSHROOM_GOOMBA_TRAP, 160, 40) + resume group 1 - SI_SET(SI_FLAG(0), FALSE), - SI_CALL(GetGoomba), - SI_IF_NE(SI_VAR(0), FALSE), - SI_CALL(GetNpcVar, NpcId_GOOMBA, 0, SI_VAR(0)), - SI_IF_EQ(SI_VAR(0), FALSE), + SI_FLAG(0) = FALSE + GetGoomba() + if SI_VAR(0) != FALSE { + GetNpcVar(NpcId_GOOMBA, 0, SI_VAR(0)) + if SI_VAR(0) == FALSE { // Trigger Goomba to peel off - SI_CALL(SetNpcVar, NpcId_GOOMBA, 0, TRUE), - SI_SET(SI_FLAG(0), TRUE), - SI_WAIT_FRAMES(10), - SI_END_IF(), - SI_END_IF(), - SI_CALL(DisablePlayerInput, FALSE), - SI_IF_EQ(SI_FLAG(0), TRUE), - SI_UNBIND_ME(), - SI_END_IF(), + SetNpcVar(NpcId_GOOMBA, 0, TRUE) + SI_FLAG(0) = TRUE + sleep 10 + } + } + DisablePlayerInput(FALSE) + if SI_FLAG(0) == TRUE { + unbind + } - SI_END(), - SI_RETURN(), -}; + break + return +}); -Script M(GoombaIdle) = { - SI_WAIT_FRAMES(1), +Script M(GoombaIdle) = SCRIPT({ + sleep 1 - SI_CALL(SetSelfVar, 0, FALSE), - SI_CALL(SetNpcAnimation, NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 13)), - SI_CALL(EnableNpcShadow, NpcId_SELF, FALSE), - SI_CALL(SetSelfEnemyFlagBits, 0x00000020, TRUE), + SetSelfVar(0, FALSE) + SetNpcAnimation(NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 13)) + EnableNpcShadow(NpcId_SELF, FALSE) + SetSelfEnemyFlagBits(0x00000020, TRUE) // Wait until read_sign sets NPC var 0 - SI_LABEL(0), - SI_CALL(GetSelfVar, 0, SI_VAR(0)), - SI_WAIT_FRAMES(1), - SI_IF_EQ(SI_VAR(0), FALSE), - SI_GOTO(0), - SI_END_IF(), + lbl: + GetSelfVar(0, SI_VAR(0)) + sleep 1 + if SI_VAR(0) == FALSE { + goto lbl + } // Peel and jump off the sign - SI_CALL(SetNpcFlagBits, NpcId_SELF, 0x00240000, TRUE), - SI_WAIT_FRAMES(3), - SI_SET_F(SI_VAR(0), SI_FIXED(0.0f)), - SI_LOOP(9), - SI_ADD_F(SI_VAR(0), SI_FIXED(10.0f)), - SI_CALL(SetNpcRotation, NpcId_SELF, 0, SI_VAR(0), 0), - SI_WAIT_FRAMES(1), - SI_END_LOOP(), - SI_CALL(SetNpcAnimation, NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 0)), - SI_LOOP(9), - SI_ADD_F(SI_VAR(0), SI_FIXED(10.0f)), - SI_CALL(SetNpcRotation, NpcId_SELF, 0, SI_VAR(0), 0), - SI_WAIT_FRAMES(1), - SI_END_LOOP(), - SI_CALL(SetNpcAnimation, NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 7)), - SI_WAIT_FRAMES(20), - SI_CALL(SetNpcAnimation, NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 1)), - SI_CALL(PlaySoundAtNpc, NpcId_SELF, 248, 0), - SI_CALL(func_802CFE2C, NpcId_SELF, 8192), - SI_CALL(func_802CFD30, NpcId_SELF, 5, 6, 1, 1, 0), - SI_WAIT_FRAMES(12), - SI_WAIT_FRAMES(5), - SI_CALL(PlaySoundAtNpc, NpcId_SELF, 812, 0), - SI_CALL(EnableNpcShadow, NpcId_SELF, TRUE), - SI_CALL(SetNpcJumpscale, NpcId_SELF, SI_FIXED(0.6005859375f)), - SI_CALL(NpcJump0, NpcId_SELF, -35, 0, 30, 23), - SI_CALL(func_802CFD30, NpcId_SELF, 0, 0, 0, 0, 0), - SI_CALL(InterpNpcYaw, NpcId_SELF, 90, 0), - SI_CALL(SetNpcFlagBits, NpcId_SELF, 0x00240000, FALSE), - SI_CALL(SetSelfEnemyFlagBits, 0x00000020, FALSE), - SI_CALL(SetSelfEnemyFlagBits, 0x40000000, TRUE), + SetNpcFlagBits(NpcId_SELF, 0x00240000, TRUE) + sleep 3 + SI_VAR(0) = 0.0 + loop 9 { + SI_VAR(0) += 10.0 + SetNpcRotation(NpcId_SELF, 0, SI_VAR(0), 0) + sleep 1 + } + SetNpcAnimation(NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 0)) + loop 9 { + SI_VAR(0) += 10.0 + SetNpcRotation(NpcId_SELF, 0, SI_VAR(0), 0) + sleep 1 + } + SetNpcAnimation(NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 7)) + sleep 20 + SetNpcAnimation(NpcId_SELF, ANIMATION(SpriteId_GOOMBA, 0, 1)) + PlaySoundAtNpc(NpcId_SELF, 248, 0) + func_802CFE2C(NpcId_SELF, 8192) + func_802CFD30(NpcId_SELF, 5, 6, 1, 1, 0) + sleep 12 + sleep 5 + PlaySoundAtNpc(NpcId_SELF, 812, 0) + EnableNpcShadow(NpcId_SELF, TRUE) + SetNpcJumpscale(NpcId_SELF, 0.6005859375) + NpcJump0(NpcId_SELF, -35, 0, 30, 23) + func_802CFD30(NpcId_SELF, 0, 0, 0, 0, 0) + InterpNpcYaw(NpcId_SELF, 90, 0) + SetNpcFlagBits(NpcId_SELF, 0x00240000, FALSE) + SetSelfEnemyFlagBits(0x00000020, FALSE) + SetSelfEnemyFlagBits(0x40000000, TRUE) // We're done jumping off; the player can read the sign again - SI_BIND(M(ReadWestSign), TriggerFlag_WALL_INTERACT, 10, NULL), + bind M(ReadWestSign) to TriggerFlag_WALL_INTERACT 10 - // Behave like a normal enemy from now on - SI_CALL(BindNpcAI, NpcId_SELF, &M(GoombaAI)), + // Behave like a normal enemy from now o + BindNpcAI(NpcId_SELF, M(GoombaAI)) +}); - SI_RETURN(), - SI_END(), -}; - -Script M(GoombaInit) = { - SI_CALL(BindNpcIdle, NpcId_SELF, &M(GoombaIdle)), - SI_RETURN(), - SI_END(), -}; -// *INDENT-ON* +Script M(GoombaInit) = SCRIPT({ + BindNpcIdle(NpcId_SELF, M(GoombaIdle)) +}); StaticNpc M(goombaNpc) = { .id = NpcId_GOOMBA, @@ -211,7 +198,7 @@ Script M(ReadEastSign) = SCRIPT({ return } - setgroup 0 + group 0 func_802D5830(1) DisablePlayerInput(1) diff --git a/tools/compile_dsl_macros.py b/tools/compile_dsl_macros.py index 73f981a1d4..d0f338b787 100755 --- a/tools/compile_dsl_macros.py +++ b/tools/compile_dsl_macros.py @@ -1,41 +1,68 @@ #! /usr/bin/python3 from sys import stdin, stderr -from lark import Lark, exceptions, Transformer, v_args +from lark import Lark, exceptions, Tree, Transformer, Visitor, v_args +from lark.visitors import Discard +import traceback def eprint(*args, **kwargs): print(*args, file=stderr, **kwargs) -""" -write_buf = "" -def write(s): - global write_buf - write_buf += s -def flush(): - global write_buf - print(write_buf, end="") - write_buf = "" -""" def write(s): print(s, end="") -def flush(): - pass -#""" + +ANSI_RED = "\033[1;31;40m" +ANSI_RESET = "\u001b[0m" + +def pairs(seq): + i = iter(seq) + prev = next(i) + for item in i: + yield prev, item + prev = item script_parser = Lark(r""" - block: "{" line* "}" + block: "{" NEWLINE* (stmt STMT_SEP)* NEWLINE* "}" - ?line: call + ?stmt: call + | label ":" -> label_decl + | "goto" label -> label_goto | if_stmt - | "return" -> return_stmt - | "setgroup" expr -> setgroup + | "return" -> return_stmt + | "break" -> break_stmt + | "sleep" expr -> sleep_stmt + | "spawn" expr -> spawn_stmt + | "await" expr -> await_stmt + | lhs "=" "spawn" expr -> spawn_set_stmt + | lhs set_op expr -> set_stmt + | lhs ":=" expr -> set_const_stmt + | bind_stmt + | bind_set_stmt + | "unbind" -> unbind_stmt + | "group" expr -> set_group + | suspend_stmt + | resume_stmt + | kill_stmt + | loop_stmt call: CNAME "(" [expr ("," expr)* [","]] ")" if_stmt: "if" expr if_op expr block ["else" block] - ?if_op: "==" -> if_op_eq + | "!=" -> if_op_ne + + suspend_stmt: "suspend" control_type expr ("," control_type expr)* [","] + resume_stmt: "resume" control_type expr ("," control_type expr)* [","] + kill_stmt: "kill" control_type expr ("," control_type expr)* [","] + ?control_type: "group" -> control_type_group + | "others" -> control_type_others + | ["script"] -> control_type_script + + bind_stmt: "bind" expr "to" expr expr + bind_set_stmt: lhs "=" "bind" expr "to" expr expr + + loop_stmt: "loop" [expr] block ?expr: c_const_expr | ESCAPED_STRING @@ -44,9 +71,23 @@ script_parser = Lark(r""" | HEX_INT | CNAME - c_const_expr: "(" (c_const_expr | NOT_PARENS)+ ")" + ?lhs: c_const_expr + + ?set_op: "=" -> set_op_eq + | "+=" -> set_op_add + | "-=" -> set_op_sub + | "*=" -> set_op_mul + | "/=" -> set_op_div + | "%=" -> set_op_mod + + c_const_expr: c_const_expr_internal + c_const_expr_internal: "(" (c_const_expr_internal | NOT_PARENS)+ ")" NOT_PARENS: /[^()]+/ + STMT_SEP: (NEWLINE+ | ";") + + label: /[a-zA-Z0-9_]+/ + %import common.CNAME %import common.SIGNED_INT %import common.DECIMAL @@ -58,55 +99,289 @@ script_parser = Lark(r""" LINE_COMMENT: "//" /[^\n]*/ NEWLINE %ignore LINE_COMMENT - %import common.WS + %import common.WS_INLINE %import common.NEWLINE - %ignore WS + %ignore WS_INLINE -""", start="block")#, parser="lalr", cache=True) +""", start="block", propagate_positions=True)#, parser="lalr", cache=True) -def si_cmd(opcode, *args): - return [opcode, len(args), *args] +class Cmd(): + def __init__(self, opcode, *args, **kwargs): + if opcode: + self.opcode = opcode + self.args = args + self.meta = kwargs.get("meta", None) + self.context = [] -@v_args(inline=True) + def add_context(self, context): + self.context.append(context) + + def to_bytecode(self): + return [ self.opcode, len(self.args), *self.args ] + + def __str__(self): + return f"Cmd({self.opcode:02X}, {', '.join(map(str, self.args))})" + +class BreakCmd(Cmd): + def __init__(self, **kwargs): + super().__init__(None, **kwargs) + + @property + def opcode(self): + # are we in a switch or a loop? + context = None + for c in self.context: + if c in ("switch", "loop"): + context = c + + if not context: + return 0x01 # END + elif context == "loop": + return 0x07 # SI_BREAK_LOOP + elif context == "switch": + return 0x22 # BREAK_CASE + + def __str__(self): + return "BreakCmd" + +class CompileError(Exception): + def __init__(self, message, meta): + super().__init__(message) + self.meta = meta + +def is_fixed_var(v): + if type(v) == int: + if v <= -250000000: + return False + elif v <= -220000000: + return True + return False + +class LabelAllocation(Visitor): + def __init__(self): + super().__init__() + self.labels = [] + + def label_decl(self, tree): + name = tree.children[0].children[0] + if name in self.labels: + raise CompileError(f"label `{name}' already declared", tree.meta) + self.labels.append(name) + +@v_args(tree=True) class Compile(Transformer): SIGNED_INT = str - DECIMAL = float # TODO: fixed HEX_INT = str - ESCAPED_STRING = eval + ESCAPED_STRING = str + + def transform(self, tree): + self.alloc = LabelAllocation() + self.alloc.visit_topdown(tree) + return super().transform(tree) def CNAME(self, name): - return f"(Bytecode)({name})" + return f"(Bytecode)(&{name})" NOT_PARENS = str - def c_const_expr(self, *args): # usually a macro expansion - return f"(Bytecode)({' '.join(args)})" + def c_const_expr_internal(self, tree): + return f"({' '.join(tree.children)})" + def c_const_expr(self, tree): + return f"(Bytecode){tree.children[0]}" - def block(self, *lines): + def DECIMAL(self, v): + # fixed-point + return int((float(v) * 1024) - 230000000) + + def block(self, tree): + # flatten children list flat = [] - for line in lines: - if type(line) != list: - eprint(f"uncompiled: {line}") - else: - flat += line + for node in tree.children: + if type(node) == list: + flat += node + elif isinstance(node, Cmd): + flat.append(node) return flat - def call(self, func, *args): + def call(self, tree): # TODO: type checking etc - return si_cmd(0x43, func, *args) + return Cmd(0x43, *tree.children, meta=tree.meta) - def if_stmt(self, a, op, b, block): - return si_cmd(op, a, b) + block + si_cmd(0x13) - def if_op_eq(self): return 0x0A + def if_stmt(self, tree): + a, op, b, block = tree.children + for cmd in block: + if type(cmd) == Cmd: + cmd.add_context("if") + return [ Cmd(op, a, b, meta=tree.meta), *block, Cmd(0x13) ] + def if_op_eq(self, tree): return 0x0A + def if_op_ne(self, tree): return 0x0B - def return_stmt(self): return si_cmd(0x02) + def loop_stmt(self, tree): + expr = tree.children.pop(0) if len(tree.children) > 1 else 0 + block = tree.children[0] - def setgroup(self, group): - return si_cmd(0x4D, group) + for cmd in block: + if type(cmd) == Cmd: + cmd.add_context("loop") + + return [ Cmd(0x05, expr, meta=tree.meta), *block, Cmd(0x06) ] + + def return_stmt(self, tree): return Cmd(0x02, meta=tree.meta) + + def break_stmt(self, tree): + return BreakCmd(meta=tree.meta) + + def set_group(self, tree): return Cmd(0x4D, tree.children[0], meta=tree.meta) + + def suspend_stmt(self, tree): + commands = [] + for opcodes, expr in pairs(tree.children): + if not "suspend" in opcodes: + raise CompileError(f"`suspend {opcodes['__control_type__']}' not supported", meta=tree.meta) + commands.append(Cmd(opcodes["suspend"], expr, meta=tree.meta)) + return commands + def resume_stmt(self, tree): + commands = [] + for opcodes, expr in pairs(tree.children): + if not "resume" in opcodes: + raise CompileError(f"`resume {opcodes['__control_type__']}' not supported", meta=tree.meta) + commands.append(Cmd(opcodes["resume"], expr, meta=tree.meta)) + return commands + def kill_stmt(self, tree): + commands = [] + for opcodes, expr in pairs(tree.children): + if not "kill" in opcodes: + raise CompileError(f"`kill {opcodes['__control_type__']}' not supported", meta=tree.meta) + commands.append(Cmd(opcodes["kill"], expr, meta=tree.meta)) + return commands + def control_type_group(self, tree): + return { + "__control_type__": "group", + "suspend": 0x4F, + "resume": 0x50, + } + def control_type_others(self, tree): + return { + "__control_type__": "others", + "suspend": 0x51, + "resume": 0x52, + } + def control_type_script(self, tree): + return { + "__control_type__": "script", + "suspend": 0x53, + "resume": 0x54, + "kill": 0x49, + } + + def sleep_stmt(self, tree): + return Cmd(0x08, tree.children[0], meta=tree.meta) + + def bind_stmt(self, tree): + script, trigger, target = tree.children + return Cmd(0x47, script, trigger, target, 1, 0, meta=tree.meta) + def bind_set_stmt(self, tree): + ret, script, trigger, target = tree.children + return Cmd(0x47, script, trigger, target, 1, ret, meta=tree.meta) + def unbind_stmt(self, tree): + return Cmd(0x48, meta=tree.meta) + + def spawn_stmt(self, tree): + return Cmd(0x44, tree.children[0], meta=tree.meta) + def spawn_set_stmt(self, tree): + lhs, script = tree.children + return Cmd(0x45, script, lhs, meta=tree.meta) + def await_stmt(self, tree): + return Cmd(0x46, tree.children[0], meta=tree.meta) + + def set_stmt(self, tree): + lhs, opcodes, rhs = tree.children + if is_fixed_var(rhs): + opcode = opcodes.get("float", None) + if not opcode: + raise CompileError(f"operation `{opcodes['__op__']}' not supported for floats", tree.meta) + else: + opcode = opcodes.get("int", None) + if not opcode: + raise CompileError(f"operation `{opcodes['__op__']}' not supported for ints", tree.meta) + return Cmd(opcode, lhs, rhs) + def set_const_stmt(self, tree): + lhs, rhs = tree.children + return Cmd(0x25, lhs, rhs) + def set_op_eq(self, tree): + return { + "__op__": "=", + "int": 0x24, + "float": 0x26, + } + def set_op_add(self, tree): + return { + "__op__": "+", + "int": 0x27, + "float": 0x2C, + } + def set_op_sub(self, tree): + return { + "__op__": "-", + "int": 0x28, + "float": 0x2D, + } + def set_op_mul(self, tree): + return { + "__op__": "*", + "int": 0x29, + "float": 0x2E, + } + def set_op_div(self, tree): + return { + "__op__": "/", + "int": 0x2A, + "float": 0x2F, + } + def set_op_mod(self, tree): + return { + "__op__": "%", + "int": 0x2B, + } + + def label_decl(self, tree): + label = tree.children[0] + return Cmd(0x03, label, meta=tree.meta) + def label_goto(self, tree): + label = tree.children[0] + return Cmd(0x04, label, meta=tree.meta) + def label(self, tree): + name = tree.children[0] + if name in self.alloc.labels: + return self.alloc.labels.index(name) + raise CompileError(f"label `{name}' is undeclared", tree.meta) + +#define SI_SET_CONST(var, value) SI_CMD(0x25, var, value) // Does not get_variable +#define SI_SUB(a, b) SI_CMD(0x28, a, b) // -= +#define SI_MUL(a, b) SI_CMD(0x29, a, b) // *= +#define SI_DIV(a, b) SI_CMD(0x2A, a, b) // /= +#define SI_MOD(a, b) SI_CMD(0x2B, a, b) // %= + +#define SI_SET_F(var, value) SI_CMD(0x26, var, value) +#define SI_SUB_F(a, b) SI_CMD(0x2D, a, b) // -= +#define SI_MUL_F(a, b) SI_CMD(0x2E, a, b) // *= +#define SI_DIV_F(a, b) SI_CMD(0x2F, a, b) // /= def compile_script(s): tree = script_parser.parse(s) + #eprint(tree.pretty()) - return Compile().transform(tree) + si_cmd(0x02) + si_cmd(0x01) + + commands = Compile().transform(tree) + + for cmd in commands: + if not isinstance(cmd, Cmd): + raise Exception(f"uncompiled {cmd}") + + # add RETURN END if no explicit END (top-level `break') was given + if next((cmd for cmd in commands if cmd.opcode == 0x01), None) == None: + commands += (Cmd(0x02), Cmd(0x01)) + + return commands def read_until_closing_paren(depth=1, lex_strings=False): text = "" @@ -155,6 +430,24 @@ def read_line(): return line +def gen_line_map(source, source_line_no = 1): + line_map = {} + output = "" + + output_line_no = 1 + + for line in source.splitlines(True): + if line[0] == "#": + parts = line[2:-1].split(" ") + source_line_no = int(parts[0]) + else: + line_map[output_line_no] = source_line_no + output += line + output_line_no += 1 + source_line_no += 1 + + return output, line_map + # Expects output from C preprocessor on stdin if __name__ == "__main__": line_no = 1 @@ -173,9 +466,6 @@ if __name__ == "__main__": if error: exit(1) else: - #with open("debug.i", "w") as i: - # i.write(write_buf) - flush() exit(0) if char == "#" and (prev_char == "\n" or prev_char == ""): @@ -188,25 +478,58 @@ if __name__ == "__main__": write("#" + line + "\n") elif char == "(": + filename = file_info[0][1:-1] + # SCRIPT(...) if macro_name == "SCRIPT": - script_source = read_until_closing_paren(lex_strings=True) + script_source, line_map = gen_line_map(read_until_closing_paren(lex_strings=True), source_line_no=line_no) try: - bytecode = compile_script(script_source) + commands = compile_script(script_source) - write("{") - for word in bytecode: - write(f"{word},") + write("{\n") + for command in commands: + if command.meta: + write(f"# {line_map[command.meta.line]} {file_info[0]}\n") + write(" ") + for word in command.to_bytecode(): + if type(word) == str: + write(word) + elif type(word) == int: + write(f"0x{word & 0xFFFFFFFF:X}") + else: + raise Exception(f"uncompiled: {command}") + write(", ") + write("\n") write("}") - except exceptions.UnexpectedToken as e: - line = e.line + line_no - filename = file_info[0][1:-1] + except exceptions.UnexpectedEOF as e: + eprint(f"{filename}:{line_no}: {ANSI_RED}error{ANSI_RESET}: unterminated SCRIPT(...) macro") + error = True + except exceptions.UnexpectedCharacters as e: + eprint(e.line) + line = line_map[e.line] + char = script_source[e.pos_in_stream] + allowed = e.allowed + eprint(f"{filename}:{line}: {ANSI_RED}script parse error{ANSI_RESET}: unexpected `{char}', expected {' or '.join(allowed)}") eprint(e.get_context(script_source)) - eprint(f"{filename}:{line}: script parse error: unexpected `{e.token}'") error = True + except exceptions.UnexpectedToken as e: + line = line_map[e.line] + + eprint(f"{filename}:{line}: {ANSI_RED}script parse error{ANSI_RESET}: unexpected `{e.token}'") + eprint(e.get_context(script_source)) + + error = True + except exceptions.VisitError as e: + if type(e.orig_exc) == CompileError: + line = line_map[e.orig_exc.meta.line] + eprint(f"{filename}:{line}: {ANSI_RED}script compile error{ANSI_RESET}: {e.orig_exc}") + else: + eprint(f"{filename}:{line_no}: {ANSI_RED}internal script compilation error{ANSI_RESET}") + traceback.print_exc() + error = True line_no += script_source.count("\n") write(f"\n# {line_no} {file_info[0]}\n")