preprocess asm files individually

this fixes a lot of previous hacks

first off, rgbds requires that labels from includes be marked as globals.
instead, 3626ddeb stuffed includes into the parent file in the preprocessor.
this meant one huge file got preprocessed every time, adding an additional
ten seconds to compile time.

running the preprocessor once for each file would create too much overhead,
so a list is fed into prequeue.py, which then makes calls to preprocessor.py.

this paves the way for compiling source files separately some day.

next, compiling previously required `make clean` to be executed first.
f3340de6 touched main.asm to force a fresh compile instead. this behavior
has been reverted. now, `make all` will only attempt to recompile if a
source file has changed.

preprocessor.py has some marginal changes. prequeue.py is created to keep
the original functionality of preprocessor.py intact. so many files are
preprocessed on first compile (1951 as of this commit) that the prequeue
call has been hidden.

compile time is reduced to 15-30 seconds on first compile, and 5-10 seconds
subsequently. the majority of this time is spent in rgbasm.
This commit is contained in:
yenatch 2013-06-21 00:58:35 -04:00
parent f3340de6dc
commit e733c4234c
3 changed files with 72 additions and 119 deletions

View File

@ -1,33 +1,23 @@
.SUFFIXES: .asm .tx .o .gbc .png .2bpp .lz .SUFFIXES: .asm .tx .o .gbc .png .2bpp .lz
TEXTFILES = \ TEXTFILES := $(shell find ./ -type f -name '*.asm' | grep -v pokecrystal.asm | grep -v constants.asm | grep -v gbhw.asm | grep -v hram.asm | grep -v constants | grep -v wram.asm)
text/sweethoney.tx \ TEXTQUEUE :=
text/phone/bill.tx \
text/phone/elm.tx \
text/phone/mom.tx \
text/phone/trainers1.tx \
text/common.tx \
text/common_2.tx \
text/common_3.tx \
main.tx
PNG_GFX = $(shell find gfx/ -type f -name '*.png') PNG_GFX := $(shell find gfx/ -type f -name '*.png')
LZ_GFX = $(shell find gfx/ -type f -name '*.lz') LZ_GFX := $(shell find gfx/ -type f -name '*.lz')
TWOBPP_GFX = $(shell find gfx/ -type f -name '*.2bpp') TWOBPP_GFX := $(shell find gfx/ -type f -name '*.2bpp')
all: pokecrystal.gbc all: pokecrystal.gbc
cmp baserom.gbc $< cmp baserom.gbc $<
clean: clean:
rm -f main.tx pokecrystal.o pokecrystal.gbc ${TEXTFILES} rm -f pokecrystal.o pokecrystal.gbc
pokecrystal.o: pokecrystal.asm constants.asm wram.asm ${TEXTFILES} lzs @echo rm -f $$\(TEXTFILES:.asm=.tx\)
@rm -f $(TEXTFILES:.asm=.tx)
pokecrystal.o: $(TEXTFILES:.asm=.tx) pokecrystal.asm constants.asm wram.asm lzs
python prequeue.py $(TEXTQUEUE)
rgbasm -o pokecrystal.o pokecrystal.asm rgbasm -o pokecrystal.o pokecrystal.asm
pokecrystal.asm: depend
depend:
@touch main.asm
.asm.tx: .asm.tx:
python preprocessor.py < $< > $@ $(eval TEXTQUEUE := $(TEXTQUEUE) $<)
pokecrystal.gbc: pokecrystal.o pokecrystal.gbc: pokecrystal.o
rgblink -o $@ $< rgblink -o $@ $<

View File

