Version 1.0
Copyright © 2022 Lowell D. Thomas
Python APG
 … an ABNF Parser Generator
rule_attributes.py
Go to the documentation of this file.
1 ''' @file apg_py/api/rule_attributes.py
2 @brief Compute the rule attributes for all rules.
3 '''
4 from apg_py.lib import identifiers as id
5 
6 
7 # from unicodedata import name
8 
9 
10 class Attr():
11  '''Simple class for attribute data for each rule.'''
12 
13  def __init__(self, name='dummy', type=id.ATTR_N, group=-1):
14  '''Attr constructor.
15  @param name The name of the rule this attribute applies to.
16  @param type The identifier of the rule's recursive type.
17  @param group The group number if the type is
18  mutually-recursive (ATTR_MR),
19  otherwise, -1.
20  '''
21  self.leftleft = False
22  self.nestednested = False
23  self.rightright = False
24  self.emptyempty = False
25  self.finitefinite = False
26  self.cycliccyclic = False
27  self.leafleaf = False
28  self.is_openis_open = False
29  self.is_completeis_complete = False
30  self.namename = name
31  self.lowerlower = name.lower()
32  self.typetype = type
33  self.groupgroup = group
34 
35  def reset(self):
36  '''Reset all attributes to false.'''
37  self.leftleft = False
38  self.nestednested = False
39  self.rightright = False
40  self.emptyempty = False
41  self.finitefinite = False
42  self.cycliccyclic = False
43  self.leafleaf = False
44  self.is_openis_open = False
45  self.is_completeis_complete = False
46 
47  def dup(self):
48  '''Create a duplicate attribute object from the present one.
49  @returns Returns new attribute object identical to the present one.
50  '''
51  cpy = Attr()
52  cpy.left = self.leftleft
53  cpy.nested = self.nestednested
54  cpy.right = self.rightright
55  cpy.empty = self.emptyempty
56  cpy.finite = self.finitefinite
57  cpy.cyclic = self.cycliccyclic
58  cpy.leaf = self.leafleaf
59  cpy.is_open = self.is_openis_open
60  cpy.is_complete = self.is_completeis_complete
61  cpy.name = self.namename
62  cpy.lower = self.lowerlower
63  cpy.type = self.typetype
64  cpy.group = self.groupgroup
65  return cpy
66 
67  def copy(self, cpy):
68  '''Copy a given attribute into the present attribute.
69  @param cpy The attribute to copy.
70  '''
71  self.leftleft = cpy.left
72  self.nestednested = cpy.nested
73  self.rightright = cpy.right
74  self.emptyempty = cpy.empty
75  self.finitefinite = cpy.finite
76  self.cycliccyclic = cpy.cyclic
77  self.leafleaf = cpy.leaf
78  self.is_openis_open = cpy.is_open
79  self.is_completeis_complete = cpy.is_complete
80  self.namename = cpy.name
81  self.lowerlower = cpy.lower
82  self.typetype = cpy.type
83  self.groupgroup = cpy.group
84 
85 
86 def rule_attributes(rules, rule_deps):
87  '''Compute the recursive and non-recursive attributes for each rule.
88  @param rules The list of rules.
89  @param rule_deps The rule dependencies previously computed
90  (see @ref rule_dependencies.py)
91  @returns Returns the rule attributes and any errors as a dictionary
92  {'errors': errors, 'attributes': attrs}
93  '''
94  # internal functions
95  def op_eval(opcodes, op_index):
96  func = switch.get(opcodes[op_index]['type'])
97  if(func is None):
98  raise Exception(
99  'op_eval: unrecognized opcode type',
100  opcodes[op_index]['type'])
101  return func(opcodes, op_index)
102 
103  def op_alt(opcodes, op_index):
104  # generate a list of child attributes
105  op = opcodes[op_index]
106  op_range = range(len(op['children']))
107  child_attrs = []
108  for i in op_range:
109  child_attrs.append(Attr())
110 
111  # compute each child attribute
112  for i in op_range:
113  child_attrs[i] = op_eval(opcodes, op['children'][i])
114 
115  # if any child attribute is true ALT is true
116  attr_op = Attr()
117  for attr in child_attrs:
118  if(attr.left):
119  attr_op.left = True
120  if(attr.nested):
121  attr_op.nested = True
122  if(attr.right):
123  attr_op.right = True
124  if(attr.cyclic):
125  attr_op.cyclic = True
126  if(attr.empty):
127  attr_op.empty = True
128  if(attr.finite):
129  attr_op.finite = True
130  return attr_op
131 
132  def is_recursive(attr):
133  if(attr.left or attr.nested or attr.right or attr.cyclic):
134  return True
135  return False
136 
137  def is_cat_nested(attrs):
138  children = len(attrs)
139  # 1) if any child is nested, CAT is nested
140  for attr in attrs:
141  if(attr.nested):
142  return True
143 
144  # 2) if the left-most right-recursive child is
145  # followed by at least one non-empty, non-right-recursive child
146  for i in range(children):
147  attri = attrs[i]
148  if(attri.right and not attri.leaf):
149  for j in range(i + 1, children):
150  attrj = attrs[j]
151  if(not attrj.empty and not attrj.right
152  and not attrj.cyclic):
153  return True
154 
155  # 3) if the right-most left-recursive child is preceded
156  # by at least one non-empty non-left-recursive child
157  for i in range(children - 1, -1, -1):
158  attri = attrs[i]
159  if(attri.right and not attri.leaf):
160  for j in range(i - 1, -1, -1):
161  attrj = attrs[j]
162  if(not attrj.empty and not attrj.left
163  and not attrj.cyclic):
164  return True
165 
166  # 4) if there is at least one recursive child between the
167  # left-most and right-most non-recursive non-empty children
168  for i in range(children):
169  attri = attrs[i]
170  if(not attr.empty and not is_recursive(attri)):
171  for j in range(i + 1, children):
172  if(is_recursive(attrs[j])):
173  for k in range(j + 1, children):
174  attrk = attrs[k]
175  if(not attrk.empty
176  and not is_recursive(attrk)):
177  return True
178 
179  # none of the above
180  return False
181 
182  def is_cat_cyclic(attrs):
183  # if all children are cyclic, CAT is cyclic
184  for attr in attrs:
185  if(not attr.cyclic):
186  return False
187  return True
188 
189  def is_cat_left(attrs):
190  # if the left-most non-empty child is left, CAT is left
191  for attr in attrs:
192  if(attr.left):
193  return True
194  if(not attr.empty):
195  return False
196  # keep looking
197  return False
198 
199  def is_cat_right(attrs):
200  # if the right-most non-empty child is right, CAT is right
201  for i in range(len(attrs) - 1, -1, -1):
202  if(attrs[i].right):
203  return True
204  if(not attrs[i].empty):
205  return False
206  # keep looking
207  return False
208 
209  def is_cat_empty(attrs):
210  # if all children are empty, CAT is empty
211  for attr in attrs:
212  if(not attr.empty):
213  return False
214  return True
215 
216  def is_cat_finite(attrs):
217  # if all children are finite, CAT is finite
218  for attr in attrs:
219  if(not attr.finite):
220  return False
221  return True
222 
223  def op_cat(opcodes, op_index):
224  # generate a list of child attributes
225  op = opcodes[op_index]
226  op_range = range(len(op['children']))
227  child_attrs = []
228  for i in op_range:
229  child_attrs.append(Attr())
230 
231  # compute each child attribute
232  for i in op_range:
233  child_attrs[i] = op_eval(opcodes, op['children'][i])
234 
235  # compute CAT attributes from child attributes
236  attr_op = Attr()
237  attr_op.left = is_cat_left(child_attrs)
238  attr_op.nested = is_cat_nested(child_attrs)
239  attr_op.right = is_cat_right(child_attrs)
240  attr_op.cyclic = is_cat_cyclic(child_attrs)
241  attr_op.empty = is_cat_empty(child_attrs)
242  attr_op.finite = is_cat_finite(child_attrs)
243  return attr_op
244 
245  def op_rep(opcodes, op_index):
246  attr_op = op_eval(opcodes, op_index + 1)
247  if(opcodes[op_index]['min'] == 0):
248  attr_op.empty = True
249  attr_op.finite = True
250  return attr_op
251 
252  def op_rnm(opcodes, op_index):
253  index = opcodes[op_index]['index']
254  rule_eval(index)
255  return working_attrs[index].dup()
256 
257  def op_bkr(opcodes, op_index):
258  attr_op = Attr()
259  bkr_op = opcodes[op_index]
260  if(bkr_op['is_udt']):
261  attr_op.empty = bkr_op['empty']
262  attr_op.finite = True
263  else:
264  # use the empty and finite values for the rule
265  index = bkr_op['index']
266  rule_eval(index)
267  attr_op.copy(working_attrs[index])
268 
269  # however, this is a terminal node like TBS
270  attr_op.left = False
271  attr_op.nested = False
272  attr_op.right = False
273  attr_op.cyclic = False
274  return attr_op
275 
276  def op_and(opcodes, op_index):
277  attr_op = op_eval(opcodes, op_index + 1)
278  attr_op.empty = True
279  return attr_op
280 
281  def op_tls(opcodes, op_index):
282  attr_op = Attr()
283  attr_op.empty = len(opcodes[op_index]['string']) == 0
284  attr_op.finite = True
285  attr_op.cyclic = False
286  return attr_op
287 
288  def op_tbs(opcodes, op_index):
289  attr_op = Attr()
290  attr_op.empty = False
291  attr_op.finite = True
292  attr_op.cyclic = False
293  return attr_op
294 
295  def op_udt(opcodes, op_index):
296  attr_op = Attr()
297  attr_op.empty = opcodes[op_index]['empty']
298  attr_op.finite = True
299  attr_op.cyclic = False
300  return attr_op
301 
302  def op_anchor(opcodes, op_index):
303  attr_op = Attr()
304  attr_op.empty = True
305  attr_op.finite = True
306  attr_op.cyclic = False
307  return attr_op
308  switch = {
309  id.ALT: op_alt,
310  id.CAT: op_cat,
311  id.REP: op_rep,
312  id.RNM: op_rnm,
313  id.BKR: op_bkr,
314  id.AND: op_and,
315  id.NOT: op_and,
316  id.BKA: op_and,
317  id.BKN: op_and,
318  id.TLS: op_tls,
319  id.TBS: op_tbs,
320  id.TRG: op_tbs,
321  id.UDT: op_udt,
322  id.ABG: op_anchor,
323  id.AEN: op_anchor}
324 
325  def rule_eval(rule_index):
326  attri = working_attrs[rule_index]
327  if(attri.is_complete):
328  return
329  if(not attri.is_open):
330  # open the rule an traverse it
331  attri.is_open = True
332  attr_op = op_eval(rules[rule_index]['opcodes'], 0)
333  attri.left = attr_op.left
334  attri.nested = attr_op.nested
335  attri.right = attr_op.right
336  attri.cyclic = attr_op.cyclic
337  attri.finite = attr_op.finite
338  attri.empty = attr_op.empty
339  attri.leaf = False
340  attri.is_open = False
341  attri.is_complete = True
342  return
343  # recursive leaf
344  if(rule_index == start_rule):
345  attri.left = True
346  attri.right = True
347  attri.cyclic = True
348  attri.leaf = True
349  return
350 
351  # terminal leaf
352  attri.finite = True
353 
354  # end internal functions
355 
356  # compute the attributes
357  # initialize the attributes
358  rule_count = len(rules)
359  rule_range = range(rule_count)
360  attrs = []
361  working_attrs = []
362  for i in rule_range:
363  attrs.append(
364  Attr(
365  rules[i]['name'],
366  rule_deps[i]['recursive_type'],
367  rule_deps[i]['group_no']))
368  working_attrs.append(
369  Attr(
370  rules[i]['name'],
371  rule_deps[i]['recursive_type'],
372  rule_deps[i]['group_no']))
373 
374  # compute the rule attributes
375  for i in rule_range:
376  for attr in working_attrs:
377  attr.reset()
378  start_rule = i
379  rule_eval(i)
380  attrs[i].copy(working_attrs[i])
381 
382  # check for errors
383  errors = []
384  for i in rule_range:
385  attr = attrs[i]
386  rule = rules[i]
387  if(attr.cyclic):
388  errors.append({'line': rule['line'],
389  'index': 0,
390  'msg': 'rule ' + rule['name'] + ' is cyclic'})
391  if(attr.left):
392  errors.append({'line': rule['line'],
393  'index': 0,
394  'msg': 'rule ' + rule['name'] +
395  ' is left recrusive'})
396  if(not attr.finite):
397  errors.append(
398  {
399  'line': rule['line'],
400  'index': 0,
401  'msg': 'rule ' + rule['name'] +
402  ' only matches infinte strings'})
403 
404  # return the attributes
405  return {'errors': errors, 'attributes': attrs}
406 
407 
408 def display_rule_attributes(attrs_arg, mode='index'):
409  '''Display all rule attributes.
410  @param attrs_arg The attributes to display.
411  @param mode The display mode
412  - 'index' Display attributes of rules in the order
413  they appear in the grammar syntax.
414  - 'type' Display attributes grouped by recursive type,
415  listed alphabetically within each type.
416  - 'alpha' Display attributes alphabetically by rule name.
417  @returns Returns a string of the display.
418  '''
419  def sort_alpha(val):
420  return val.lower
421 
422  def sort_type(val):
423  return val.type
424 
425  def sort_group(val):
426  return val.group
427 
428  attrs = []
429  for attr in attrs_arg:
430  attrs.append(attr.dup())
431  if(mode == 'type'):
432  attrs.sort(key=sort_alpha)
433  attrs.sort(key=sort_type)
434  attrs.sort(key=sort_group)
435  elif(mode == 'alpha'):
436  attrs.sort(key=sort_alpha)
437  # default is to display by rule index
438  fmt = '%6s: %6s: %6s: %6s: %6s: %6s: %6s: %6s: %s\n'
439  display = ''
440  display += (fmt % ('left', 'nested',
441  'right', 'cyclic', 'finite',
442  'empty', 'type',
443  'group',
444  'rule name'))
445 
446  def yes_or_no(val):
447  if(val):
448  return 'yes'
449  return 'no'
450 
451  def display_type(val):
452  if(val == id.ATTR_N):
453  return 'N'
454  if(val == id.ATTR_R):
455  return 'R'
456  if(val == id.ATTR_MR):
457  return 'MR'
458  return '??'
459  for attr in attrs:
460  left = yes_or_no(attr.left)
461  nested = yes_or_no(attr.nested)
462  right = yes_or_no(attr.right)
463  cyclic = yes_or_no(attr.cyclic)
464  finite = yes_or_no(attr.finite)
465  empty = yes_or_no(attr.empty)
466  type = display_type(attr.type)
467  if(attr.type == id.ATTR_N or attr.type == id.ATTR_R):
468  group = '--'
469  else:
470  group = str(attr.group)
471  if(attr.type == id.ATTR_MR):
472  group = str(attr.group)
473  display += (fmt % (left, nested, right, cyclic,
474  finite, empty, type, group, attr.name))
475 
476  return display
Simple class for attribute data for each rule.
def reset(self)
Reset all attributes to false.
def dup(self)
Create a duplicate attribute object from the present one.
def __init__(self, name='dummy', type=id.ATTR_N, group=-1)
Attr constructor.
def copy(self, cpy)
Copy a given attribute into the present attribute.
def display_rule_attributes(attrs_arg, mode='index')
Display all rule attributes.
def rule_attributes(rules, rule_deps)
Compute the recursive and non-recursive attributes for each rule.
Python APG, Version 1.0, is licensed under the 2-Clause BSD License,
an Open Source Initiative Approved License.