Эх сурвалжийг харах

Implemented expression preprocessor and 'graph' statement.

Sander Mathijs van Veen 14 жил өмнө
parent
commit
bb8c1c916c
3 өөрчлөгдсөн 117 нэмэгдсэн , 42 устгасан
  1. 1 1
      external/graph_drawing
  2. 1 1
      external/pybison
  3. 115 40
      src/parser.py

+ 1 - 1
external/graph_drawing

@@ -1 +1 @@
-Subproject commit 4d65a52b8ba0f0ad1843cb5535a485ff372317a3
+Subproject commit 003a9e2536ed7d899b570f07d7d6a0ce60c5f155

+ 1 - 1
external/pybison

@@ -1 +1 @@
-Subproject commit 2ae8c15dba13faba38e1a3d1a711cf6d24395a5a
+Subproject commit eb1d1da4c21cc3f48cabe19485381e3f7e80f279

+ 115 - 40
src/parser.py

@@ -39,8 +39,8 @@ class Parser(BisonParser):
     # of tokens of the lex script.
     tokens = ['NUMBER', 'IDENTIFIER',
               'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'POW',
-              'LPAREN', 'RPAREN', 'COMMA', 'CONCAT_POW',
-              'NEWLINE', 'QUIT', 'RAISE']
+              'LPAREN', 'RPAREN', 'COMMA',  #'CONCAT_POW',
+              'NEWLINE', 'QUIT', 'RAISE', 'GRAPH']
 
     # ------------------------------
     # precedences
@@ -71,6 +71,64 @@ class Parser(BisonParser):
         except EOFError:
             return ''
 
+    def hook_read(self, data):
+        """
+        This hook will be called when the read() method returned. The data
+        argument points to the data read by the read() method. This hook
+        function should return the data to be used by the parser.
+        """
+        import re
+
+        # TODO: remove this quick preprocesing hack. This hack enabled
+        # concatenated expressions, since the grammar currently does not
+        # support those. This workaround will replace:
+        #   - ")(" with ")*(".
+        #   - "a(" with "a*(".
+        #   - ")a" with ")*a".
+        #   - "ab" with "a*b".
+        #   - "4a" with "4*a".
+        #   - "a4" with "a^4".
+
+        pattern = ('(?:(\))\s*(\()'       # match: )(
+                + '|([a-z0-9])\s*(\()'    # match: a(
+                + '|(\))\s*([a-z0-9])'    # match: )a
+                + '|([a-z])\s*([a-z]+)'   # match: ab
+                + '|([0-9])\s*([a-z])'    # match: 4a
+                + '|([a-z])\s*([0-9]))')  # match: a4
+
+        def preprocess_data(match):
+            left, right = filter(None, match.groups())
+
+            # Filter words (otherwise they will be preprocessed as well)
+            if left + right in ['graph', 'raise']:
+                return left + right
+
+            # If all characters on the right are numbers. e.g. "a4", the
+            # expression implies exponentiation. Make sure ")4" is not
+            # converted into an exponentiation, because that's multipliciation.
+            if left != ')' \
+                    and all(map(lambda x: 48 <= ord(x) < 58, list(right))):
+                return '%s^%s' % (left, right)
+
+            # match: ab | abc | abcd (where left = "a")
+            return '*'.join([left] + list(right))
+
+        # Iteratively replace all matches.
+        while True:
+            data_after = re.sub(pattern, preprocess_data, data)
+
+            if data == data_after:
+                break
+
+            if self.verbose:
+                print 'hook_read() modified the input data:'
+                print 'before:', data.replace('\n', '\\n')
+                print 'after :', data_after.replace('\n', '\\n')
+
+            data = data_after
+
+        return data
+
     # ---------------------------------------------------------------
     # These methods are the python handlers for the bison targets.
     # (which get called by the bison code each time the corresponding
@@ -103,14 +161,27 @@ class Parser(BisonParser):
         """
         line : NEWLINE
              | exp NEWLINE
+             | debug NEWLINE
              | RAISE NEWLINE
         """
-        if option == 1:
+        if option in [1, 2]:
             return values[0]
 
-        if option == 2:
+        if option == 3:
             raise RuntimeError('on_line: exception raised')
 
+    def on_debug(self, target, option, names, values):
+        """
+        debug : GRAPH exp
+        """
+
+        if option == 0:
+            print generate_graph(values[1])
+            return values[1]
+
+        raise BisonSyntaxError('Unsupported option %d in target "%s".'
+                               % (option, target))  # pragma: nocover
+
     def on_exp(self, target, option, names, values):
         """
         exp : NUMBER
@@ -118,8 +189,8 @@ class Parser(BisonParser):
             | LPAREN exp RPAREN
             | unary
             | binary
-            | concat
         """
