Version 1.0
Copyright © 2022 Lowell D. Thomas
Python APG
 … an ABNF Parser Generator
ast.py
Go to the documentation of this file.
1 ''' @file apg_py/lib/ast.py
2 @brief A class for creating and translating the Abstract Syntax Tree (AST).'''
3 
4 import copy
5 from apg_py.lib import identifiers as id
6 
7 
8 def inner_add_callback(nodes, name, callback):
9  '''Set a callback function for a named node.
10  Records will only be kept for nodes that have a
11  callback assigned to them. Called by both the original AST
12  and the shallow copy of the AST passed to the pattern-matching results.
13  @param nodes The list of AST nodes.
14  @param name the rule or UDT name of the node
15  @param callback the callback function for this node'''
16  lower = name.lower()
17  if(lower in nodes):
18  nodes[lower] = callback
19  else:
20  raise Exception('add_callback name not recognized', name)
21 
22 
23 def inner_translate(input, nodes, records, data=None):
24  '''Traverse the AST and call the user's callback functions for
25  translation of the saved AST node phrases.
26  Called by both the original AST
27  and the shallow copy of the AST passed to the pattern-matching results.
28  @param input The input string to the parser as a tuple of integers.
29  @param nodes The list of AST nodes.
30  @param records The list of AST node records.
31  @param data arbitary user data to be passed to the callback functions
32  '''
33  i = 0
34  while(i < len(records)):
35  record = records[i]
36  callback = nodes[record['name']]
37  if(record['state'] == id.SEM_PRE):
38  # Note that the callback functions could be modified or
39  # removed (set to None)
40  # between the parsing of the input string and the translation
41  # of the AST. Therefore, there may be retained records for
42  # nodes that have no callback function assigned to them.
43  if(callback):
44  ret = callback(
45  id.SEM_PRE,
46  input,
47  record['phrase_index'],
48  record['phrase_length'],
49  data)
50  if(ret == id.SEM_SKIP):
51  callback(
52  id.SEM_POST,
53  input,
54  record['phrase_index'],
55  record['phrase_length'],
56  data)
57  i = record['that_record']
58  else:
59  if(callback):
60  callback(
61  id.SEM_POST,
62  input,
63  record['phrase_index'],
64  record['phrase_length'],
65  data)
66  i += 1
67 
68 
69 class Ast():
70  '''A class for capturing the AST as the parser traverses the parse tree.'''
71 
72  def __init__(self, parser):
73  '''Class constructor.
74  @param parser the parser object to attach this AST object to'''
75  self.parserparser = parser
76  parser.ast = self
77  self.inputinput = [] # will be set by the parser
78  self.indexStackindexStack = []
79  self.recordsrecords = []
80  self.nodesnodes = {}
81  for rule in parser.rules:
82  self.nodesnodes[rule['lower']] = None
83  for udt in parser.udts:
84  self.nodesnodes[udt['lower']] = None
85 
86  def copy(self):
87  '''Make a copy suitable for adding callback functions
88  and doing translations.
89  This copy will not interact with the parser.
90  The primary purpose of this is so the class ApgExp (@ref exp.py)
91  can internally
92  do an AST translation of the parsed results and also return
93  a copy of the AST for the user to do a separate, independent
94  translation of the pattern-matched results.
95  '''
96  class ast_copy():
97  def __init__(cls, ast_to_copy):
98  cls.inputinput = copy.copy(ast_to_copy.input)
99  cls.recordsrecords = copy.deepcopy(ast_to_copy.records)
100  cls.nodesnodes = copy.copy(ast_to_copy.nodes)
101 
102  def add_callback(cls, name, callback):
103  inner_add_callback(cls.nodesnodes, name, callback)
104 
105  def translate(cls, data=None):
106  inner_translate(cls.inputinput, cls.nodesnodes, cls.recordsrecords, data)
107  return ast_copy(self)
108 
109  def add_callback(self, name, callback):
110  '''Add a callback function to the named AST node.
111  @param name The name of the node to add the callback to.
112  @param callback The callback function to add to the node.
113  The function should have the prototype:<br>
114  fn(state, input, index, length, data)
115  - state - SEM_PRE for down, SEM_POST for up (see
116  @ref identifiers.py)
117  - input - the parser's input string as a tuple of
118  integers/character codes
119  - index - the index of the first character of the matched phrase
120  - length - the number of characters in the matched phrase
121  - data - the user-supplied data (see @ref Ast.translate()
122  '''
123  inner_add_callback(self.nodesnodes, name, callback)
124 
125  def down(self, name):
126  '''Saves an AST record as the parser traverses down through a
127  node with an assigned callback function.
128  @param name The rule or UDT name of the node.
129  '''
130  if(self.nodesnodes[name]):
131  # only keep records for rule/UDT names that have callback functions
132  this_record = len(self.recordsrecords)
133  self.indexStackindexStack.append(this_record)
134  self.recordsrecords.append({
135  'name': name,
136  'this_record': this_record,
137  'that_record': None,
138  'state': id.SEM_PRE,
139  'callback': self.nodesnodes[name],
140  'phrase_index': None,
141  'phrase_length': None,
142  })
143 
144  def up(self, name, phrase_index, phrase_length):
145  '''Saves an AST record as the parser traverses up through a
146  node with an assigned callback function.
147  Completes the matching "down" record with the information
148  that was not available during the downward traversal of the node.
149  @param name The rule or UDT name of the node.
150  @param phrase_index Index of the first input character of the matched phrase.
151  @param phrase_length The number of input characters matched.
152  '''
153  if(self.nodesnodes[name]):
154  # only keep records for rule/UDT names that have callback functions
155  this_record = len(self.recordsrecords)
156  that_record = self.indexStackindexStack.pop()
157  self.recordsrecords.append({
158  'name': name,
159  'this_record': this_record,
160  'that_record': that_record,
161  'state': id.SEM_POST,
162  'callback': self.nodesnodes[name],
163  'phrase_index': phrase_index,
164  'phrase_length': phrase_length,
165  })
166  self.recordsrecords[that_record]['that_record'] = this_record
167  self.recordsrecords[that_record]['phrase_index'] = phrase_index
168  self.recordsrecords[that_record]['phrase_length'] = phrase_length
169 
170  def save_state(self):
171  '''Saves the state of the AST. Should be called by the RNM operators
172  so that the state can be restored should the branch below fail.
173  The state is a list saving off the length of the list of
174  records and the index stack.
175  '''
176  return [len(self.recordsrecords), len(self.indexStackindexStack)]
177 
178  def restore_state(self, state):
179  '''Restores the AST to a previously saved state.
180  Should be called by the RNM operators, for example,
181  if the branch below fails.
182  @param state the return value of a previous call
183  to @ref Ast.save_state()
184  '''
185  del self.recordsrecords[state[0]:]
186  del self.indexStackindexStack[state[1]:]
187 
188  def clear(self):
189  '''Clear the AST for reuse by the parser.'''
190  del self.recordsrecords[0:]
191  del self.indexStackindexStack[0:]
192 
193  def translate(self, data=None):
194  '''Do a depth-first traversal of the AST nodes,
195  calling user-supplied callback functions
196  1) if a record for the node has been created and
197  2) if the user has attached a callback function to the node
198  with @ref Ast.add_callback().
199  @param data User-supplied data which is made available to the
200  callback functions but is otherwise ignored by the AST.'''
201  inner_translate(self.inputinput, self.nodesnodes, self.recordsrecords, data)
A class for capturing the AST as the parser traverses the parse tree.
Definition: ast.py:69
def add_callback(self, name, callback)
Add a callback function to the named AST node.
Definition: ast.py:109
def down(self, name)
Saves an AST record as the parser traverses down through a node with an assigned callback function.
Definition: ast.py:125
def restore_state(self, state)
Restores the AST to a previously saved state.
Definition: ast.py:178
def clear(self)
Clear the AST for reuse by the parser.
Definition: ast.py:188
def save_state(self)
Saves the state of the AST.
Definition: ast.py:170
def translate(self, data=None)
Do a depth-first traversal of the AST nodes, calling user-supplied callback functions 1) if a record ...
Definition: ast.py:193
def up(self, name, phrase_index, phrase_length)
Saves an AST record as the parser traverses up through a node with an assigned callback function.
Definition: ast.py:144
def __init__(self, parser)
Class constructor.
Definition: ast.py:72
def copy(self)
Make a copy suitable for adding callback functions and doing translations.
Definition: ast.py:86
def inner_translate(input, nodes, records, data=None)
Traverse the AST and call the user's callback functions for translation of the saved AST node phrases...
Definition: ast.py:23
def inner_add_callback(nodes, name, callback)
Set a callback function for a named node.
Definition: ast.py:8
Python APG, Version 1.0, is licensed under the 2-Clause BSD License,
an Open Source Initiative Approved License.