#!/usr/bin/env python # -*- coding: utf-8 -*- ''' File: z80_syntax_checker.py Author: Romain Giot Description: Quick'n dirty z80 syntax file checker ''' """simple Z80 syntax checker. Quick and dirty tool to find small errors in z80 files, and detect them before assembling a big project. Syntax checking is not supposed to be perfect and efficient. But it can quickly detect some errors. """ # TODO add: ex de, hl ; push sp ; add a # TODO remove duplciate alert when set is used # TODO raise errors when instructions use hl and ix together # imports import sys, os import re # Regex for macros RE_OPEN_MACRO = re.compile('^\s*(macro|MACRO)\s+(\S+)' ) RE_CLOSE_MACRO = re.compile('^\s*(endm(acro)?|ENDM(ACRO))\s+' ) # Classic registers STR_16_REGISTERS = '(hl|bc|de|ix|iy)' STR_8_REGISTERS = '(h|l|b|c|d|e|ixl|ixh|a)' RE_LOAD_REGISTER_SYNTAX = re.compile('\s*ld\s+(%s|%s)\s*,?\s*(;.*)?$' \ % (STR_8_REGISTERS, STR_16_REGISTERS) , re.IGNORECASE) # Regex for labels RE_LABELS = re.compile('^([A-Z_.][A-Z_0-9]+)', re.IGNORECASE) def _get_all_lines(source): """Load all the lines of the file.""" f = open(source) lines = f.readlines() f.close() return lines class Z80_Parser(object): """Parse a z80 source file. The source file is not supposed to be assembled alone. """ def __init__(self, fname): """Parse the source file.""" lines = _get_all_lines(fname) self._fname = fname self.check_macros(lines) self.check_load_syntax(lines) self.check_labels(lines) def emit_error(self, line, col, message): """Emit an error (does not use the column value)""" sys.stderr.write("%s:%d Error %s\n" % (self._fname, line, message)) def emit_warning(self, line, col, message): """Emit an error (does not use the column value)""" sys.stderr.write("%s:%d Warning %s\n" % (self._fname, line, message)) def check_load_syntax(self, lines): """Check the syntax for loading registers""" for i, line in enumerate(lines): load_line = i+1 load_from_mem = re.match(RE_LOAD_REGISTER_SYNTAX, line) if load_from_mem: register = load_from_mem.group(1) self.emit_error(load_line, max(line.find('LD'), line.find('ld')), 'Register %s not loaded' % register) def check_labels(self, lines, min_width=8, max_width=50): """Check the validity of labels. - Duplicates - Size """ latest_parent = '' labels = [] for i, line in enumerate(lines): label_line = i +1 label_res = re.match(RE_LABELS, line) if label_res: # Get the label value (manage internal labels) label = label_res.group(1) if label.startswith('.'): real_label = latest_parent + label else: real_label = label latest_parent = label # Check if label already exists if real_label in labels: self.emit_error(label_line, 1, 'Label %s already exists' \ % real_label) else: labels.append(real_label) # Check size validity if len(label) < min_width and label[0]!='.': #do not test for local ones self.emit_warning(label_line, 1, 'Label %s too short' % label) elif len(label) > max_width: self.emit_warning(label_line, 1, 'Label %s too long' % label) def check_macros(self, lines): """Verify syntax macro""" opened_macro = 0 macro_line = -1 macro_names = [] for i, line in enumerate(lines): macro_line = i+ 1 # macro opening detection open_re = re.match(RE_OPEN_MACRO, line) if open_re: macro_name = open_re.group(2) # check for included macros if opened_macro: self.emit_error(macro_line, -1, 'Macro %s opened inside another one (%s)' \ %(macro_name, macro_names[-1])) # check for duplicated macros if macro_name in macro_names: self.emit_error(macro_line, -1, 'Macro %s already defined' % macro_name) opened_macro = opened_macro + 1 macro_names.append(macro_name) close_re = re.match(RE_CLOSE_MACRO, line) if close_re: if not opened_macro: self.emit_error(macro_line, -1, 'Macro not previously defined') else: opened_macro = opened_macro - 1 if opened_macro: self.emit_error(macro_line, -1, 'Macro %s never closed' % macro_names[-1]) # code if __name__ == '__main__': # Check parameters if len(sys.argv) != 2: sys.stderr.write("Usage\n\t%s source_file\n" % sys.argv[0]) quit(-1) source = sys.argv[1] if not os.path.exists(source): sys.stderr.write("%s does not exist\n" % source) quit(-1) Z80_Parser(source) # metadata __author__ = 'Krusty/Benediction' __copyright__ = 'Copyright 2012, Benediction' __credits__ = ['Krusty/Benediction'] __licence__ = 'GPL' __version__ = '0.1' __maintainer__ = 'Krusty/Benediction' __email__ = 'krusty@cpcscene.fr' __status__ = 'Prototype'