-
-
Save 20kdc/7b131f083317bbf19c291893092042c5 to your computer and use it in GitHub Desktop.
draft-liblcf-generator-with-defarrayvals (Just a slightly modified version, see EasyRPG/liblcf for original. Not really enough to warrant a whole fork unless I'm asked to PR this in.)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Copyright (c) 2016 liblcf authors | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the | |
"Software"), to deal in the Software without restriction, including | |
without limitation the rights to use, copy, modify, merge, publish, | |
distribute, sublicense, and/or sell copies of the Software, and to | |
permit persons to whom the Software is furnished to do so, subject to | |
the following conditions: | |
The above copyright notice and this permission notice shall be included | |
in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from __future__ import division | |
import sys | |
import os | |
import re | |
import shutil | |
import filecmp | |
from collections import namedtuple, OrderedDict | |
from itertools import groupby | |
import operator | |
from jinja2 import Environment, PackageLoader, select_autoescape | |
env = Environment( | |
loader=PackageLoader('templates', ''), | |
autoescape=select_autoescape([]), | |
keep_trailing_newline=True, | |
lstrip_blocks=True | |
) | |
gen_dir = os.path.dirname(os.path.abspath(__file__)) | |
csv_dir = os.path.join(gen_dir, "csv") | |
dest_dir = os.path.abspath(os.path.join(gen_dir, "..", "src", "generated")) | |
tmp_dir = os.path.join(dest_dir, "tmp") | |
cpp_types = { | |
'Boolean': 'bool', | |
'Double': 'double', | |
'UInt8': 'uint8_t', | |
'UInt16': 'uint16_t', | |
'UInt32': 'uint32_t', | |
'Int16': 'int16_t', | |
'Int32': 'int32_t', | |
'String': 'std::string', | |
} | |
# Additional Jinja 2 functions | |
def cpp_type(ty, prefix=True): | |
if ty in cpp_types: | |
return cpp_types[ty] | |
m = re.match(r'Array<(.*):(.*)>', ty) | |
if m: | |
return 'std::vector<%s>' % cpp_type(m.group(1), prefix) | |
m = re.match(r'(Vector|Array)<(.*)>', ty) | |
if m: | |
return 'std::vector<%s>' % cpp_type(m.group(2), prefix) | |
m = re.match(r'Ref<(.*):(.*)>', ty) | |
if m: | |
return cpp_type(m.group(2), prefix) | |
m = re.match(r'Ref<(.*)>', ty) | |
if m: | |
return 'int32_t' | |
m = re.match(r'Enum<(.*)>', ty) | |
if m: | |
return 'int32_t' | |
m = re.match(r'(.*)_Flags$', ty) | |
if m: | |
ty = m.expand(r'\1::Flags') | |
if prefix: | |
ty = 'RPG::' + ty | |
return ty | |
if prefix: | |
ty = 'RPG::' + ty | |
return ty | |
def pod_default_execexpr(dfl, vtype): | |
# There's an established(?) syntax that gets ignored, used for terrain data. | |
# In the form I am implementing it as: | |
# Value: '[' DefaultExpressionForValue ']' | |
# Multiply: Expr '*' Number | |
# Append: Expr '+' Expr | |
# Expr: Append | Multiply | Value | |
# I'm implementing it here | |
m = re.match(r'^(.*)\+(.*)$', dfl) | |
if m: | |
return pod_default_execexpr(m.group(1), vtype) + pod_default_execexpr(m.group(2), vtype) | |
m = re.match(r'^(.*)\*(.*)$', dfl) | |
if m: | |
return pod_default_execexpr(m.group(1), vtype) * int(m.group(2)) | |
m = re.match(r'^\[(.*)\]$', dfl) | |
if m: | |
return [m.group(1)] | |
print("Oh no! : " + dfl) | |
return None | |
# Returns the C++ expression for a given default value. | |
# Returns None for no default. | |
def pod_default_core(dfl, ftype): | |
# Not a POD, no default | |
if dfl == '' or dfl == '\'\'' or ftype.startswith('Array'): | |
return None | |
m = re.match(r'^Vector<(.*)>$', ftype) | |
if m: | |
vtype = m.group(1) | |
lst = pod_default_execexpr(dfl, vtype) | |
dfl = '' | |
for v in lst: | |
if dfl != '': | |
dfl += ', ' | |
dfl += v | |
return '{' + dfl + '}' | |
if ftype == 'Boolean': | |
dfl = dfl.lower() | |
elif ftype == 'String': | |
dfl = '"' + dfl[1:-1] + '"' | |
if '|' in dfl: | |
dfl = -1 | |
return str(dfl) | |
# Returns a postfix. | |
def pod_default(field): | |
pdc = pod_default_core(field.default, field.type) | |
if pdc: | |
return " = " + pdc | |
return "" | |
def flag_size(flag): | |
return (len(flag) + 7) // 8 | |
def flag_set(field, bit): | |
bit -= 1 | |
try: | |
res = bool(int(field.default) & (1 << bit)) | |
except ValueError: | |
# Default was not an int | |
res = False | |
return str(res).lower() | |
def filter_structs_without_codes(structs): | |
for struct in structs: | |
if all(f.code for f in sfields[struct.name]): | |
yield struct | |
def filter_unused_fields(fields): | |
for field in fields: | |
if field.type: | |
yield field | |
# End of Jinja 2 functions | |
int_types = { | |
'UInt8': 'uint8_t', | |
'UInt16': 'uint16_t', | |
'UInt32': 'uint32_t', | |
'Int16': 'int16_t', | |
'Int32': 'int32_t' | |
} | |
def struct_headers(ty, header_map): | |
if ty == 'String': | |
return ['<string>'] | |
if ty in int_types: | |
return ['<stdint.h>'] | |
if ty in cpp_types: | |
return [] | |
m = re.match(r'Ref<(.*):(.*)>', ty) | |
if m: | |
return struct_headers(m.group(2), header_map) | |
if re.match(r'Ref<(.*)>', ty): | |
return [] | |
if re.match(r'Enum<(.*)>', ty): | |
return [] | |
if re.match(r'(.*)_Flags$', ty): | |
return [] | |
m = re.match(r'Array<(.*):(.*)>', ty) | |
if m: | |
return ['<vector>'] + struct_headers(m.group(1), header_map) | |
m = re.match(r'(Vector|Array)<(.*)>', ty) | |
if m: | |
return ['<vector>'] + struct_headers(m.group(2), header_map) | |
header = header_map.get(ty) | |
if header is not None: | |
return ['"rpg_%s.h"' % header] | |
if ty in ['Parameters', 'Equipment', 'EventCommand', 'MoveCommand', 'Rect', 'TreeMap']: | |
return ['"rpg_%s.h"' % ty.lower()] | |
return [] | |
def process_file(filename, namedtup): | |
# Mapping is: All elements of the line grouped by the first column | |
result = OrderedDict() | |
with open(os.path.join(csv_dir, filename), 'r') as f: | |
lines = [] | |
for line in f: | |
sline = line.strip() | |
if not sline: | |
continue | |
if sline.startswith("#"): | |
continue | |
lines.append(sline.split(',')) | |
for k, g in groupby(lines, operator.itemgetter(0)): | |
result[k] = list(map(lambda x: namedtup(*x[1:]), list(g))) | |
return result | |
def get_structs(filename='structs.csv'): | |
Struct = namedtuple("Struct", "name hasid") | |
result = process_file(filename, Struct) | |
processed_result = OrderedDict() | |
for k, struct in result.items(): | |
processed_result[k] = [] | |
for elem in struct: | |
elem = Struct(elem.name, bool(int(elem.hasid)) if elem.hasid else None) | |
processed_result[k].append(elem) | |
return processed_result | |
def get_fields(filename='fields.csv'): | |
Field = namedtuple("Field", "name size type code default comment") | |
result = process_file(filename, Field) | |
processed_result = OrderedDict() | |
for k, field in result.items(): | |
processed_result[k] = [] | |
for elem in field: | |
elem = Field( | |
elem.name, | |
True if elem.size == 't' else False, | |
elem.type, | |
0 if elem.code == '' else int(elem.code, 0), | |
elem.default, | |
elem.comment) | |
processed_result[k].append(elem) | |
return processed_result | |
def get_enums(filename='enums.csv'): | |
result = process_file(filename, namedtuple("Enum", "entry value index")) | |
new_result = OrderedDict() | |
# Additional processing to group by the Enum Entry | |
# Results in e.g. EventCommand -> Code -> List of (Name, Index) | |
for k, v in result.items(): | |
new_result[k] = OrderedDict() | |
for kk, gg in groupby(v, operator.attrgetter("entry")): | |
new_result[k][kk] = list(map(lambda x: (x.value, x.index), gg)) | |
return new_result | |
def get_flags(filename='flags.csv'): | |
return process_file(filename, namedtuple("Flag", "field")) | |
def get_setup(filename='setup.csv'): | |
return process_file(filename, namedtuple("Setup", "method headers")) | |
def get_headers(): | |
header_map = dict() | |
structs_flat = [] | |
for filetype, struct in structs.items(): | |
for elem in struct: | |
structs_flat.append(elem) | |
header_map[elem.name] = elem.name.lower() | |
result = {} | |
for struct in structs_flat: | |
struct_name = struct.name | |
if struct_name not in sfields: | |
continue | |
headers = set() | |
for field in sfields[struct_name]: | |
ftype = field.type | |
if not ftype: | |
continue | |
headers.update(struct_headers(ftype, header_map)) | |
if struct_name in setup: | |
for s in setup[struct_name]: | |
if s.headers: | |
headers.update([s.headers]) | |
result[struct_name] = sorted(x for x in headers if x[0] == '<') + sorted(x for x in headers if x[0] == '"') | |
return result | |
def needs_ctor(struct_name): | |
return struct_name in setup and any('Init()' in method | |
for method, hdrs in setup[struct_name]) | |
def generate(): | |
if not os.path.exists(tmp_dir): | |
os.mkdir(tmp_dir) | |
for filetype in ['ldb','lmt','lmu','lsd']: | |
filepath = os.path.join(tmp_dir, '%s_chunks.h' % filetype) | |
with open(filepath, 'w') as f: | |
f.write(chunk_tmpl.render( | |
type=filetype | |
)) | |
for filetype, structlist in structs.items(): | |
for struct in structlist: | |
filename = struct.name.lower() | |
if struct.hasid is not None: | |
if struct.name not in sfields: | |
continue | |
filepath = os.path.join(tmp_dir, '%s_%s.cpp' % (filetype, filename)) | |
with open(filepath, 'w') as f: | |
f.write(lcf_struct_tmpl.render( | |
struct_name=struct.name, | |
type=filetype | |
)) | |
if needs_ctor(struct.name): | |
filepath = os.path.join(tmp_dir, 'rpg_%s.cpp' % filename) | |
with open(filepath, 'w') as f: | |
f.write(rpg_source_tmpl.render( | |
struct_name=struct.name, | |
filename=filename | |
)) | |
filepath = os.path.join(tmp_dir, 'rpg_%s.h' % filename) | |
with open(filepath, 'w') as f: | |
f.write(rpg_header_tmpl.render( | |
struct_name=struct.name, | |
has_id=struct.hasid | |
)) | |
if struct.name in flags: | |
filepath = os.path.join(tmp_dir, '%s_%s_flags.cpp' % (filetype, filename)) | |
with open(filepath, 'w') as f: | |
f.write(flags_tmpl.render( | |
struct_name=struct.name, | |
type=filetype | |
)) | |
for tmp_file in os.listdir(tmp_dir): | |
tmp_path = os.path.join(tmp_dir, tmp_file) | |
dest_path = os.path.join(dest_dir, tmp_file) | |
if not (os.path.exists(dest_path) and filecmp.cmp(tmp_path, dest_path)): | |
shutil.copyfile(tmp_path, dest_path) | |
os.remove(tmp_path) | |
os.rmdir(tmp_dir) | |
def main(argv): | |
if not os.path.exists(dest_dir): | |
os.mkdir(dest_dir) | |
global structs, sfields, enums, flags, setup, headers | |
global chunk_tmpl, lcf_struct_tmpl, rpg_header_tmpl, rpg_source_tmpl, flags_tmpl | |
structs = get_structs() | |
sfields = get_fields() | |
enums = get_enums() | |
flags = get_flags() | |
setup = get_setup() | |
headers = get_headers() | |
# Setup Jinja | |
env.filters["cpp_type"] = cpp_type | |
env.filters["pod_default"] = pod_default | |
env.filters["struct_has_code"] = filter_structs_without_codes | |
env.filters["field_is_used"] = filter_unused_fields | |
env.filters["flag_size"] = flag_size | |
env.filters["flag_set"] = flag_set | |
env.tests['needs_ctor'] = needs_ctor | |
globals = dict( | |
structs=structs, | |
fields=sfields, | |
flags=flags, | |
enums=enums, | |
setup=setup, | |
headers=headers | |
) | |
chunk_tmpl = env.get_template('chunks.tmpl', globals=globals) | |
lcf_struct_tmpl = env.get_template('reader.tmpl', globals=globals) | |
rpg_header_tmpl = env.get_template('rpg_header.tmpl', globals=globals) | |
rpg_source_tmpl = env.get_template('rpg_source.tmpl', globals=globals) | |
flags_tmpl = env.get_template('flag_reader.tmpl', globals=globals) | |
generate() | |
if __name__ == '__main__': | |
main(sys.argv) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment