Add port of Bison location tracking, rudimentary error message showing location
authorNick Downing <nick@ndcode.org>
Wed, 30 Jan 2019 12:34:35 +0000 (23:34 +1100)
committerNick Downing <nick@ndcode.org>
Wed, 30 Jan 2019 12:34:35 +0000 (23:34 +1100)
Makefile
parse-gram.y
scan-gram.l
skel_lex_yy.py
skel_y_tab.py [new file with mode: 0644]
state.py
tests_ast/cal_py.y

index d75a375..9b4bb74 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,8 +12,8 @@ lex_yy_code.py: scan-code.l skel_lex_yy.py
 t_def.py: piyacc.t
        bootstrap_pitree/pitree.py --python $<
 
-y_tab.py: parse-gram.y
-       bootstrap_piyacc/piyacc.py --element --python $<
+y_tab.py: parse-gram.y skel_y_tab.py
+       bootstrap_piyacc/piyacc.py --element --python --skel skel_y_tab.py $<
 
 clean:
        rm -f element.py lex_yy.py lex_yy_code.py t_def.py y_tab.py
index 27e9e4a..7b7a052 100644 (file)
   YYLTYPE = state.location
 }
 
+%code
+{
+  def YYLLOC_DEFAULT(loc, stack, n):
+    lloc_default(loc, stack, n)
+}
+
 /* Nick %define api.prefix {gram_} */
 %define api.pure full
 %define locations
@@ -653,19 +659,14 @@ epilogue.opt
 
 %%
 
-#def lloc_default(rhs, n):
-#  i = None
-#  loc = None
-#  loc.start = rhs[n].end
-#  loc.end = rhs[n].end
-#  i = 1
-#  while i <= n:
-#    if not equal_boundaries(rhs[i].start, rhs[i].end):
-#      loc.start = rhs[i].start
-#      break
-#    i += 1
-#  return loc
-#
+def lloc_default(loc, stack, n):
+  loc.start = stack[-1][2].end.copy()
+  loc.end = stack[-1][2].end.copy()
+  for i in range(-n, 0):
+    if not state.equal_boundaries(stack[i][2].start, stack[i][2].end):
+      loc.start = stack[i][2].start.copy()
+      break
+
 #def strip_braces(code):
 #  code[len(code) - 1] = 0
 #  return code + 1
index 89abf70..21aa4f5 100644 (file)
@@ -25,6 +25,9 @@
   import y_tab
 
   scanner_cursor = state.boundary()
+  def YY_USER_ACTION():
+    state.location_compute(y_tab.yylloc, scanner_cursor, yytext)
+
   gram_last_string = ''
   bracketed_id_str = None
   bracketed_id_loc = 0
index 688fc95..acfde43 100644 (file)
@@ -28,6 +28,24 @@ import bisect
 import element
 import sys
 
+# this can be redefined in SECTION1
+def YY_AT_BOL():
+  return yy_buffer_stack[-1].at_bol
+
+def yy_set_bol(at_bol):
+  yy_buffer_stack[-1].at_bol = at_bol
+
+def YY_USER_ACTION():
+  pass
+
+def YY_RULE_START():
+  # note that this should also be done after yyless() and REJECT(),
+  # and state should be saved in case they result in a null string,
+  # however, it doesn't seem to be in flex, maintain compatibility:
+  if len(yytext):
+    yy_set_bol(yytext[-1] == '\n')
+  YY_USER_ACTION()
+
 # GENERATE SECTION1
 
 # GENERATE STARTCONDDECL
@@ -118,11 +136,7 @@ def yy_rule_start():
   yytext = yy_group_text[:yy_group_stack[-1]]
   yytext_len = yy_group_stack[-1]
   del yy_group_stack[-2:]
-  # note that this should also be done after yyless() and REJECT(),
-  # and state should be saved in case they result in a null string,
-  # however, it doesn't seem to be in flex, maintain compatibility:
-  if len(yytext):
-    yy_buffer_stack[-1].at_bol = yytext[-1] == '\n'
+  YY_RULE_START()
   yy_element_stack.append([])
 
 def yy_group_end():
