Version 1.0
Copyright © 2022 Lowell D. Thomas
Python APG
 … an ABNF Parser Generator
trace.py
Go to the documentation of this file.
1 ''' @file apg_py/lib/trace.py @brief Displays a trace of the parse tree.
2 The trace is a printed description of each
3 parse tree node processed by the parser.
4 '''
5 
6 import sys
7 # import time
8 from apg_py.lib import identifiers as id
9 
10 # localtime = time.asctime(time.localtime(time.time()))
11 # print("Print Local current time :", localtime)
12 # sys.stdout.write('writing to stdout\n')
13 
14 
15 class Trace():
16  '''Class for tracing and displaying the progress
17  of the parser through the parse tree.
18  The Trace class has a copy of the Parser class and knows how to use it.
19  Therefore, Trace and Parser need to remain in sync throughout
20  development.'''
21 
22  def __init__(self, parser, fname=None, mode='dc', line_max=32):
23  '''
24  @param parser The parser to trace.
25  @param fname If present, the file name to write the trace to.
26  @param mode The display mode.
27  - 'x' Display characters as hexadecimal digits. e.g. x2e.
28  - 'xc' Display all characters 32-127 as ASCII characters,
29  otherwise use hexadecimal digit display.
30  - 'd' Display characters as decimal digits. e.g. 32.
31  - 'dc' (default) Display all characters 32-127 as ASCII characters,
32  otherwise use decimal digit display.
33  @param line_max The maximum line length in characters
34  for any trace line.
35  '''
36  self.filefile = sys.stdout
37  if(mode not in ['x', 'xc', 'd', 'dc']):
38  raise Exception('mode must be one of x, d, xc, dc: found: ', mode)
39  self.modemode = mode
40  self.parserparser = parser
41  parser.trace = self
42  if(fname):
43  self.filefile = open(fname, "w")
44  self.line_maxline_max = line_max
45  self.selectOpselectOp = {
46  id.ALT: self.traceALTtraceALT,
47  id.CAT: self.traceCATtraceCAT,
48  id.REP: self.traceREPtraceREP,
49  id.RNM: self.traceRNMtraceRNM,
50  id.TLS: self.traceTLStraceTLS,
51  id.TBS: self.traceTBStraceTBS,
52  id.TRG: self.traceTRGtraceTRG,
53  id.UDT: self.traceUDTtraceUDT,
54  id.AND: self.traceANDtraceAND,
55  id.NOT: self.traceNOTtraceNOT,
56  id.BKR: self.traceBKRtraceBKR,
57  id.BKA: self.traceBKAtraceBKA,
58  id.BKN: self.traceBKNtraceBKN,
59  id.ABG: self.traceABGtraceABG,
60  id.AEN: self.traceAENtraceAEN,
61  }
62  self.select_modeselect_mode = {
63  'x': self.phrasexphrasex,
64  'd': self.phrasedphrased,
65  'xc': self.phrasexcphrasexc,
66  'dc': self.phrasedcphrasedc
67  }
68 
69  def __del__(self):
70  '''Destructor for ensuring that the output file is closed, if any.'''
71  if(self.filefile and self.filefile != sys.stdout):
72  # self.file.flush()
73  self.filefile.close()
74  self.filefile = None
75 
76  def finish(self):
77  '''Guarantee that the output file is flushed.'''
78  if(self.filefile):
79  self.filefile.flush()
80 
81  def set_display_length(self, max_len):
82  '''Set the maximum trace line display length in characters.
83  @param max_len The maximum number of characters to
84  display on a line.'''
85  self.line_maxline_max = max(0, max_len)
86 
87  def indent(self, n):
88  '''For internal use only.'''
89  ret = ''
90  for i in range(n):
91  ret += '.'
92  return ret
93 
94  def traceALT(self, op):
95  '''For internal use only.'''
96  return 'ALT(' + str(len(op['children'])) + ')'
97 
98  def traceCAT(self, op):
99  '''For internal use only.'''
100  return 'CAT(' + str(len(op['children'])) + ')'
101 
102  def traceREP(self, op):
103  '''For internal use only.'''
104  rep_max = str(op['max']) if(op['max'] < id.MAX_INT) else 'inf'
105  return 'REP(' + str(op['min']) + ',' + rep_max + ')'
106 
107  def traceRNM(self, op):
108  '''For internal use only.'''
109  rule = self.parserparser.rules[op['index']]
110  return 'RNM(' + rule['name'] + ')'
111 
112  def traceUDT(self, op):
113  '''For internal use only.'''
114  udt = self.parserparser.udts[op['index']]
115  return 'UDT(' + udt['name'] + ')'
116 
117  def traceTLS(self, op):
118  '''For internal use only.'''
119  tChars = min(3, len(op['string']))
120  tEnd = '...' if(len(op['string']) > tChars) else ''
121  display = ''
122  for i in range(tChars):
123  if(i > 0):
124  display += ','
125  display += str(op['string'][i])
126  return 'TLS(' + display + tEnd + ')'
127 
128  def traceTBS(self, op):
129  '''For internal use only.'''
130  tChars = min(3, len(op['string']))
131  tEnd = '...' if(len(op['string']) > tChars) else ''
132  display = ''
133  for i in range(tChars):
134  if(i > 0):
135  display += ','
136  display += str(op['string'][i])
137  return 'TBS(' + display + tEnd + ')'
138 
139  def traceTRG(self, op):
140  '''For internal use only.'''
141  return 'TRG(' + str(op['min']) + ',' + str(op['max']) + ')'
142 
143  def traceAND(self, op):
144  '''For internal use only.'''
145  return 'AND'
146 
147  def traceNOT(self, op):
148  '''For internal use only.'''
149  return 'NOT'
150 
151  def traceBKR(self, op):
152  '''For internal use only.'''
153  case = '%i'
154  if(op['bkr_case'] == id.BKR_MODE_CS):
155  case = '%s'
156  mode = '%u'
157  if(op['bkr_mode'] == id.BKR_MODE_RM):
158  case = '%r'
159  return 'BKR(\\' + case + mode + op['name'] + ')'
160 
161  def traceBKA(self, op):
162  '''For internal use only.'''
163  return 'BKA'
164 
165  def traceBKN(self, op):
166  '''For internal use only.'''
167  return 'BKN'
168 
169  def traceABG(self, op):
170  '''For internal use only.'''
171  return 'ABG'
172 
173  def traceAEN(self, op):
174  '''For internal use only.'''
175  return 'AEN'
176 
177  def phrasex(self, phrase):
178  '''For internal use only.'''
179  ret = ''
180  count = 0
181  for dig in phrase:
182  if(count > 0):
183  ret += ' '
184  ret += ('x%02x' % dig)
185  count += 1
186  return ret
187 
188  def phrasexc(self, phrase):
189  '''For internal use only.'''
190  ret = ''
191  count = 0
192  prev_not_ascii = True
193  for dig in phrase:
194  if(dig >= 32 and dig < 127):
195  if(count > 0 and prev_not_ascii):
196  ret += ' '
197  prev_not_ascii = False
198  ret += chr(dig)
199  else:
200  if(count > 0):
201  ret += ' '
202  ret += ('x%02x' % dig)
203  prev_not_ascii = True
204  count += 1
205  return ret
206 
207  def phrased(self, phrase):
208  '''For internal use only.'''
209  ret = ''
210  count = 0
211  for dig in phrase:
212  if(count > 0):
213  ret += ' '
214  ret += ('%d' % dig)
215  count += 1
216  return ret
217 
218  def phrasedc(self, phrase):
219  '''For internal use only.'''
220  ret = ''
221  count = 0
222  prev_not_ascii = True
223  for dig in phrase:
224  if(dig >= 32 and dig < 127):
225  if(count > 0 and prev_not_ascii):
226  ret += ' '
227  prev_not_ascii = False
228  ret += chr(dig)
229  else:
230  if(count > 0):
231  ret += ' '
232  ret += ('%d' % dig)
233  prev_not_ascii = True
234  count += 1
235  return ret
236 
237  def down(self, op):
238  '''For internal use only.'''
239  msg = 'state must be ACTIVE on downward transversal of node'
240  assert(self.parserparser.state == id.ACTIVE), msg
241  if(self.parserparser.current_look_direction == id.LOOKAROUND_BEHIND):
242  arrow = '<- '
243  else:
244  arrow = '-> '
245  fn = self.selectOpselectOp.get(op['type'])
246  ret = self.indentindent(self.parserparser.tree_depth)
247  ret += '|-|-|'
248  ret += fn(op)
249  ret += arrow
250  phraseEnd = min(
251  self.parserparser.sub_end,
252  self.parserparser.phrase_index +
253  self.line_maxline_max)
254  line_end = '' if(phraseEnd == self.parserparser.sub_end) else '...'
255  phrase = self.parserparser.input[
256  self.parserparser.phrase_index:phraseEnd]
257  charFormat = self.select_modeselect_mode.get(self.modemode, None)
258  assert(charFormat is not None), 'invalid mode found in trace down'
259  ret += charFormat(phrase)
260  ret += line_end
261  self.filefile.write(ret + '\n')
262 
263  def up(self, op, begin_index):
264  '''For internal use only.'''
265  msg = 'ACTIVE state not allowed on upward transversal of node'
266  state = self.parserparser.state
267  assert(state != id.ACTIVE), msg
268  if(self.parserparser.current_look_direction == id.LOOKAROUND_BEHIND):
269  arrow = '<- '
270  phrase_length = begin_index - self.parserparser.phrase_index
271  phrase_length = min(phrase_length, self.line_maxline_max)
272  phrase_index = self.parserparser.phrase_index
273  else:
274  arrow = '-> '
275  phrase_length = self.parserparser.phrase_index - begin_index
276  phrase_length = min(phrase_length, self.line_maxline_max)
277  phrase_index = self.parserparser.phrase_index - phrase_length
278  ret = self.indentindent(self.parserparser.tree_depth)
279  stateDisplay = id.dict.get(state, None)[0]
280  assert(stateDisplay is not None), 'invalid state found in trace.up()'
281  ret += '|' + stateDisplay + '|' + \
282  str(phrase_length) + '|'
283  fn = self.selectOpselectOp.get(op['type'])
284  ret += fn(op)
285  if(state == id.MATCH):
286  ret += arrow
287  line_end = '...' if(
288  phrase_length < phrase_length) else ''
289  phrase = self.parserparser.input[phrase_index:
290  phrase_index + phrase_length]
291  charFormat = self.select_modeselect_mode.get(self.modemode, None)
292  assert(charFormat is not None), 'invalid mode found in trace down'
293  ret += charFormat(phrase)
294  ret += line_end
295  elif(state == id.EMPTY):
296  ret += arrow
297  self.filefile.write(ret + '\n')
Class for tracing and displaying the progress of the parser through the parse tree.
Definition: trace.py:15
def phrased(self, phrase)
For internal use only.
Definition: trace.py:207
def down(self, op)
For internal use only.
Definition: trace.py:237
def traceNOT(self, op)
For internal use only.
Definition: trace.py:147
def traceBKA(self, op)
For internal use only.
Definition: trace.py:161
def set_display_length(self, max_len)
Set the maximum trace line display length in characters.
Definition: trace.py:81
def traceAND(self, op)
For internal use only.
Definition: trace.py:143
def traceCAT(self, op)
For internal use only.
Definition: trace.py:98
def __init__(self, parser, fname=None, mode='dc', line_max=32)
Definition: trace.py:22
def traceAEN(self, op)
For internal use only.
Definition: trace.py:173
def traceUDT(self, op)
For internal use only.
Definition: trace.py:112
def traceBKR(self, op)
For internal use only.
Definition: trace.py:151
def finish(self)
Guarantee that the output file is flushed.
Definition: trace.py:76
def traceTBS(self, op)
For internal use only.
Definition: trace.py:128
def __del__(self)
Destructor for ensuring that the output file is closed, if any.
Definition: trace.py:69
def traceTRG(self, op)
For internal use only.
Definition: trace.py:139
def up(self, op, begin_index)
For internal use only.
Definition: trace.py:263
def traceTLS(self, op)
For internal use only.
Definition: trace.py:117
def traceABG(self, op)
For internal use only.
Definition: trace.py:169
def phrasex(self, phrase)
For internal use only.
Definition: trace.py:177
def indent(self, n)
For internal use only.
Definition: trace.py:87
def traceREP(self, op)
For internal use only.
Definition: trace.py:102
def phrasedc(self, phrase)
For internal use only.
Definition: trace.py:218
def traceBKN(self, op)
For internal use only.
Definition: trace.py:165
def phrasexc(self, phrase)
For internal use only.
Definition: trace.py:188
def traceRNM(self, op)
For internal use only.
Definition: trace.py:107
def traceALT(self, op)
For internal use only.
Definition: trace.py:94
def fn(input, result)
Python APG, Version 1.0, is licensed under the 2-Clause BSD License,
an Open Source Initiative Approved License.