pokegold-spaceworld/tools/compile_text.py
2019-03-13 00:08:15 +10:00

175 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Compile strings from CSVs to the text bank and
into macros for inclusion in assembly files.
"""
import csv
import os
import sys
from collections import namedtuple
from separate_text import *
String = namedtuple('String', ('label', 'lines', 'space', 'collapse'))
def strings_in_csv(csv_path):
strings = []
total_space = 0
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader)
target_space = int(next(reader)[1])
next(reader)
total_space = 0
for label, num_bytes, collapse_bytes, jp_starts, _, text in (row[:6] for row in reader):
num_bytes = int(num_bytes)
jp_starts = jp_starts.split('\n')
text = text.strip()
collapse_bytes = int(collapse_bytes)
starts_present = set(filter(
lambda start: start and not LINE_STARTS[start].is_end,
jp_starts
))
if starts_present == {'db'}:
# Where each line is just a db, starts should be a bunch of dbs.
lines = [Line('db', line) for line in text.split('\n')]
elif 'next' in starts_present:
# Menus and such get the nexty treatment.
raw_lines = text.split('\n')
lines = []
lines.append(Line(jp_starts[0], raw_lines[0]))
lines += [Line('next', line) for line in raw_lines[1:]]
else:
# Otherwise, we gotta use an a bit more ~sophisticated~ strategy!
lines = []
line_in_paragraph = 0
for line_num, line in enumerate(text.split('\n')):
if line_in_paragraph == 0:
if line_num == 0:
start = jp_starts[0]
else:
start = 'para'
elif line_in_paragraph == 1:
start = 'line'
else:
start = 'cont'
if line:
lines.append(Line(start, line))
line_in_paragraph += 1
else:
line_in_paragraph = 0
if LINE_STARTS[jp_starts[-1]].is_end:
lines.append(Line(jp_starts[-1], None))
strings.append(String(label, lines, num_bytes, collapse_bytes))
total_space += num_bytes
if total_space != target_space:
raise ValueError("Number of bytes allocated for strings doesn't match the target size.")
return strings
def to_asm(string):
asm = ""
for line in string.lines:
if line.start == 'para':
asm += "\n"
if line.text is None:
asm += "\t{}\n".format(line.start)
else:
asm += "\t{} \"{}\"\n".format(line.start, line.text.replace('"', '\\"'))
return asm
def compile(csv_path):
strings = strings_in_csv(csv_path)
macro_code = ""
extra_macro_code = ""
text_bank_code = ""
extra_macro_code += "textpad_{}: MACRO\n".format(os.path.splitext(os.path.basename(csv_path))[0])
for row_num, (string, macro) in enumerate(zip(strings, string_macros(strings)), 4):
macro_code += "{}: MACRO\n".format(macro)
# If more space is needed in the patch text bank, this can be modified
# to fill as much as possible of the space available before the far
# text control code. For now, though, this is clean and nice!
num_bytes = num_bytes_in(string)
if num_bytes <= string.space:
# Translations that fit within the space of the original should go there.
macro_code += to_asm(string)
else:
# Otherwise, they need to get put in the fearsome PATCH TEXT BANK!
label = macro[0].upper() + macro[1:]
first_start = string.lines[0].start
if first_start == 'db':
raise ValueError(
"DB string too long (row {})! "
"Adjust some byte lengths, maybe?".format(row_num)
)
macro_code += "\t{} \"<FAR_TEXT>\"\n".format(first_start)
macro_code += "\tdw {}\n".format(label)
macro_code += "\tdb \"@\"\n"
num_bytes = 5
if num_bytes > string.space:
raise ValueError(
"Too little space for a far text code (row {})! "
"Make space for 5 bytes, maybe?".format(row_num)
)
if string.lines[0].start == 'text':
# Since far text already starts with a text command, that shouldn't
# be included in the string. Remove this if the far text is
# changed to use a script command instead of a control code.
string.lines[0] = Line('db', string.lines[0].text)
text_bank_code += "{}::\n".format(label)
text_bank_code += to_asm(string)
text_bank_code += "\n"
if num_bytes < string.space:
if string.collapse > 0:
extra_macro_code += "\nREPT {} - {}\n\tnop\nENDR\n".format(string.space, num_bytes)
else:
macro_code += "\nREPT {} - {}\n\tdb \"@\"\nENDR\n".format(string.space, num_bytes)
macro_code += "ENDM\n\n"
extra_macro_code += "ENDM\n\n"
macro_code += extra_macro_code
return macro_code, text_bank_code
def main(argv):
script_name = os.path.split(argv[0])[-1]
if len(argv) == 1:
print("Usage: {} <CSV input files...>".format(script_name))
return 1
for csv_path in argv[1:]:
print ("Compiling \"{}\" to .inc and .asm...".format(csv_path))
macro_code, text_bank_code = compile(csv_path)
inc_path = os.path.join('build', os.path.splitext(csv_path)[0] + '.inc')
asm_path = os.path.splitext(inc_path)[0] + '.asm'
os.makedirs(os.path.split(inc_path)[0], exist_ok=True)
with open(inc_path, 'w', encoding='utf-8') as f:
f.write(macro_code)
with open(asm_path, 'w', encoding='utf-8') as f:
f.write(text_bank_code)
if __name__ == '__main__':
sys.exit(main(sys.argv))