@@ -157,12 +171,6 @@ def yy_pop_state():
   global yystart
   yystart = yystart_stack.pop()
 
-def YY_AT_BOL():
-  return yy_buffer_stack[-1].at_bol
-
-def yy_set_bol(at_bol):
-  yy_buffer_stack[-1].at_bol = at_bol
-
 # GENERATE SECTION2
 
 def yylex(_class = element.Element, *args, **kwargs):
diff --git a/skel_y_tab.py b/skel_y_tab.py
new file mode 100644 (file)
index 0000000..25d1b3f
--- /dev/null
@@ -0,0 +1,154 @@
+# Copyright (C) 2019 Nick Downing <nick@ndcode.org>
+# SPDX-License-Identifier: GPL-2.0-with-bison-exception
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; version 2.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51
+# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+#
+# As a special exception, you may create a larger work that contains part or
+# all of the piyacc parser skeleton and distribute that work under terms of
+# your choice, so long as that work isn't itself a parser generator using the
+# skeleton or a modified version thereof as a parser skeleton. Alternatively,
+# if you modify or redistribute the parser skeleton itself, you may (at your
+# option) remove this special exception, which will cause the skeleton and the
+# resulting piyacc output files to be licensed under the GNU General Public
+# License without this special exception.
+
+import bisect
+import element
+import lex_yy
+#import xml.etree.ElementTree
+
+# this can be redefined in SECTION1
+class YYLTYPE:
+  def __init__(
+    self,
+    first_line = 0,
+    first_column = 0,
+    last_line = 0,
+    last_column = 0
+  ):
+    self.first_line = first_line
+    self.first_column = first_column
+    self.last_line = last_line
+    self.last_column = last_column
+  def copy(self):
+    return YYLTYPE(
+       self.first_line,
+       self.first_column,
+       self.last_line,
+       self.last_column
+    )
+
+def YYLLOC_DEFAULT(current, stack, n):
+  if n:
+    current.first_line = stack[-n][2].first_line
+    current.first_column = stack[-n][2].first_column
+    current.last_line = stack[-1][2].last_line
+    current.last_column = stack[-1][2].last_column
+  else:
+    current.first_line = current.last_line = stack[-1][2].last_line
+    current.first_column = current.last_column = stack[-1][2].last_column
+
+# GENERATE SECTION1
+
+# GENERATE TOKENS
+
+yystack = None
+yychar = None
+YYEMPTY = -1
+
+yyval = None
+yyloc = YYLTYPE()
+
+yylval = None
+yylloc = YYLTYPE()
+
+yy_element_stack = None
+
+# GENERATE SECTION2
+
+def yyparse(factory, *args, **kwargs):
+  global yystack, yychar, yyval, yyloc, yylval, yylloc, yy_element_stack
+
+  # GENERATE INITIALACTION
+
+  state = 0
+  yystack = [(-1, yylval, yylloc.copy())] # kludge for bison compatibility
+  yychar = -1
+  yy_element_stack = []
+  while True:
+    #print('state', state, 'yystack', yystack)
+    reduce = yy_lr1dfa_states[state][4]
+    if reduce == -1:
+      if yychar == -1:
+        yychar = lex_yy.yylex()
+        #print('yychar', yychar, 'yylval', yylval, 'yylloc', yylloc, 'lex_yy.yytext', lex_yy.yytext)
+        #print('lex_yy.yy_element_space')
+        #xml.etree.ElementTree.dump(lex_yy.yy_element_space)
+        #print('lex_yy.yy_element_token')
+        #xml.etree.ElementTree.dump(lex_yy.yy_element_token)
+      action = yy_lr1dfa_states[state][1][
+        bisect.bisect_right(yy_lr1dfa_states[state][0], yychar)
+      ]
+      if action == -1:
+        raise Exception('syntax error')
+      if (action & 1) == 0:
+        yystack.append((state, yylval, yylloc.copy()))
+
+        # push space then AST element contiguously onto yy_element_stack
+        # even numbered elements are spaces, odd numbered elements are AST
+        yy_element_stack.extend(
+          [lex_yy.yy_element_space, lex_yy.yy_element_token]
+        )
+
+        state = action >> 1
+        #print('shift', state)
+        yychar = -1
+        continue
+      reduce = action >> 1
+    #print('reduce', reduce)
+    len_symbols, ref_data = yy_lr1dfa_productions[reduce]
+    # GENERATE YYLLOC_DEFAULT BEGIN
+    YYLLOC_DEFAULT(yyloc, yystack, len_symbols)
+    # GENERATE END
+    base = len(yystack) - len_symbols
+    yystack.append((state, None, None)) # only has effect if len_symbols == 0
+    state, yyval, _ = yystack[base]
+    ref_data()
+    del yystack[base:]
+    if reduce == 0:
+      assert base == 1
+      break
+    yystack.append((state, yyval, yyloc.copy()))
+
+    # action creates empty space in yy_element_stack[base * 2] if needed
+    assert len(yy_element_stack) >= base * 2 - 1
+
+    # concatenate yy_element_stack[base * 2 - 1:] to a single AST element
+    yy_element_stack[base * 2 - 1:] = [
+      element.concatenate(
+        yy_element_stack[base * 2 - 1:],
+        element.Element
+      )
+    ]
+
+    state = yy_lr1dfa_states[state][3][
+      bisect.bisect_right(yy_lr1dfa_states[state][2], reduce)
+    ]
+    assert state != -1
+
+  # return space then AST then space in the user's choice of element type
+  yy_element_stack.append(lex_yy.yy_element_space)
+  return element.concatenate(yy_element_stack, factory, *args, **kwargs)
+
+# GENERATE SECTION3
index c03f18c..e395abb 100644 (file)
--- a/state.py
+++ b/state.py
@@ -18,13 +18,16 @@ import sys
 
 # miscellaneous state accessed by scan-gram.l and parse-gram.y
 class boundary:
-  def __init__(self, file = '<stdin>', line = 0, column = 0):
+  def __init__(self, file = '<stdin>', line = 1, column = 1):
     self.file = file
     self.line = line
     self.column = column
   def copy(self):
     return boundary(self.file, self.line, self.column)
 
+def equal_boundaries(a, b):
+  return a.column == b.column and a.line == b.line and a.file == b.file
+
 class location:
   def __init__(self, start = None, end = None):
     self.start = boundary() if start is None else start
@@ -77,7 +80,17 @@ def complain(loc, flags, message):
   #
   #if (flags & fatal)
   #  exit (EXIT_FAILURE);
-  print(message)
+  sys.stderr.write(
+    '{0:s}({1:d},{2:d})..{3:s}({4:d},{5:d}): {6:s}\n'.format(
+      loc.start.file,
+      loc.start.line,
+      loc.start.column,
+      loc.end.file,
+      loc.end.line,
+      loc.end.column,
+      message
+    )
+  )
   sys.exit(1)
 
 undef_assoc = 0
@@ -91,3 +104,24 @@ printer = 1
 
 def quote(str):
   return '"{0:s}"'.format(str.replace('\\', '\\\\').replace('"', '\\"'))
+
+def location_compute(loc, cur, token):
+  line = cur.line;
+  column = cur.column;
+  p0 = 0
+  p = 0
+  loc.start = cur.copy()
+  while p < len(token):
+    if token[p] == '\n':
+      line += 1
+      column = 1
+      p0 = p + 1
+    elif token[p] == '\t':
+      column += p - p0
+      column += 8 - ((column - 1) & 7)
+      p0 = p + 1
+    p += 1
+  column += p - p0
+  cur.line = line
+  cur.column = column
+  loc.end = cur.copy()
index 4ec2399..c5fbe45 100644 (file)
@@ -23,6 +23,7 @@ import sys
 
 %locations
 
+%blah
 %token NUM
 
 %left '+' '-'