@ -310,24 +310,14 @@ def separate_comment(l):
""" """
Separates asm and comments on a single line. Separates asm and comments on a single line.
""" """
asm = ""
comment = None
in_quotes = False in_quotes = False
for i in xrange(len(l)):
# token either belongs to the line or to the comment if not in_quotes:
for token in l: if l[i] == ";":
if comment: break
comment += token if l[i] == "\"":
else: in_quotes = not in_quotes
if not in_quotes: return i
if token == ";":
comment = ";"
continue
if token == "\"":
in_quotes = not in_quotes
asm += token
return asm, comment
def quote_translator(asm): def quote_translator(asm):
""" """
@ -335,63 +325,50 @@ def quote_translator(asm):
""" """
# split by quotes # split by quotes
asms = asm.split("\"") asms = asm.split('"')
# skip asm that actually does use ASCII in quotes # skip asm that actually does use ASCII in quotes
lowasm = asms[0].lower() if "SECTION" in asms[0]\
or "INCBIN" in asms[0]\
if "section" in lowasm \ or "INCLUDE" in asms[0]:
or "incbin" in lowasm: return asm
sys.stdout.write(asm)
return
print_macro = False print_macro = False
if asms[0].strip() == 'print': if asms[0].strip() == 'print':
asms[0] = asms[0].replace('print','db 0,') asms[0] = asms[0].replace('print','db 0,')
print_macro = True print_macro = True
output = "" output = ''
even = False even = False
i = 0
for token in asms: for token in asms:
i = i + 1
if even: if even:
characters = [] characters = []
# token is a string to convert to byte values # token is a string to convert to byte values
while len(token): while len(token):
# read a single UTF-8 codepoint # read a single UTF-8 codepoint
char = token[0] char = token[0]
if ord(char) >= 0xFC: if ord(char) < 0xc0:
char = char + token[1:6] token = token[1:]
token = token[6:] # certain apostrophe-letter pairs are considered a single character
elif ord(char) >= 0xF8: if char == "'" and token:
char = char + token[1:5] if token[0] in 'dlmrstv':
token = token[5:] char += token[0]
elif ord(char) >= 0xF0: token = token[1:]
char = char + token[1:4] elif ord(char) < 0xe0:
token = token[4:]
elif ord(char) >= 0xE0:
char = char + token[1:3]
token = token[3:]
elif ord(char) >= 0xC0:
char = char + token[1:2] char = char + token[1:2]
token = token[2:] token = token[2:]
elif ord(char) < 0xf0:
char = char + token[1:3]
token = token[3:]
elif ord(char) < 0xf8:
char = char + token[1:4]
token = token[4:]
elif ord(char) < 0xfc:
char = char + token[1:5]
token = token[5:]
else: else:
token = token[1:] char = char + token[1:6]
token = token[6:]
# certain apostrophe-letter pairs are only a single byte
if char == "'" and len(token) > 0 and \
(token[0] == "d" or \
token[0] == "l" or \
token[0] == "m" or \
token[0] == "r" or \
token[0] == "s" or \
token[0] == "t" or \
token[0] == "v"):
char = char + token[0]
token = token[1:]
characters += [char] characters += [char]
if print_macro: if print_macro:
@ -421,32 +398,26 @@ def quote_translator(asm):
output += ", ".join(["${0:02X}".format(chars[char]) for char in characters]) output += ", ".join(["${0:02X}".format(chars[char]) for char in characters])
# if not even
else: else:
output += (token) output += token
even = not even even = not even
sys.stdout.write(output) return output
return
def extract_token(asm): def extract_token(asm):
token = asm.split(" ")[0].replace("\t", "").replace("\n", "") return asm.split(" ")[0].strip()
return token
def make_macro_table(): def make_macro_table():
return dict([(macro.macro_name, macro) for macro in macros]) return dict(((macro.macro_name, macro) for macro in macros))
macro_table = make_macro_table() macro_table = make_macro_table()
def macro_test(asm): def macro_test(asm):
""" """
Returns a matching macro, or None/False. Returns a matching macro, or None/False.
""" """
# macros are determined by the first symbol on the line # macros are determined by the first symbol on the line
token = extract_token(asm) token = extract_token(asm)
# check against all names # check against all names
if token in macro_table: if token in macro_table:
return (macro_table[token], token) return (macro_table[token], token)
@ -600,64 +571,45 @@ def macro_translator(macro, token, line):
sys.stdout.write(output) sys.stdout.write(output)
def include_file(asm):
"""This is more reliable than rgbasm/rgbds including files on its own."""
prefix = asm.split("INCLUDE \"")[0] + '\n'
filename = asm.split("\"")[1]
suffix = asm.split("\"")[2]
read_line(prefix)
lines = open(filename, "r").readlines()
for line in lines:
read_line(line)
read_line(suffix)
def read_line(l): def read_line(l):
"""Preprocesses a given line of asm.""" """Preprocesses a given line of asm."""
# strip and store any comment on this line # strip comments
if ";" in l: asm, comment = l[:separate_comment(l)], l[separate_comment(l):]
asm, comment = separate_comment(l)
else:
asm = l
comment = None
# handle INCLUDE as a special case # export all labels
if "INCLUDE \"" in l: if ':' in asm[:asm.find('"')]:
include_file(asm) sys.stdout.write('GLOBAL ' + asm.split(':')[0] + '\n')
# expect preprocessed .asm files
if "INCLUDE" in asm:
asm = asm.replace('.asm','.tx')
sys.stdout.write(asm)
# ascii string macro preserves the bytes as ascii (skip the translator) # ascii string macro preserves the bytes as ascii (skip the translator)
elif len(asm) > 6 and "\tascii " in [asm[:7], "\t" + asm[:6]]: elif len(asm) > 6 and "ascii " == asm[:6] or "\tascii " == asm[:7]:
asm = asm.replace("ascii", "db", 1) asm = asm.replace("ascii", "db", 1)
sys.stdout.write(asm) sys.stdout.write(asm)
# convert text to bytes when a quote appears (not in a comment) # convert text to bytes when a quote appears (not in a comment)
elif "\"" in asm: elif "\"" in asm:
quote_translator(asm) sys.stdout.write(quote_translator(asm))
# check against other preprocessor features # check against other preprocessor features
else: else:
macro, token = macro_test(asm) macro, token = macro_test(asm)
if macro: if macro:
macro_translator(macro, token, asm) macro_translator(macro, token, asm)
else: else:
sys.stdout.write(asm) sys.stdout.write(asm)
sys.stdout.write(comment)
# show line comment
if comment != None:
sys.stdout.write(comment)
def preprocess(lines=None): def preprocess(lines=None):
"""Main entry point for the preprocessor.""" """Main entry point for the preprocessor."""
if not lines: if not lines:
# read each line from stdin # read each line from stdin
lines = sys.stdin lines = (sys.stdin.readlines())
elif not isinstance(lines, list): elif not isinstance(lines, list):
# split up the input into individual lines # split up the input into individual lines
lines = lines.split("\n") lines = lines.split("\n")

11
prequeue.py Normal file
View File

@ -0,0 +1,11 @@
import os
import sys
import preprocessor
if __name__ == '__main__':
for source in sys.argv[1:]:
dest = os.path.splitext(source)[0] + '.tx'
sys.stdin = open(source, 'r')
sys.stdout = open(dest, 'w')
preprocessor.preprocess()