# HG changeset patch # User Teemu Piippo # Date 1558712230 -7200 # Node ID 66c9591b733d945f4ea93018b4c5eb92f2500ad6 # Parent e46fa477007b7611950e3a35e3b68e37fd9ec2fd added proper handling of syntax errors diff -r e46fa477007b -r 66c9591b733d LICENSE --- a/LICENSE Fri May 24 15:32:10 2019 +0300 +++ b/LICENSE Fri May 24 17:37:10 2019 +0200 @@ -1,4 +1,4 @@ -Copyright 2018 Santeri Piippo +Copyright 2019 Teemu Piippo Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff -r e46fa477007b -r 66c9591b733d geometry.py --- a/geometry.py Fri May 24 15:32:10 2019 +0300 +++ b/geometry.py Fri May 24 17:37:10 2019 +0200 @@ -67,24 +67,19 @@ def __matmul__(self, transformation_matrix): return transform(self, transformation_matrix) def __eq__(self, other): - return self.is_close(other, threshold = 1e-10) + return self.is_close(other, threshold = 1.0e-10) def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): return self.coordinates < other.coordinates def __hash__(self): return hash(self.coordinates) - def is_close(self, other, *, threshold): - return all( - abs(a - b) < threshold - for a, b in zip(self.coordinates, other.coordinates) - ) - -class VertexOp: - def __init__(self, callable): - self.callable = callable - def __rmul__(self, coordinate): - return self.callable(coordinate) + def is_close(self, other, *, threshold = 1.0e-05): + return self.distance_to(other) < threshold + #return all( + # abs(a - b) < threshold + # for a, b in zip(self.coordinates, other.coordinates) + #) class LineSegment: def __init__(self, v1, v2): diff -r e46fa477007b -r 66c9591b733d ldcheck.py --- a/ldcheck.py Fri May 24 15:32:10 2019 +0300 +++ b/ldcheck.py Fri May 24 17:37:10 2019 +0200 @@ -116,6 +116,8 @@ action = ListTestSuiteAction, help = 'Lists all possible checks and exit', ) + parser.add_argument('--dump-structure', action = 'store_true') + parser.add_argument('--rebuild', action = 'store_true') args = parser.parse_args() config = load_config('ldcheck.cfg') for ldconfig_ldr_path in find_ldconfig_ldr_paths(config): @@ -128,7 +130,14 @@ name = basename(args.filename), config = config, ) - from testsuite import load_tests, check_model, format_report - test_suite = load_tests() - report = check_model(model, test_suite) - print(format_report(report, model, test_suite)) + if args.dump_structure: + for entry in model.body: + print(entry) + elif args.rebuild: + for entry in model.body: + print(entry.textual_representation(), end = '\r\n') + else: + from testsuite import load_tests, check_model, format_report + test_suite = load_tests() + report = check_model(model, test_suite) + print(format_report(report, model, test_suite)) diff -r e46fa477007b -r 66c9591b733d linetypes.py --- a/linetypes.py Fri May 24 15:32:10 2019 +0300 +++ b/linetypes.py Fri May 24 17:37:10 2019 +0200 @@ -16,24 +16,27 @@ def typename(self): return 'empty line' +class MetaCommand: + def __init__(self, text, style = 'old'): + self.text = text + def __repr__(self): + return str.format('linetypes.MetaCommand({text!r})', + text = self.text, + ) + def textual_representation(self): + return ('0 ' + self.text).strip() + def typename(self): + return 'metacommand' + class Comment: def __init__(self, text, style = 'old'): - if style == 'old' and text.startswith('//'): - self.text = text[2:].strip() - self.style = 'new' - else: - self.text = text - self.style = style + self.text = text def __repr__(self): - return str.format('linetypes.Comment({text!r}, {style!r})', + return str.format('linetypes.Comment({text!r})', text = self.text, - style = self.style, ) def textual_representation(self): - if self.style == 'old': - return '0 ' + self.text - else: - return '0 // ' + self.text + return '0 //' + self.text def typename(self): return 'comment' @@ -113,3 +116,16 @@ return result def typename(self): return 'contour line segment' + +class Error: + def __init__(self, line, reason): + self.line = line + self.reason = reason + def __repr__(self): + return str.format( + 'linetypes.Error(line = {line!r}, reason = {reason!r})', + line = self.line, + reason = self.reason, + ) + def textual_representation(self): + return self.line diff -r e46fa477007b -r 66c9591b733d parse.py --- a/parse.py Fri May 24 15:32:10 2019 +0300 +++ b/parse.py Fri May 24 17:37:10 2019 +0200 @@ -7,27 +7,36 @@ pass def parse_ldraw_code(line): - if isinstance(line, bytes): - line = line.decode() - line = line.strip() - if not line: - return linetypes.EmptyLine() - elif line == '0': - return linetypes.Comment('') - elif line.startswith('0 '): - return linetypes.Comment(line[2:]) - elif line.startswith('1 '): - return parse_ldraw_subfile_reference(line) - elif line.startswith('2 '): - return parse_ldraw_line(line) - elif line.startswith('3 '): - return parse_ldraw_triangle(line) - elif line.startswith('4 '): - return parse_ldraw_quadrilateral(line) - elif line.startswith('5 '): - return parse_ldraw_conditional_line(line) + try: + if isinstance(line, bytes): + line = line.decode() + line = line.strip() + if not line: + return linetypes.EmptyLine() + elif line == '0': + return linetypes.MetaCommand('') + elif line.startswith('0 '): + return parse_ldraw_meta_line(line) + elif line.startswith('1 '): + return parse_ldraw_subfile_reference(line) + elif line.startswith('2 '): + return parse_ldraw_line(line) + elif line.startswith('3 '): + return parse_ldraw_triangle(line) + elif line.startswith('4 '): + return parse_ldraw_quadrilateral(line) + elif line.startswith('5 '): + return parse_ldraw_conditional_line(line) + else: + raise BadLdrawLine('unknown line type') + except BadLdrawLine as error: + return linetypes.Error(line, str(error)) + +def parse_ldraw_meta_line(line): + if line.startswith('0 //'): + return linetypes.Comment(line[4:]) else: - raise BadLdrawLine('unknown line type') + return linetypes.MetaCommand(line[2:]) def parse_ldraw_subfile_reference(line): pattern = r'^1\s+([^ ]+)' + r'\s+([^ ]+)' * (3 + 9 + 1) + r'\s*$' @@ -74,8 +83,12 @@ except ValueError: raise BadLdrawLine('bad numeric values') vertices.append(Vertex(*coordinates)) + try: + colour = int(match.group(1), 0) + except ValueError: + raise BadLdrawLine('invalid syntax for colour: ' + repr(match.group(1))) return { - 'colour': Colour(match.group(1)), + 'colour': Colour(colour), 'vertices': vertices, } diff -r e46fa477007b -r 66c9591b733d tests/misc.py --- a/tests/misc.py Fri May 24 15:32:10 2019 +0300 +++ b/tests/misc.py Fri May 24 17:37:10 2019 +0200 @@ -1,4 +1,5 @@ -from testsuite import warning +from testsuite import error, warning +import linetypes def colours_test(model): ''' Checks that all colours used in the part model are valid. ''' @@ -8,14 +9,26 @@ if hasattr(element, 'colour') and not element.colour.is_valid ) +def syntax_errors(model): + yield from ( + error(element, 'syntax-error', reason = element.reason) + for element in model.body + if isinstance(element, linetypes.Error) + ) + manifest = { 'tests': { 'colour-validity': colours_test, + 'syntax-errors': syntax_errors, }, 'messages': { 'bad-colour': lambda colour_index: str.format( 'invalid colour {}', colour_index, ), + 'syntax-error': lambda reason: str.format( + 'syntax error: {}', + reason, + ), }, }