implement SCRIPT(...) dsl

This commit is contained in:
Alex Bates 2020-10-22 05:54:03 +01:00
parent 54c337596b
commit 83153acb59
No known key found for this signature in database
GPG Key ID: 5E11C2DB78877706
11 changed files with 255 additions and 31 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# Python
__pycache__/
venv/
.lark_cache*
# Misc
.idea/

View File

@ -8,7 +8,8 @@
],
"defines": [
"F3DEX_GBI_2",
"_LANGUAGE_C"
"_LANGUAGE_C",
"SCRIPT(...)={}"
],
"cStandard": "c89",
"cppStandard": "c++17",

View File

@ -3,6 +3,7 @@
"ms-vscode.cpptools",
"nanaian.vscode-star-rod",
"notskm.clang-tidy",
"EditorConfig.EditorConfig",
],
"unwantedRecommendations": [
"llvm-vs-code-extensions.vscode-clangd",

View File

@ -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",
}

View File

@ -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

View File

@ -97,7 +97,7 @@ $(BUILD_DIR)/%.s.o: %.s
$(AS) $(ASFLAGS) -o $@ $<
$(BUILD_DIR)/%.c.o: %.c $(H_FILES)
cpp $(CPPFLAGS) $< | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) - -o $@
cpp $(CPPFLAGS) $< | tools/compile_dsl_macros.py | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) - -o $@
$(BUILD_DIR)/%.bin.o: %.bin
$(LD) -r -b binary -o $@ $<

View File

@ -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

View File

@ -1,2 +1,3 @@
capstone
PyYAML
lark-parser

View File

@ -205,30 +205,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),
setgroup 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))
});

229
tools/compile_dsl_macros.py Executable file
View File

@ -0,0 +1,229 @@
#! /usr/bin/python3
from sys import stdin, stderr
from lark import Lark, exceptions, Transformer, v_args
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
#"""
script_parser = Lark(r"""
block: "{" line* "}"
?line: call
| if_stmt
| "return" -> return_stmt
| "setgroup" expr -> setgroup
call: CNAME "(" [expr ("," expr)* [","]] ")"
if_stmt: "if" expr if_op expr block ["else" block]
?if_op: "==" -> if_op_eq
?expr: c_const_expr
| ESCAPED_STRING
| SIGNED_INT
| DECIMAL
| HEX_INT
| CNAME
c_const_expr: "(" (c_const_expr | NOT_PARENS)+ ")"
NOT_PARENS: /[^()]+/
%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
%import common.NEWLINE
%ignore WS
""", start="block")#, parser="lalr", cache=True)
def si_cmd(opcode, *args):
return [opcode, len(args), *args]
@v_args(inline=True)
class Compile(Transformer):
SIGNED_INT = str
DECIMAL = float # TODO: fixed
HEX_INT = str
ESCAPED_STRING = eval
def CNAME(self, name):
return f"(Bytecode)({name})"
NOT_PARENS = str
def c_const_expr(self, *args): # usually a macro expansion
return f"(Bytecode)({' '.join(args)})"
def block(self, *lines):
flat = []
for line in lines:
if type(line) != list:
eprint(f"uncompiled: {line}")
else:
flat += line
return flat
def call(self, func, *args):
# TODO: type checking etc
return si_cmd(0x43, func, *args)
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 return_stmt(self): return si_cmd(0x02)
def setgroup(self, group):
return si_cmd(0x4D, group)
def compile_script(s):
tree = script_parser.parse(s)
#eprint(tree.pretty())
return Compile().transform(tree) + si_cmd(0x02) + si_cmd(0x01)
def read_until_closing_paren(depth=1, lex_strings=False):
text = ""
in_string = False
string_escape = False
while True:
char = stdin.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 = stdin.read(1)
if len(char) == 0:
# EOF
return line
if char == "\n":
break
line += char
return line
# Expects output from C preprocessor on stdin
if __name__ == "__main__":
line_no = 1
char_no = 1
file_info = []
error = False
macro_name = "" # captures recent UPPER_CASE identifier
prev_char = ""
while True:
char = stdin.read(1)
if len(char) == 0:
# EOF
write(macro_name)
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 == ""):
# 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 == "(":
# SCRIPT(...)
if macro_name == "SCRIPT":
script_source = read_until_closing_paren(lex_strings=True)
try:
bytecode = compile_script(script_source)
write("{")
for word in bytecode:
write(f"{word},")
write("}")
except exceptions.UnexpectedToken as e:
line = e.line + line_no
filename = file_info[0][1:-1]
eprint(e.get_context(script_source))
eprint(f"{filename}:{line}: script parse error: unexpected `{e.token}'")
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

View File

@ -2,9 +2,9 @@ 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
o_as_suffix: yes
segments:
- name: header
type: header