Extend calculator example to build an AST and re-calculate expressions from it
authorNick Downing <downing.nick@gmail.com>
Thu, 13 Sep 2018 11:15:19 +0000 (21:15 +1000)
committerNick Downing <downing.nick@gmail.com>
Thu, 13 Sep 2018 11:15:19 +0000 (21:15 +1000)
element.py
skel/skel_py_element.py
tests_ast/ast.py [new file with mode: 0644]
tests_ast/ast.sh [new file with mode: 0755]
tests_ast/cal.py
tests_ast/cal_py.l
tests_ast/cal_py.y
tests_ast/element.py

index 1f8e84f..48e18da 100644 (file)
@@ -144,14 +144,10 @@ def set_text(root, i, text):
   else:
     root[i - 1].tail = text
 
-def to_end_relative(root, pos, off):
-  assert pos >= 0 and off >= 0
-  off -= len(get_text(root, pos))
-  pos -= len(root) + 1
-  return pos, off
-
-def to_start_relative(root, pos, off):
-  assert pos < 0 and off <= 0
-  pos += len(root) + 1
-  off += len(get_text(root, pos))
-  return pos, off
+def concatenate(children, factory = Element, *args, **kwargs):
+  root = factory(*args, **kwargs)
+  for child in children:
+    i = len(root)
+    set_text(root, i, get_text(root, i) + get_text(child, 0))
+    root[i:] = child[:]
+  return root
index e9da944..0950581 100644 (file)
@@ -1,5 +1,6 @@
 import bisect
 import element
+import xml.etree.ElementTree
 
 # GENERATE SECTION1
 
@@ -20,7 +21,7 @@ yy_element_token = None
 
 # GENERATE SECTION2
 
-def yyparse():
+def yyparse(factory, *args, **kwargs):
   global yystack, yytoken, yyval, yyloc, yylval, yylloc
   global yy_element_stack, yy_element_space, yy_element_token
 
@@ -33,6 +34,7 @@ def yyparse():
   yy_element_stack = []
   while True:
     #print('state', state, 'yystack', yystack)
+    assert len(yy_element_stack) == len(yystack) * 2
     reduce = yy_lr1dfa_states[state][4]
     if reduce == -1:
       if yytoken == -1:
@@ -71,21 +73,36 @@ def yyparse():
     yystack.append((state, yyval, yyloc))
 
     # concatenate yy_element_stack[base * 2:] to space then AST element
-    j = base * 2 + 1
-    for i in range(j + 1, len(yy_element_stack)):
-      k = len(yy_element_stack[j])
-      element.set_text(
-        yy_element_stack[j],
-        k,
-        element.get_text(yy_element_stack[j], k) +
-        element.get_text(yy_element_stack[i], 0)
-      )
-      yy_element_stack[j][k:] = yy_element_stack[i][:]
-    del yy_element_stack[j + 1:]
+    i = base * 2
+    #print('i', i, 'len(yy_element_stack)', len(yy_element_stack))
+    if i >= len(yy_element_stack):
+      yy_element_stack.append(element.Element('root'))
+      yy_element_stack.append(element.Element('root'))
+    else:
+      #print('yy_element_stack[i]')
+      #xml.etree.ElementTree.dump(yy_element_stack[i])
+      #print('yy_element_stack[i + 1]')
+      #xml.etree.ElementTree.dump(yy_element_stack[i + 1])
+      for j in range(i + 2, len(yy_element_stack)):
+        #print('j', j)
+        #print('yy_element_stack[j]')
+        #xml.etree.ElementTree.dump(yy_element_stack[j])
+        k = len(yy_element_stack[i + 1])
+        element.set_text(
+          yy_element_stack[i + 1],
+          k,
+          element.get_text(yy_element_stack[i + 1], k) +
+          element.get_text(yy_element_stack[j], 0)
+        )
+        yy_element_stack[i + 1][k:] = yy_element_stack[j][:]
+      del yy_element_stack[i + 2:]
 
     state = yy_lr1dfa_states[state][3][
       bisect.bisect_right(yy_lr1dfa_states[state][2], reduce)
     ]
     assert state != -1
 
+  # return the space then the AST in the user's choice of element type
+  return element.concatenate(yy_element_stack, factory, *args, **kwargs)
+
 # GENERATE SECTION3
