From e44c8023143caafdb7b402ec98767220f127d706 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 6 Mar 2018 12:23:57 +0000 Subject: [PATCH] Syntax errors have line numbers in them now. --- eg/rudiments/word-table.60p | 2 +- src/sixtypical/ast.py | 3 +- src/sixtypical/parser.py | 65 +++++++++++++++++++----------------- src/sixtypical/scanner.py | 22 +++++++++--- tests/SixtyPical Analysis.md | 2 +- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/eg/rudiments/word-table.60p b/eg/rudiments/word-table.60p index a0dc978..ef40dd8 100644 --- a/eg/rudiments/word-table.60p +++ b/eg/rudiments/word-table.60p @@ -1,5 +1,5 @@ word one -word table many +word table[256] many routine main inputs one, many diff --git a/src/sixtypical/ast.py b/src/sixtypical/ast.py index d4ab321..0ab9f33 100644 --- a/src/sixtypical/ast.py +++ b/src/sixtypical/ast.py @@ -5,7 +5,8 @@ class AST(object): child_attrs = () value_attrs = () - def __init__(self, **kwargs): + def __init__(self, line_number, **kwargs): + self.line_number = line_number self.attrs = {} for attr in self.children_attrs: self.attrs[attr] = kwargs.pop(attr, []) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 6b3ec84..4caf862 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -6,7 +6,7 @@ from sixtypical.model import ( RoutineType, VectorType, TableType, BufferType, PointerType, LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef, ) -from sixtypical.scanner import Scanner +from sixtypical.scanner import Scanner, SixtyPicalSyntaxError class SymEntry(object): @@ -30,6 +30,9 @@ class Parser(object): self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token)) self.backpatch_instrs = [] + def syntax_error(self, msg): + raise SixtyPicalSyntaxError(self.scanner.line_number, msg) + def soft_lookup(self, name): if name in self.current_statics: return self.current_statics[name].model @@ -40,7 +43,7 @@ class Parser(object): def lookup(self, name): model = self.soft_lookup(name) if model is None: - raise SyntaxError('Undefined symbol "%s"' % name) + self.syntax_error('Undefined symbol "{}"'.format(name)) return model # --- grammar productions @@ -56,7 +59,7 @@ class Parser(object): defn = self.defn() name = defn.name if name in self.symbols: - raise SyntaxError('Symbol "%s" already declared' % name) + self.syntax_error('Symbol "%s" already declared' % name) self.symbols[name] = SymEntry(defn, defn.location) defns.append(defn) while self.scanner.on('define', 'routine'): @@ -68,7 +71,7 @@ class Parser(object): routine = self.legacy_routine() name = routine.name if name in self.symbols: - raise SyntaxError('Symbol "%s" already declared' % name) + self.syntax_error('Symbol "%s" already declared' % name) self.symbols[name] = SymEntry(routine, routine.location) routines.append(routine) self.scanner.check_type('EOF') @@ -84,26 +87,26 @@ class Parser(object): if instr.opcode in ('call', 'goto'): name = instr.location if name not in self.symbols: - raise SyntaxError('Undefined routine "%s"' % name) + self.syntax_error('Undefined routine "%s"' % name) if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)): - raise SyntaxError('Illegal call of non-executable "%s"' % name) + self.syntax_error('Illegal call of non-executable "%s"' % name) instr.location = self.symbols[name].model if instr.opcode in ('copy',) and isinstance(instr.src, basestring): name = instr.src if name not in self.symbols: - raise SyntaxError('Undefined routine "%s"' % name) + self.syntax_error('Undefined routine "%s"' % name) if not isinstance(self.symbols[name].model.type, (RoutineType, VectorType)): - raise SyntaxError('Illegal copy of non-executable "%s"' % name) + self.syntax_error('Illegal copy of non-executable "%s"' % name) instr.src = self.symbols[name].model - return Program(defns=defns, routines=routines) + return Program(self.scanner.line_number, defns=defns, routines=routines) def typedef(self): self.scanner.expect('typedef') type_ = self.defn_type() name = self.defn_name() if name in self.typedefs: - raise SyntaxError('Type "%s" already declared' % name) + self.syntax_error('Type "%s" already declared' % name) self.typedefs[name] = type_ return type_ @@ -127,11 +130,11 @@ class Parser(object): self.scanner.scan() if initial is not None and addr is not None: - raise SyntaxError("Definition cannot have both initial value and explicit address") + self.syntax_error("Definition cannot have both initial value and explicit address") location = LocationRef(type_, name) - return Defn(name=name, addr=addr, initial=initial, location=location) + return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location) def defn_size(self): self.scanner.expect('[') @@ -147,7 +150,7 @@ class Parser(object): if self.scanner.consume('table'): size = self.defn_size() if size <= 0 or size > 256: - raise SyntaxError("Table size must be > 0 and <= 256") + self.syntax_error("Table size must be > 0 and <= 256") type_ = TableType(type_, size) return type_ @@ -167,7 +170,7 @@ class Parser(object): elif self.scanner.consume('vector'): type_ = self.defn_type_term() if not isinstance(type_, RoutineType): - raise SyntaxError("Vectors can only be of a routine, not %r" % type_) + self.syntax_error("Vectors can only be of a routine, not %r" % type_) type_ = VectorType(type_) elif self.scanner.consume('routine'): (inputs, outputs, trashes) = self.constraints() @@ -181,7 +184,7 @@ class Parser(object): type_name = self.scanner.token self.scanner.scan() if type_name not in self.typedefs: - raise SyntaxError("Undefined type '%s'" % type_name) + self.syntax_error("Undefined type '%s'" % type_name) type_ = self.typedefs[type_name] return type_ @@ -220,6 +223,7 @@ class Parser(object): addr = None location = LocationRef(type_, name) return Routine( + self.scanner.line_number, name=name, block=block, addr=addr, location=location ) @@ -227,7 +231,7 @@ class Parser(object): def routine(self, name): type_ = self.defn_type() if not isinstance(type_, RoutineType): - raise SyntaxError("Can only define a routine, not %r" % type_) + self.syntax_error("Can only define a routine, not %r" % type_) statics = [] if self.scanner.consume('@'): self.scanner.check_type('integer literal') @@ -244,6 +248,7 @@ class Parser(object): addr = None location = LocationRef(type_, name) return Routine( + self.scanner.line_number, name=name, block=block, addr=addr, location=location, statics=statics ) @@ -253,7 +258,7 @@ class Parser(object): for defn in statics: name = defn.name if name in self.symbols or name in self.current_statics: - raise SyntaxError('Symbol "%s" already declared' % name) + self.syntax_error('Symbol "%s" already declared' % name) c[name] = SymEntry(defn, defn.location) return c @@ -333,7 +338,7 @@ class Parser(object): while self.scanner.consume('static'): defn = self.defn() if defn.initial is None: - raise SyntaxError("Static definition {} must have initial value".format(defn)) + self.syntax_error("Static definition {} must have initial value".format(defn)) defns.append(defn) return defns @@ -343,7 +348,7 @@ class Parser(object): while not self.scanner.on('}'): instrs.append(self.instr()) self.scanner.expect('}') - return Block(instrs=instrs) + return Block(self.scanner.line_number, instrs=instrs) def instr(self): if self.scanner.consume('if'): @@ -355,7 +360,7 @@ class Parser(object): block2 = None if self.scanner.consume('else'): block2 = self.block() - return If(src=src, block1=block1, block2=block2, inverted=inverted) + return If(self.scanner.line_number, src=src, block1=block1, block2=block2, inverted=inverted) elif self.scanner.consume('repeat'): inverted = False src = None @@ -366,7 +371,7 @@ class Parser(object): src = self.locexpr() else: self.scanner.expect('forever') - return Repeat(src=src, block=block, inverted=inverted) + return Repeat(self.scanner.line_number, src=src, block=block, inverted=inverted) elif self.scanner.token in ("ld",): # the same as add, sub, cmp etc below, except supports an indlocexpr for the src opcode = self.scanner.token @@ -374,32 +379,32 @@ class Parser(object): dest = self.locexpr() self.scanner.expect(',') src = self.indlocexpr() - return SingleOp(opcode=opcode, dest=dest, src=src) + return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) elif self.scanner.token in ("add", "sub", "cmp", "and", "or", "xor"): opcode = self.scanner.token self.scanner.scan() dest = self.locexpr() self.scanner.expect(',') src = self.indexed_locexpr() - return SingleOp(opcode=opcode, dest=dest, src=src) + return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) elif self.scanner.token in ("st",): opcode = self.scanner.token self.scanner.scan() src = self.locexpr() self.scanner.expect(',') dest = self.indlocexpr() - return SingleOp(opcode=opcode, dest=dest, src=src) + return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) elif self.scanner.token in ("shl", "shr", "inc", "dec"): opcode = self.scanner.token self.scanner.scan() dest = self.locexpr() - return SingleOp(opcode=opcode, dest=dest, src=None) + return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None) elif self.scanner.token in ("call", "goto"): opcode = self.scanner.token self.scanner.scan() name = self.scanner.token self.scanner.scan() - instr = SingleOp(opcode=opcode, location=name, dest=None, src=None) + instr = SingleOp(self.scanner.line_number, opcode=opcode, location=name, dest=None, src=None) self.backpatch_instrs.append(instr) return instr elif self.scanner.token in ("copy",): @@ -408,16 +413,16 @@ class Parser(object): src = self.indlocexpr(forward=True) self.scanner.expect(',') dest = self.indlocexpr() - instr = SingleOp(opcode=opcode, dest=dest, src=src) + instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src) self.backpatch_instrs.append(instr) return instr elif self.scanner.consume("with"): self.scanner.expect("interrupts") self.scanner.expect("off") block = self.block() - return WithInterruptsOff(block=block) + return WithInterruptsOff(self.scanner.line_number, block=block) elif self.scanner.consume("trash"): dest = self.locexpr() - return SingleOp(opcode='trash', src=None, dest=dest) + return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest) else: - raise ValueError('bad opcode "%s"' % self.scanner.token) + self.syntax_error('bad opcode "%s"' % self.scanner.token) diff --git a/src/sixtypical/scanner.py b/src/sixtypical/scanner.py index fd48b3b..72bca62 100644 --- a/src/sixtypical/scanner.py +++ b/src/sixtypical/scanner.py @@ -3,11 +3,20 @@ import re +class SixtyPicalSyntaxError(ValueError): + def __init__(self, line_number, message): + super(SixtyPicalSyntaxError, self).__init__(line_number, message) + + def __str__(self): + return "Line {}: {}".format(self.args[0], self.args[1]) + + class Scanner(object): def __init__(self, text): self.text = text self.token = None self.type = None + self.line_number = 1 self.scan() def scan_pattern(self, pattern, type, token_group=1, rest_group=2): @@ -19,6 +28,7 @@ class Scanner(object): self.type = type self.token = match.group(token_group) self.text = match.group(rest_group) + self.line_number += self.token.count('\n') return True def scan(self): @@ -46,14 +56,15 @@ class Scanner(object): if self.scan_pattern(r'.', 'unknown character'): return else: - raise AssertionError("this should never happen, self.text=(%s)" % self.text) + raise AssertionError("this should never happen, self.text=({})".format(self.text)) def expect(self, token): if self.token == token: self.scan() else: - raise SyntaxError("Expected '%s', but found '%s'" % - (token, self.token)) + raise SixtyPicalSyntaxError(self.scanner.line_number, "Expected '{}', but found '{}'".format( + token, self.token + )) def on(self, *tokens): return self.token in tokens @@ -63,8 +74,9 @@ class Scanner(object): def check_type(self, type): if not self.type == type: - raise SyntaxError("Expected %s, but found %s ('%s')" % - (type, self.type, self.token)) + raise SixtyPicalSyntaxError(self.scanner.line_number, "Expected {}, but found '{}'".format( + self.type, self.token + )) def consume(self, token): if self.token == token: diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 4cd21c6..84111c8 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2323,7 +2323,7 @@ A vector in a vector table cannot be directly called. | copy bar, many + x | call many + x | } - ? ValueError + ? SyntaxError ### typedef ###