# coding: utf-8 """ RGBDS BSS section and constant parsing. """ import os import os.path def separate_comment(line): if ';' in line: i = line.find(';') return line[:i], line[i:] return line, None def rgbasm_to_py(text): return text.replace('$', '0x').replace('%', '0b') def make_wram_labels(wram_sections): wram_labels = {} for section in wram_sections: for label in section['labels']: if label['address'] not in wram_labels.keys(): wram_labels[label['address']] = [] wram_labels[label['address']] += [label['label']] return wram_labels def bracket_value(string, i=0): return string.split('[')[1 + i*2].split(']')[0] class BSSReader: """ Read rgbasm BSS/WRAM sections and associate labels with addresses. Also reads constants/variables, even in macros. """ sections = [] section = None address = None macros = {} constants = {} section_types = { 'VRAM': 0x8000, 'SRAM': 0xa000, 'WRAM0': 0xc000, 'WRAMX': 0xd000, 'HRAM': 0xff80, } def __init__(self, *args, **kwargs): self.__dict__.update(kwargs) def read_bss_line(self, l): parts = l.strip().split(' ') token = parts[0].strip() params = ' '.join(parts[1:]).split(',') if token in ['ds', 'db', 'dw']: if any(params): length = eval(rgbasm_to_py(params[0]), self.constants.copy()) else: length = {'ds': 1, 'db': 1, 'dw': 2}[token] self.address += length # assume adjacent labels to use the same space for label in self.section['labels'][::-1]: if label['length'] == 0: label['length'] = length else: break elif token in self.macros.keys(): macro_text = '\n'.join(self.macros[token]) + '\n' for i, p in enumerate(params): macro_text = macro_text.replace('\\'+str(i+1),p) macro_text = macro_text.split('\n') macro_reader = BSSReader( sections = list(self.sections), section = dict(self.section), address = self.address, constants = self.constants, ) macro_sections = macro_reader.read_bss_sections(macro_text) self.section = macro_sections[-1] if self.section['labels']: self.address = self.section['labels'][-1]['address'] + self.section['labels'][-1]['length'] def read_bss_sections(self, bss): if self.section is None: self.section = { "labels": [], } if type(bss) is str: bss = bss.split('\n') macro = False macro_name = None for line in bss: line = line.lstrip() line, comment = separate_comment(line) line = line.strip() split_line = line.split() split_line_upper = map(str.upper, split_line) if not line: pass elif line[-4:].upper() == 'ENDM': macro = False macro_name = None elif macro: self.macros[macro_name] += [line] elif line[-5:].upper() == 'MACRO': macro_name = line.split(':')[0] macro = True self.macros[macro_name] = [] elif 'INCLUDE' == line[:7].upper(): filename = line.split('"')[1] if os.path.exists("src/"): self.read_bss_sections(open("src/" + filename).readlines()) else: self.read_bss_sections(open(filename).readlines()) elif 'SECTION' == line[:7].upper(): if self.section: # previous self.sections += [self.section] section_def = line.split(',') name = section_def[0].split('"')[1] type_ = section_def[1].strip() if len(section_def) > 2: bank = bracket_value(section_def[2]) else: bank = None if '[' in type_: self.address = int(rgbasm_to_py(bracket_value(type_)), 16) else: if self.address == None or bank != self.section['bank'] or self.section['type'] != type_: self.address = self.section_types.get(type_, self.address) # else: keep going from this address self.section = { 'name': name, 'type': type_, 'bank': bank, 'start': self.address, 'labels': [], } elif ':' in line: # rgbasm allows labels without :, but prefer convention label = line[:line.find(':')] if '\\' in label: raise Exception(line + ' ' + label) if ';' not in label: section_label = { 'label': label, 'address': self.address, 'length': 0, } self.section['labels'] += [section_label] self.read_bss_line(line.split(':')[-1]) elif any(x in split_line_upper for x in ['EQU', '=', 'SET']): # TODO: refactor for x in ['EQU', '=', 'SET']: if x in split_line_upper: index = split_line_upper.index(x) real = split_line[index] name, value = map(' '.join, [split_line[:index], split_line[index+1:]]) value = rgbasm_to_py(value) self.constants[name] = eval(value, self.constants.copy()) else: self.read_bss_line(line) self.sections += [self.section] return self.sections def read_bss_sections(bss): reader = BSSReader() return reader.read_bss_sections(bss) def constants_to_dict(constants): """Deprecated. Use BSSReader.""" return dict((eval(rgbasm_to_py(constant[constant.find('EQU')+3:constant.find(';')])), constant[:constant.find('EQU')].strip()) for constant in constants) def scrape_constants(text): if type(text) is not list: text = text.split('\n') bss = BSSReader() bss.read_bss_sections(text) constants = bss.constants return {v: k for k, v in constants.items()} def read_constants(filepath): """ Load lines from a file and grab any constants using BSSReader. """ lines = [] if os.path.exists(filepath): with open(filepath, "r") as file_handler: lines = file_handler.readlines() return scrape_constants(lines) class WRAMProcessor(object): """ RGBDS BSS section and constant parsing. """ def __init__(self, config): """ Setup for WRAM parsing. """ self.config = config self.paths = {} if os.path.exists("src/"): path = "src/" else: path = "" if hasattr(self.config, "wram"): self.paths["wram"] = self.config.wram else: self.paths["wram"] = os.path.join(self.config.path, path + "wram.asm") if hasattr(self.config, "hram"): self.paths["hram"] = self.config.hram else: self.paths["hram"] = os.path.join(self.config.path, path + "hram.asm") if hasattr(self.config, "gbhw"): self.paths["gbhw"] = self.config.gbhw else: self.paths["gbhw"] = os.path.join(self.config.path, path + "gbhw.asm") def initialize(self): """ Read constants. """ self.setup_wram_sections() self.setup_wram_labels() self.setup_hram_constants() self.setup_gbhw_constants() self.reformat_wram_labels() def read_wram_sections(self): """ Opens the wram file and calls read_bss_sections. """ wram_content = None wram_file_path = self.paths["wram"] with open(wram_file_path, "r") as wram: wram_content = wram.readlines() wram_sections = read_bss_sections(wram_content) return wram_sections def setup_wram_sections(self): """ Call read_wram_sections and set a variable. """ self.wram_sections = self.read_wram_sections() return self.wram_sections def setup_wram_labels(self): """ Make wram labels based on self.wram_sections as input. """ self.wram_labels = make_wram_labels(self.wram_sections) return self.wram_labels def read_hram_constants(self): """ Read constants from hram.asm using read_constants. """ hram_constants = read_constants(self.paths["hram"]) return hram_constants def setup_hram_constants(self): """ Call read_hram_constants and set a variable. """ self.hram_constants = self.read_hram_constants() return self.hram_constants def read_gbhw_constants(self): """ Read constants from gbhw.asm using read_constants. """ gbhw_constants = read_constants(self.paths["gbhw"]) return gbhw_constants def setup_gbhw_constants(self): """ Call read_gbhw_constants and set a variable. """ self.gbhw_constants = self.read_gbhw_constants() return self.gbhw_constants def reformat_wram_labels(self): """ Flips the wram_labels dictionary the other way around to access addresses by label. """ self.wram = {} for (address, labels) in self.wram_labels.iteritems(): for label in labels: self.wram[label] = address