added proper handling of syntax errors

Fri, 24 May 2019 17:37:10 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Fri, 24 May 2019 17:37:10 +0200
changeset 38
66c9591b733d
parent 37
e46fa477007b
child 39
abc83875167e

added proper handling of syntax errors

LICENSE file | annotate | diff | comparison | revisions
geometry.py file | annotate | diff | comparison | revisions
ldcheck.py file | annotate | diff | comparison | revisions
linetypes.py file | annotate | diff | comparison | revisions
parse.py file | annotate | diff | comparison | revisions
tests/misc.py file | annotate | diff | comparison | revisions
--- 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:
 
--- 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):
--- 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))
--- 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
--- 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,
     }
 
--- 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,
+        ),
     },
 }

mercurial