mirror of
https://github.com/pmret/papermario.git
synced 2024-11-08 12:02:30 +01:00
commit
6b8211bee1
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
venv/
|
||||
.lark_cache*
|
||||
|
||||
# Misc
|
||||
.idea/
|
||||
|
3
.vscode/c_cpp_properties.json
vendored
3
.vscode/c_cpp_properties.json
vendored
@ -9,7 +9,8 @@
|
||||
],
|
||||
"defines": [
|
||||
"F3DEX_GBI_2",
|
||||
"_LANGUAGE_C"
|
||||
"_LANGUAGE_C",
|
||||
"SCRIPT(...)={}"
|
||||
],
|
||||
"cStandard": "c89",
|
||||
"cppStandard": "c++17",
|
||||
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -3,6 +3,7 @@
|
||||
"ms-vscode.cpptools",
|
||||
"nanaian.vscode-star-rod",
|
||||
"notskm.clang-tidy",
|
||||
"EditorConfig.EditorConfig",
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,6 +1,4 @@
|
||||
{
|
||||
"editor.detectIndentation": false,
|
||||
"editor.insertSpaces": true,
|
||||
"files.eol": "\n",
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.rulers": [120],
|
||||
@ -10,6 +8,7 @@
|
||||
"-Iinclude/PR",
|
||||
"-Isrc",
|
||||
"-D_LANGUAGE_C",
|
||||
"-DSCRIPT(...)={}",
|
||||
],
|
||||
"python.pythonPath": "/usr/bin/python3",
|
||||
"git.ignoreLimitWarning": true,
|
||||
|
2
Doxyfile
2
Doxyfile
@ -103,7 +103,7 @@ GENERATE_LATEX = NO
|
||||
ENABLE_PREPROCESSING = YES
|
||||
MACRO_EXPANSION = YES
|
||||
EXPAND_ONLY_PREDEF = YES
|
||||
PREDEFINED = DOXYGEN NON_MATCHING __attribute__((x))=
|
||||
PREDEFINED = DOXYGEN NON_MATCHING SCRIPT(...)={} __attribute__((x))=
|
||||
EXPAND_AS_DEFINED = INCLUDE_ASM UNK_TYPE UNK_PTR UNK_RET UNK_FUN_ARG UNK_FUN_PTR UNK_ARGS M
|
||||
SKIP_FUNCTION_MACROS = YES
|
||||
|
||||
|
5
Makefile
5
Makefile
@ -129,7 +129,10 @@ else
|
||||
# C
|
||||
$(filter %.c.o,$(OBJECTS)): $(BUILD_DIR)/%.c.o: %.c
|
||||
@mkdir -p $(shell dirname $@)
|
||||
cpp $(CPPFLAGS) $< | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) - -o $@
|
||||
|
||||
cpp $(CPPFLAGS) $< > $(BUILD_DIR)/$*.i
|
||||
@grep -cF "SCRIPT(" $(BUILD_DIR)/$*.i | 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
COMPILER_OPTS="-std=gnu89 -Iinclude -Isrc -D_LANGUAGE_C"
|
||||
COMPILER_OPTS="-std=gnu89 -Iinclude -Isrc -D_LANGUAGE_C -DSCRIPT(...)={}"
|
||||
|
||||
shopt -s globstar
|
||||
|
||||
|
@ -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)
|
||||
|
16
include/si.h
16
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
|
||||
|
@ -1,2 +1,3 @@
|
||||
capstone
|
||||
PyYAML
|
||||
lark-parser
|
||||
|
@ -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,117 +38,106 @@ 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,
|
||||
.radius = 23,
|
||||
.ai = &M(GoombaAI),
|
||||
.onHit = EnemyNpcHit,
|
||||
.onDefeat = EnemyNpcDefeat,
|
||||
.onHit = &EnemyNpcHit,
|
||||
.onDefeat = &EnemyNpcDefeat,
|
||||
.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(),
|
||||
loop {
|
||||
GetSelfVar(0, SI_VAR(0))
|
||||
sleep 1
|
||||
} until SI_VAR(0) == FALSE
|
||||
|
||||
// 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,
|
||||
@ -205,30 +190,22 @@ NpcGroupList M(npcGroupList) = {
|
||||
NPC_GROUP_LIST_END(),
|
||||
};
|
||||
|
||||
// *INDENT-OFF*
|
||||
Script M(ReadEastSign) = {
|
||||
SI_CALL(func_800441F0, SI_VAR(0)),
|
||||
SI_IF_EQ(SI_VAR(0), 1),
|
||||
SI_RETURN(),
|
||||
SI_END_IF(),
|
||||
Script M(ReadEastSign) = SCRIPT({
|
||||
func_800441F0(SI_VAR(0))
|
||||
if SI_VAR(0) == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
SI_GROUP(0),
|
||||
group 0
|
||||
|
||||
SI_CALL(func_802D5830, 1),
|
||||
SI_CALL(DisablePlayerInput, 1),
|
||||
SI_CALL(ShowMessageAtScreenPos, MessageID_SIGN_GOOMBA_KINGS_FORTRESS_AHEAD, 160, 40),
|
||||
SI_CALL(DisablePlayerInput, 0),
|
||||
SI_CALL(func_802D5830, 0),
|
||||
func_802D5830(1)
|
||||
DisablePlayerInput(1)
|
||||
ShowMessageAtScreenPos(MessageID_SIGN_GOOMBA_KINGS_FORTRESS_AHEAD, 160, 40)
|
||||
DisablePlayerInput(0)
|
||||
func_802D5830(0)
|
||||
});
|
||||
|
||||
SI_RETURN(),
|
||||
SI_END(),
|
||||
};
|
||||
|
||||
Script M(MakeEntities) = {
|
||||
SI_CALL(MakeEntity, 0x802EAFDC, 436, 0, -42, 0, 0x80000000),
|
||||
SI_CALL(AssignScript, &M(ReadEastSign)),
|
||||
|
||||
SI_RETURN(),
|
||||
SI_END(),
|
||||
};
|
||||
// *INDENT-ON*
|
||||
Script M(MakeEntities) = SCRIPT({
|
||||
MakeEntity(0x802EAFDC, 436, 0, -42, 0, 0x80000000)
|
||||
AssignScript(M(ReadEastSign))
|
||||
});
|
||||
|
615
tools/compile_dsl_macros.py
Executable file
615
tools/compile_dsl_macros.py
Executable file
@ -0,0 +1,615 @@
|
||||
#! /usr/bin/python3
|
||||
|
||||
from sys import argv, stdin, stderr
|
||||
from lark import Lark, exceptions, Tree, Transformer, Visitor, v_args, Token
|
||||
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
|
||||
#print(s, end="")
|
||||
|
||||
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: "{" NEWLINE* (stmt STMT_SEP)* NEWLINE* "}"
|
||||
|
||||
?stmt: call
|
||||
| label ":" -> label_decl
|
||||
| "goto" label -> label_goto
|
||||
| if_stmt
|
||||
| "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
|
||||
| loop_until_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
|
||||
loop_until_stmt: "loop" block "until" expr if_op expr
|
||||
|
||||
?expr: c_const_expr
|
||||
| ESCAPED_STRING
|
||||
| SIGNED_INT
|
||||
| DECIMAL
|
||||
| HEX_INT
|
||||
| CNAME
|
||||
|
||||
?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
|
||||
%import common.HEXDIGIT
|
||||
%import common.ESCAPED_STRING
|
||||
|
||||
HEX_INT: ["+"|"-"] "0x" HEXDIGIT+
|
||||
|
||||
LINE_COMMENT: "//" /[^\n]*/ NEWLINE
|
||||
%ignore LINE_COMMENT
|
||||
|
||||
%import common.WS_INLINE
|
||||
%import common.NEWLINE
|
||||
%ignore WS_INLINE
|
||||
|
||||
""", start="block", propagate_positions=True)#, parser="lalr", cache=True)
|
||||
|
||||
class BaseCmd():
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.meta = kwargs.get("meta", None)
|
||||
self.context = [RootCtx()]
|
||||
|
||||
def add_context(self, ctx):
|
||||
if not isinstance(ctx, CmdCtx):
|
||||
raise Exception()
|
||||
self.context.insert(0, ctx)
|
||||
|
||||
# must be overloaded
|
||||
def opcode():
|
||||
raise Exception()
|
||||
|
||||
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 Cmd(BaseCmd):
|
||||
def __init__(self, opcode, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._opcode = opcode
|
||||
|
||||
def opcode(self):
|
||||
return self._opcode
|
||||
|
||||
class BreakCmd(BaseCmd):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def opcode(self):
|
||||
for ctx in self.context:
|
||||
opcode = ctx.break_opcode(self.meta)
|
||||
if opcode:
|
||||
return opcode
|
||||
|
||||
def __str__(self):
|
||||
return "BreakCmd"
|
||||
|
||||
class CmdCtx():
|
||||
def break_opcode(self, meta):
|
||||
pass
|
||||
|
||||
class RootCtx(CmdCtx):
|
||||
def break_opcode(self, meta):
|
||||
return 0x01
|
||||
|
||||
class IfCtx(CmdCtx):
|
||||
pass
|
||||
|
||||
class SwitchCtx(CmdCtx):
|
||||
def break_opcode(self, meta):
|
||||
return 0x22
|
||||
|
||||
class LoopCtx(CmdCtx):
|
||||
def break_opcode(self, meta):
|
||||
return 0x07
|
||||
|
||||
class LoopUntilCtx(CmdCtx):
|
||||
def break_opcode(self, meta):
|
||||
raise CompileError("breaking out of a loop..until is not supported (hint: use a label)", meta)
|
||||
|
||||
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)
|
||||
|
||||
def gen_label(self):
|
||||
self.labels.append("$generated")
|
||||
return len(self.labels) - 1
|
||||
|
||||
@v_args(tree=True)
|
||||
class Compile(Transformer):
|
||||
SIGNED_INT = str
|
||||
HEX_INT = 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})"
|
||||
|
||||
def ESCAPED_STRING(self, str_with_quotes):
|
||||
return f"(Bytecode)({str_with_quotes})"
|
||||
|
||||
NOT_PARENS = str
|
||||
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 DECIMAL(self, v):
|
||||
# fixed-point
|
||||
return int((float(v) * 1024) - 230000000)
|
||||
|
||||
def block(self, tree):
|
||||
# flatten children list
|
||||
flat = []
|
||||
for node in tree.children:
|
||||
if type(node) == list:
|
||||
flat += node
|
||||
elif isinstance(node, BaseCmd):
|
||||
flat.append(node)
|
||||
elif isinstance(node, Token) and (node.value.startswith("\n") or node.value == ";"):
|
||||
pass
|
||||
else:
|
||||
raise Exception(f"block statment {type(node)} is not a BaseCmd: {node}")
|
||||
return flat
|
||||
|
||||
def call(self, tree):
|
||||
# TODO: type checking etc
|
||||
return Cmd(0x43, *tree.children, meta=tree.meta)
|
||||
|
||||
def if_stmt(self, tree):
|
||||
a, op, b, block = tree.children
|
||||
for cmd in block:
|
||||
if isinstance(cmd, BaseCmd):
|
||||
cmd.add_context(IfCtx())
|
||||
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 loop_stmt(self, tree):
|
||||
expr = tree.children.pop(0) if len(tree.children) > 1 else 0
|
||||
block = tree.children[0]
|
||||
|
||||
for cmd in block:
|
||||
if isinstance(cmd, BaseCmd):
|
||||
cmd.add_context(LoopCtx())
|
||||
|
||||
return [ Cmd(0x05, expr, meta=tree.meta), *block, Cmd(0x06) ]
|
||||
|
||||
# loop..until pseudoinstruction
|
||||
def loop_until_stmt(self, tree):
|
||||
block, a, op, b = tree.children
|
||||
|
||||
for cmd in block:
|
||||
if isinstance(cmd, BaseCmd):
|
||||
cmd.add_context(LoopUntilCtx())
|
||||
|
||||
label = self.alloc.gen_label()
|
||||
|
||||
return [
|
||||
Cmd(0x03, label, meta=tree.meta), # label:
|
||||
*block,
|
||||
Cmd(op, a, b, meta=tree.meta), # if a op b
|
||||
Cmd(0x04, label, meta=tree.meta), # goto label
|
||||
Cmd(0x13, meta=tree.meta), # end if
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
def compile_script(s):
|
||||
tree = script_parser.parse(s)
|
||||
|
||||
#eprint(tree.pretty())
|
||||
|
||||
commands = Compile().transform(tree)
|
||||
|
||||
# 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 = ""
|
||||
|
||||
in_string = False
|
||||
string_escape = False
|
||||
|
||||
while True:
|
||||
char = f.read(1)
|
||||
|
||||
if len(char) == 0:
|
||||
# EOF
|
||||
return text
|
||||
|
||||
if string_escape == True:
|
||||
string_escape = False
|
||||
elif char == "(" and not in_string:
|
||||
depth += 1
|
||||
elif char == ")" and not in_string:
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
elif char == '"' and lex_strings:
|
||||
in_string = not in_string
|
||||
elif char == "\\" and in_string:
|
||||
string_escape = True
|
||||
|
||||
text += char
|
||||
|
||||
return text
|
||||
|
||||
def read_line():
|
||||
line = ""
|
||||
|
||||
while True:
|
||||
char = f.read(1)
|
||||
|
||||
if len(char) == 0:
|
||||
# EOF
|
||||
return line
|
||||
|
||||
if char == "\n":
|
||||
break
|
||||
|
||||
line += char
|
||||
|
||||
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 argv
|
||||
if __name__ == "__main__":
|
||||
line_no = 1
|
||||
char_no = 1
|
||||
file_info = []
|
||||
error = False
|
||||
|
||||
num_scripts = int(stdin.read())
|
||||
if num_scripts == 0:
|
||||
exit(0)
|
||||
print(f"compiling {num_scripts} scripts")
|
||||
|
||||
with open(argv[1], 'r') as f:
|
||||
macro_name = "" # captures recent UPPER_CASE identifier
|
||||
prev_char = ""
|
||||
while True:
|
||||
char = f.read(1)
|
||||
|
||||
if len(char) == 0:
|
||||
# EOF
|
||||
write(macro_name)
|
||||
break
|
||||
|
||||
if char == "#" and (prev_char == "\n" or prev_char == ""):
|
||||
# cpp line/file marker
|
||||
line = read_line()
|
||||
line_split = line[1:].split(" ")
|
||||
|
||||
line_no = int(line_split[0])
|
||||
file_info = line_split[1:]
|
||||
|
||||
write("#" + line + "\n")
|
||||
elif char == "(":
|
||||
filename = file_info[0][1:-1]
|
||||
|
||||
# SCRIPT(...)
|
||||
if macro_name == "SCRIPT":
|
||||
script_source, line_map = gen_line_map(read_until_closing_paren(lex_strings=True), source_line_no=line_no)
|
||||
|
||||
try:
|
||||
commands = compile_script(script_source)
|
||||
|
||||
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"{command}.to_bytecode() gave {type(word)} {word}")
|
||||
write(", ")
|
||||
write("\n")
|
||||
write("}")
|
||||
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:
|
||||
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))
|
||||
|
||||
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 transform error{ANSI_RESET}")
|
||||
traceback.print_exc()
|
||||
error = True
|
||||
except CompileError as e:
|
||||
line = line_map[e.meta.line]
|
||||
eprint(f"{filename}:{line}: {ANSI_RED}script compile error{ANSI_RESET}: {e}")
|
||||
error = True
|
||||
except Exception as e:
|
||||
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")
|
||||
else:
|
||||
# leave non-macro in source
|
||||
write(macro_name + char)
|
||||
|
||||
macro_name = ""
|
||||
elif char == "_" or (char >= 'A' and char <= 'Z'):
|
||||
macro_name += char
|
||||
else:
|
||||
write(macro_name + char)
|
||||
macro_name = ""
|
||||
|
||||
if char == "\n":
|
||||
char_no = 0
|
||||
line_no += 1
|
||||
|
||||
char_no += 1
|
||||
prev_char = char
|
||||
|
||||
if error:
|
||||
exit(1)
|
||||
else:
|
||||
with open(argv[1], "w") as f:
|
||||
f.write(write_buf)
|
||||
exit(0)
|
@ -2,7 +2,6 @@ name: "Paper Mario (North America)"
|
||||
basename: "papermario"
|
||||
options:
|
||||
find-file-boundaries: True
|
||||
pycparser_flags: ["-Iinclude", "-D_LANGUAGE_C", "-ffreestanding", "-DF3DEX_GBI_2", "-DSPLAT"]
|
||||
compiler: "GCC"
|
||||
mnemonic_ljust: 10
|
||||
ld_o_replace_extension: no
|
||||
|
Loading…
Reference in New Issue
Block a user