diff --git a/tests_ast/ast.py b/tests_ast/ast.py
new file mode 100644 (file)
index 0000000..ca8c6f0
--- /dev/null
@@ -0,0 +1,254 @@
+import element
+
+class AST(element.Element):
+  class Expr(element.Element):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Expr',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      element.Element.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = element.Element.copy(
+        self,
+        Expr if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Expr({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      raise NotImplementedException()
+
+  class Num(Expr):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Num',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      AST.Expr.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = AST.Expr.copy(
+        self,
+        Num if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Num({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      return float(element.get_text(self, 0))
+
+  class Add(element.Element):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Add',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      element.Element.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = element.Element.copy(
+        self,
+        Add if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Add({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      return self[0].eval() + self[1].eval()
+
+  class Sub(element.Element):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Sub',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      element.Element.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = element.Element.copy(
+        self,
+        Sub if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Sub({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      return self[0].eval() - self[1].eval()
+
+  class Mul(element.Element):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Mul',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      element.Element.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = element.Element.copy(
+        self,
+        Mul if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Mul({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      return self[0].eval() * self[1].eval()
+
+  class Div(element.Element):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Div',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      element.Element.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = element.Element.copy(
+        self,
+        Div if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Div({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      return self[0].eval() / self[1].eval()
+
+  class Neg(element.Element):
+    # GENERATE ELEMENT() BEGIN
+    def __init__(
+      self,
+      tag = 'AST_Neg',
+      attrib = {},
+      text = '',
+      children = []
+    ):
+      element.Element.__init__(
+        self,
+        tag,
+        attrib,
+        text,
+        children
+      )
+    def copy(self, factory = None):
+      result = element.Element.copy(
+        self,
+        Neg if factory is None else factory
+      )
+      return result
+    def __repr__(self):
+      params = []
+      self.repr_serialize(params)
+      return 'ast.AST.Neg({0:s})'.format(', '.join(params))
+    # GENERATE END
+    def eval(self):
+      return -self[0].eval()
+
+  # GENERATE ELEMENT() BEGIN
+  def __init__(
+    self,
+    tag = 'AST',
+    attrib = {},
+    text = '',
+    children = []
+  ):
+    element.Element.__init__(
+      self,
+      tag,
+      attrib,
+      text,
+      children
+    )
+  def copy(self, factory = None):
+    result = element.Element.copy(
+      self,
+      AST if factory is None else factory
+    )
+    return result
+  def __repr__(self):
+    params = []
+    self.repr_serialize(params)
+    return 'ast.AST({0:s})'.format(', '.join(params))
+  # GENERATE END
+
+# GENERATE FACTORY(element.Element) BEGIN
+tag_to_class = {
+  'AST': AST,
+  'AST_Expr': AST.Expr,
+  'AST_Num': AST.Num,
+  'AST_Add': AST.Add,
+  'AST_Sub': AST.Sub,
+  'AST_Mul': AST.Mul,
+  'AST_Div': AST.Div,
+  'AST_Neg': AST.Neg
+}
+def factory(tag, attrib = {}, *args, **kwargs):
+  return tag_to_class.get(tag, element.Element)(tag, attrib, *args, **kwargs)
+# GENERATE END
diff --git a/tests_ast/ast.sh b/tests_ast/ast.sh
new file mode 100755 (executable)
index 0000000..45b2e1c
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+if ../generate_ast.py ast <ast.py >ast.py.new && ! diff -q ast.py ast.py.new
+then
+  mv ast.py.new ast.py
+else
+  rm -f ast.py.new
+fi
index fd1a7b9..80bd04f 100755 (executable)
@@ -1,15 +1,16 @@
 #!/usr/bin/env python3
 
+import ast
 import element
 import sys
+import xml.etree.ElementTree
 import y_tab
 
 sys.stdout.write('Enter the expression: ')
 sys.stdout.flush()
-y_tab.yyparse()
-sys.stdout.write(
-  'space \'{0:s}\'\nAST \'{1:s}\'\n'.format(
-    element.get_text(y_tab.yy_element_stack[0], 0),
-    element.get_text(y_tab.yy_element_stack[1], 0)
-  )
-)
+_ast = y_tab.yyparse(ast.AST)
+xml.etree.ElementTree.dump(_ast)
+element.serialize(_ast, 'a.xml', 'utf-8')
+_ast = element.deserialize('a.xml', ast.factory, 'utf-8')
+for i in _ast:
+  sys.stdout.write('{0:g}\n'.format(i.eval()))
index 272fba6..5e6acea 100644 (file)
@@ -1,4 +1,6 @@
 %{
+import ast
+import element
 import y_tab
 %}
 
@@ -8,6 +10,12 @@ DIGIT [0-9]+\.?|[0-9]*\.[0-9]+
 
 [ ]
 {DIGIT}        {
+  y_tab.yy_element_token = element.Element(
+    'root',
+    children = [
+      element.concatenate([y_tab.yy_element_token], ast.AST.Num)
+    ]
+  )
   y_tab.yylval = float(yytext)
   return y_tab.NUM
 }
index 4fea55a..d17f562 100644 (file)
@@ -1,5 +1,7 @@
 %{
+import ast
 from lex_yy import yylex
+import xml.etree.ElementTree
 import sys
 %}
 %token NUM
@@ -11,8 +13,8 @@ import sys
 %%
 
 S : S E '\n' {
-  sys.stdout.write('Answer: {0:g}\nEnter:\n'.format($2))
-}
+    sys.stdout.write('Answer: {0:g}\nEnter:\n'.format($2))
+  }
   | S '\n'
   |
   | error '\n' {
@@ -20,13 +22,64 @@ S : S E '\n' {
   yyerrok()
 }
   ;
-E : E '+' E { $$ = $1 + $3 }
-  | E '-' E { $$ = $1 - $3 }
-  | E '*' E { $$ = $1 * $3 }
-  | E '/' E { $$ = $1 / $3 }
-  | '(' E ')' { $$ = $2 }
-  /*| '-' E %prec UMINUS { $$ = -$2 }*/
-  | '-' { print('unary minus', $1) } E %prec UMINUS { $$ = -$3 }
+E : E '+' E {
+    yy_element_stack[-5:] = [
+      element.Element(
+        'root',
+        children = [
+          element.concatenate(yy_element_stack[-5:], ast.AST.Add)
+        ]
+      )
+    ]
+    $$ = $1 + $3
+  }
+  | E '-' E {
+    yy_element_stack[-5:] = [
+      element.Element(
+        'root',
+        children = [
+          element.concatenate(yy_element_stack[-5:], ast.AST.Sub)
+        ]
+      )
+    ]
+    $$ = $1 - $3
+  }
+  | E '*' E {
+    yy_element_stack[-5:] = [
+      element.Element(
+        'root',
+        children = [
+          element.concatenate(yy_element_stack[-5:], ast.AST.Mul)
+        ]
+      )
+    ]
+    $$ = $1 * $3
+  }
+  | E '/' E {
+    yy_element_stack[-5:] = [
+      element.Element(
+        'root',
+        children = [
+          element.concatenate(yy_element_stack[-5:], ast.AST.Div)
+        ]
+      )
+    ]
+    $$ = $1 / $3
+  }
+  | '(' E ')' {
+    $$ = $2
+  }
+  | '-' E %prec UMINUS {
+    $$ = -$2
+    yy_element_stack[-3:] = [
+      element.Element(
+        'root',
+        children = [
+          element.concatenate(yy_element_stack[-3:], ast.AST.Neg)
+        ]
+      )
+    ]
+  }
   | NUM
   ;
 %%
index 1f8e84f..48e18da 100644 (file)
@@ -144,14 +144,10 @@ def set_text(root, i, text):
   else:
     root[i - 1].tail = text
 
-def to_end_relative(root, pos, off):
-  assert pos >= 0 and off >= 0
-  off -= len(get_text(root, pos))
-  pos -= len(root) + 1
-  return pos, off
-
-def to_start_relative(root, pos, off):
-  assert pos < 0 and off <= 0
-  pos += len(root) + 1
-  off += len(get_text(root, pos))
-  return pos, off
+def concatenate(children, factory = Element, *args, **kwargs):
+  root = factory(*args, **kwargs)
+  for child in children:
+    i = len(root)
+    set_text(root, i, get_text(root, i) + get_text(child, 0))
+    root[i:] = child[:]
+  return root