+        #    | concat
 
         if option == 0:  # rule: NUMBER
             # TODO: A bit hacky, this achieves long integers and floats.
@@ -132,9 +203,12 @@ class Parser(BisonParser):
         if option == 2:  # rule: LPAREN exp RPAREN
             return values[1]
 
-        if option in [3, 4, 5]:  # rule: unary | binary | concat
+        if option in [3, 4]:  # rule: unary | binary
             return values[0]
 
+        #if option in [3, 4, 5]:  # rule: unary | binary | concat
+        #    return values[0]
+
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
                                % (option, target))  # pragma: nocover
 
@@ -179,39 +253,39 @@ class Parser(BisonParser):
         raise BisonSyntaxError('Unsupported option %d in target "%s".'
                                % (option, target))  # pragma: nocover
 
-    def on_concat(self, option, target, names, values):
-        """
-        concat : exp IDENTIFIER
-               | exp NUMBER
-               | exp LPAREN exp RPAREN
-               | exp CONCAT_POW
-               | CONCAT_POW
-        """
-
-        if option in [0, 1]:  # rule: exp IDENTIFIER | exp NUMBER
-            # example: xy -> x*y
-            # example: (x)4 -> x*4
-            val = int(values[1]) if option == 1 else values[1]
-            return Node('*', *(combine('*', values[0]) + [Leaf(val)]))
-
-        if option == 2:  # rule: exp LPAREN exp RPAREN
-            # example: x(y) -> x*(y)
-            return Node('*', *(combine('*', values[0])
-                              + combine('*', values[2])))
-
-        if option == 3:
-            # example: xy4 -> x*y^4
-            identifier, exponent = list(values[1])
-            node = Node('^', Leaf(identifier), Leaf(int(exponent)))
-            return Node('*', values[0], node)
-
-        if option == 4:
-            # example: x4 -> x^4
-            identifier, exponent = list(values[0])
-            return Node('^', Leaf(identifier), Leaf(int(exponent)))
-
-        raise BisonSyntaxError('Unsupported option %d in target "%s".'
-                               % (option, target))  # pragma: nocover
+    #def on_concat(self, option, target, names, values):
+    #    """
+    #    concat : exp IDENTIFIER %prec TIMES
+    #           | exp NUMBER %prec TIMES
+    #           | exp LPAREN exp RPAREN %prec TIMES
+    #           | exp CONCAT_POW %prec TIMES
+    #           | CONCAT_POW
+    #    """
+
+    #    if option in [0, 1]:  # rule: exp IDENTIFIER | exp NUMBER
+    #        # example: xy -> x*y
+    #        # example: (x)4 -> x*4
+    #        val = int(values[1]) if option == 1 else values[1]
+    #        return Node('*', *(combine('*', values[0]) + [Leaf(val)]))
+
+    #    if option == 2:  # rule: exp LPAREN exp RPAREN
+    #        # example: x(y) -> x*(y)
+    #        return Node('*', *(combine('*', values[0])
+    #                          + combine('*', values[2])))
+
+    #    if option == 3:
+    #        # example: xy4 -> x*y^4
+    #        identifier, exponent = list(values[1])
+    #        node = Node('^', Leaf(identifier), Leaf(int(exponent)))
+    #        return Node('*', values[0], node)
+
+    #    if option == 4:
+    #        # example: x4 -> x^4
+    #        identifier, exponent = list(values[0])
+    #        return Node('^', Leaf(identifier), Leaf(int(exponent)))
+
+    #    raise BisonSyntaxError('Unsupported option %d in target "%s".'
+    #                           % (option, target))  # pragma: nocover
 
     # -----------------------------------------
     # raw lex script, verbatim here
@@ -230,11 +304,11 @@ class Parser(BisonParser):
     #define YY_INPUT(buf,result,max_size) { \
         (*py_input)(py_parser, buf, &result, max_size); \
     }
+    /*[a-zA-Z][0-9]+ { returntoken(CONCAT_POW); }*/
     %}
 
     %%
 
-    [a-zA-Z][0-9]+ { returntoken(CONCAT_POW); }
     [0-9]+    { returntoken(NUMBER); }
     [a-zA-Z]  { returntoken(IDENTIFIER); }
     "("       { returntoken(LPAREN); }
@@ -247,6 +321,7 @@ class Parser(BisonParser):
     ","       { returntoken(COMMA); }
     "quit"    { printf("lex: got QUIT\n"); yyterminate(); returntoken(QUIT); }
     "raise"   { returntoken(RAISE); }
+    "graph"   { returntoken(GRAPH); }
 
     [ \t\v\f] {}
     [\n]      {yylineno++; returntoken(NEWLINE); }