blob: 9867d49fc3ca219c0ce9fcfa2756fe8e2fb1c9f1 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#!/usr/bin/python
2#
3# Copyright (c) 2013 Big Switch Networks, Inc.
4#
5# Licensed under the Eclipse Public License, Version 1.0 (the
6# "License"); you may not use this file except in compliance with the
7# License. You may obtain a copy of the License at
8#
9# http://www.eclipse.org/legal/epl-v10.html
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16#
17
18# Simple command description builder.
19#
20
21import os
22import copy
23import fileinput
24import traceback
25import argparse
26
27def parse(*line):
28 if args.p:
29 print line
30
31def debug(*line):
32 if args.d:
33 print line
34
35def verbose(*line):
36 if args.v:
37 print line
38
39def error(*line):
40 print line
41
42
43add_token = ('o', '+')
44front_token = ('o', '^')
45end_token = ('o', '$')
46
47def tokens(line):
48 """
49 Returns the tokenzied line, with a eol token at the end
50 The start-token must be prefixed before parsing
51 """
52 tokens = []
53 i = 0
54 end = len(line)
55 while i < end:
56 # break
57 while line[i] in ' \t\r\n':
58 i += 1
59 if i == end:
60 break
61 if i == end:
62 break
63 if line[i] in '[]{}<>()|':
64 tokens.append(('o', line[i])) # 'o' <= char, op
65 i += 1
66 continue
67 # span.
68 token = ''
69 run_i = i
70 while not line[run_i] in ' \t[]{}<>|\n':
71 run_i += 1
72 if run_i == end:
73 break
74 tokens.append(('t', line[i:run_i])) # 't' <= token
75 i = run_i
76 #
77 tokens.append(end_token) # 't' <= '$' for eol
78
79 return tokens
80
81priority_dict = {
82 '^' : 0, # hat, front of line
83 '$' : 1, # newline, end of line
84 ')' : 2,
85 '(' : 3,
86 '}' : 4,
87 '{' : 5,
88 ']' : 6,
89 '[' : 7,
90 '>' : 8,
91 '<' : 9,
92 '|' : 10,
93 '+' : 11,
94 }
95
96def priority(tok):
97 if not is_op(tok):
98 return -1 # lower than any with a valid priority, enables push
99 if not tok[1] in priority_dict:
100 return 100 # higher than any with a valid priority, enables push
101 return priority_dict[tok[1]]
102
103def is_op(tok):
104 return tok[0] == 'o'
105
106def is_token(tok):
107 return tok[0] == 't'
108
109def is_field(tok):
110 return tok[0] == 'field'
111
112def is_tree(tok):
113 return tok[0] == 'tree'
114
115def is_list(tok):
116 return type(tok) == list
117
118def op_of(tok):
119 return tok[1]
120
121def token_of(tok):
122 return tok[1]
123
124def field_list_of(tok):
125 return tok[1]
126
127balance = {
128 ']' : '[',
129 '}' : '{',
130 '>' : '<',
131 ')' : '(',
132 '$' : '^',
133}
134
135def partner(tok):
136 if is_op(tok):
137 if token_of(tok) in balance:
138 return balance[token_of(tok)]
139 return None
140
141
142stack = []
143
144def print_stack():
145 for s in stack:
146 print s
147
148def push(item):
149 parse("PUSH ", stack, "ADD ", item)
150 stack.append(item)
151
152def pop():
153 parse( "POP", stack )
154 p = stack[-1]
155 del stack[-1]
156 return p
157
158def reset_stack():
159 global stack
160 stack = []
161
162def peek(i = 1):
163 top = len(stack)
164 if top < i:
165 return ('o','^') # <= front of line
166 return stack[-i]
167
168def is_in_order(collect):
169 if is_op(collect[1]) and collect[1][1] == '+':
170 return True
171 return False
172
173def is_either(collect):
174 if is_op(collect[1]) and collect[1][1] == '|':
175 return True
176 return False
177
178def gather_field_list(collect, field_list):
179 if type(collect) == list:
180 for f in collect:
181 gather_field_list(f, field_list)
182 elif is_token(collect):
183 field_list.append(collect)
184 elif is_tree(collect):
185 gather_field_list(token_of(token_of(collect)), field_list)
186 else:
187 field_list.append(field_list)
188
189def gather_field(collect):
190 parse( "GATHER FIELD ", collect)
191 field_list = []
192 if is_token(collect):
193 field_list.append(collect)
194 elif is_tree(collect):
195 gather_field_list(token_of(token_of(collect)), field_list)
196 elif type(collect) == list:
197 gather_field_list(collect, field_list)
198 else:
199 field_list.append(collect)
200 return ('field', field_list)
201
202def tree_builder(collect, tok):
203 result = None
204 op = op_of(tok)
205 parse( "WHAT ", collect, tok)
206 if op == '}': # { stuff } ... select one from the args
207 # XXX early return
208 if len(collect) == 1:
209 result = ('CHOICE ALONE', collect)
210 elif is_either(collect):
211 result = ("CHOICE OF", [c for c in collect if not is_op(c)])
212 elif is_in_order(collect):
213 result = ("CHOICE ORDER", [c for c in collect if not is_op(c)])
214 elif is_tree(collect):
215 result = ('CHOICE TREE', collect)
216 elif is_field(collect):
217 return gather_field(collect)
218 elif is_token(collect):
219 return collect
220 elif is_token(collect):
221 return collect
222 else:
223 result = ("CHOICE TROUBLE", op, collect)
224 elif op == ']': # [ stuff ] ... stuff which is optional
225 if len(collect) == 1:
226 result = ('OPTION ALONE', collect)
227 elif is_either(collect):
228 result = ("OPTION OF", [c for c in collect if not is_op(c)])
229 elif is_in_order(collect):
230 result = ("OPTION ORDER", [c for c in collect if not is_op(c)])
231 elif is_tree(collect):
232 result = ('OPTION TREE', collect)
233 elif is_field(collect):
234 return ('tree', ('OPTION FIELD', collect))
235 elif is_token(collect):
236 result = ('OPTION TOKEN', collect)
237 else:
238 result = ("OPTION TROUBLE", op, collect)
239 elif op == ')': # ( stuff ) ... no semantic meaning,
240 # XXX early return
241 return collect
242 elif op == '>': # description of a field
243 gather = gather_field(collect)
244 parse("GATHERED: ",gather)
245 return gather
246 elif op == '|': # either of
247 result = ('EITHER', [c for c in collect if not is_op(c)])
248 elif op == '+': # sum of
249 result = ('ORDER', [c for c in collect if not is_op(c)])
250 elif op == '$': # eol, collect up any tree's left
251 # XXX syntax error?
252 print "STACK "
253 print_stack()
254 print "TOK ", tok
255 print "COLLECT", collect
256 exit()
257 else:
258 parse('return collect, later tok', tok)
259 return collect
260 parse( "BUILD ", op, type(result))
261 return ('tree', result)
262
263
264def single_token(tok):
265 (which, t) = tok
266 parse( "NEXT", which, t, peek())
267
268 if is_token(tok) and is_token(peek()):
269 # two tokens in a row, pretend the op is '+'
270 push(add_token)
271
272 # is this a <tree><tree> ?
273 if is_tree(peek()) and (is_tree(peek(2)) or is_field(peek(2))):
274 # collect together as many as possible
275 collect = [pop()]
276 while is_tree(peek()) or is_field(peek()):
277 collect.insert(0, pop())
278 push(tree_builder(collect, add_token))
279 # is this a <tree><tree> ?
280 elif (not is_op(peek())) and (not is_op(peek(2))):
281 # collect together as many as possible
282 collect = [pop()]
283 while not is_op(peek()):
284 collect.insert(0, pop())
285 push(tree_builder(collect, add_token))
286
287 if is_op(tok):
288 if not is_op(peek(1)): # item or token or field
289 parse( 'PRIO ', tok, priority(tok), peek(2), priority(peek(2)))
290 while priority(tok) < priority(peek(2)):
291 # collect as many from the same priority
292 last = pop()
293 parse( "-->", stack, tok, last)
294 # uniary op?
295 if is_op(peek()) and partner(tok) == op_of(peek()):
296 parse( "UNIARY ")
297 pop() # <= pop matching op
298 push(tree_builder(last, tok)) # <= token popped off
299 parse( "LEAVE", stack, tok)
300 return # don't push the uniary right op
301
302 collect = last
303 op = tok
304 if is_op(peek()):
305 op = peek()
306 parse( "BINARY ", op)
307 collect = [last]
308 while is_op(peek()) and \
309 not (partner(tok) == op_of(peek())) and \
310 priority(op) == priority(peek()):
311 parse( "WHY ", op_of(op), priority(op), op_of(peek()), priority(peek()))
312 collect.insert(0, pop()) # <= op
313 collect.insert(0, pop()) # <= token
314 if len(collect) == 1:
315 print "NOT BINARY", tok, op, peek
316 exit()
317 parse( "==> ", collect, tok)
318 parse( "__ ", stack)
319 push(tree_builder(collect, op))
320 parse( "SO FAR ", stack)
321 parse( "OP FAR ", tok)
322 push(tok)
323 parse( "LAST", stack, tok)
324
325
326#
327def single_line(tokens):
328 reset_stack()
329 for (which, t) in tokens:
330 single_token((which, t))
331
332def all_tokens(token_list):
333 for token in token_list:
334 parse( "ALL TOKEN? ", token)
335 if not is_token(token):
336 return False
337 return True
338
339
340class printer:
341 def __init__(self, form, is_optional, indent = 2):
342 self.form = form
343 self.is_optional = is_optional
344 self.need_optional = False
345 self.indent = indent
346
347 def __str__(self):
348 return "form %s need %s optional %s indent %s" % (
349 self.form, self.need_optional, self.is_optional, self.indent)
350
351 def indents(self, extra = 0):
352 return " " * (self.indent + extra)
353
354 def to_dict(self):
355 self.form = 'dict'
356
357 def is_dict(self):
358 if self.form == 'dict':
359 return True
360 return False
361
362 def to_tuple(self):
363 self.form = 'tuple'
364
365 def is_tuple(self):
366 if self.form == 'tuple':
367 return True
368 return False
369
370 def more_indent(self, incr = 1):
371 self.indent += incr
372
373 def less_indent(self, decr = 1):
374 self.indent -= decr
375
376 def nest(self, incr = 1, form = None, is_optional = None):
377 new = copy.deepcopy(self)
378 new.indent += 1
379 if form:
380 new.form = form
381 if is_optional:
382 new.is_optional = is_optional
383 return new
384
385class description:
386 def __init__(self):
387 self.desc = []
388
389 def raw_out(self, line):
390 self.desc.append(line)
391
392 def out(self, line, printer):
393 self.desc.append(printer.indents() + line)
394
395 def out_with_indent(self, line, printer):
396 self.desc.append(printer.indents(1) + line)
397
398 def result(self):
399 return '\n'.join(self.desc)
400
401desc = description()
402
403def single_recurse(tree):
404 """
405 look for nested trees whose leaf only has a single element
406 """
407 return False
408
409def maker_in_order(in_order, printer):
410 debug( "IN_ORDER ", is_tree(in_order), in_order )
411 if is_list(in_order):
412 was_dict = False
413 desc.out('# %d items in order' % len(in_order) , printer)
414 desc.out('# %s ' % printer , printer)
415 if printer.is_dict():
416 printer.to_tuple()
417 desc.out("(", printer)
418 printer.more_indent()
419 was_dict = True
420 do_optional = False
421 if printer.is_optional:
422 if not was_dict:
423 desc.out("(", printer)
424 printer.more_indent()
425 do_optional = True
426 save_need_optional = printer.need_optional
427 printer.need_optional = True
428 printer.is_optional = False
429
430 for (n, tree) in enumerate(in_order, 1):
431 debug( "IN_ORDER ITEM ", n, is_token(tree), tree)
432 desc.out('# item %d %s' % (n, printer) , printer)
433 maker_items(tree, printer)
434
435 if was_dict or do_optional:
436 printer.less_indent()
437 desc.out("),", printer)
438 if was_dict:
439 printer.to_dict()
440 if do_optional:
441 printer.is_optional = True
442 printer.need_optional = save_need_optional
443
444 elif is_tree(in_order):
445 was_dict = False
446 if printer.is_dict():
447 desc.out("(", printer) # )(
448 desc.out("#in_order2", printer)
449 traceback.print_stack()
450 printer.to_tuple()
451 printer.more_indent()
452 was_dict = True
453 debug( "IN_ORDER TREE ", token_of(in_order) )
454 maker_do_op(token_of(in_order), printer)
455 if was_dict:
456 printer.less_indent()
457 desc.out("),", printer)
458 printer.to_dict()
459 elif is_token(in_order):
460 maker_items(in_order, printer.nest(incr = 1))
461 elif is_field(in_order):
462 maker_field(field_list_of(in_order), printer.nest(incr = 1))
463 else:
464 error( "IN_ORDER STUCK" )
465
466def maker_field(field_list, printer):
467 was_tuple = False
468 if printer.is_tuple:
469 was_tuple = True
470 desc.out("{", printer)
471 printer.more_indent()
472 printer.to_tuple()
473
474 if printer.need_optional:
475 desc.out("'optional' : %s," % printer.is_optional, printer)
476
477 for field in field_list:
478 # Add more items here to provide more field decoration
479 printer.more_indent
480 value = token_of(field)
481 if value.find('=') == -1:
482 desc.out("'field' : '%s'," % value, printer)
483 else:
484 desc.out("'%s' : '%s'," % tuple(value.split('=')), printer)
485 printer.less_indent
486
487 if was_tuple:
488 printer.less_indent()
489 desc.out( "},", printer )
490 printer.to_dict()
491
492def maker_choice(tree_tuple, printer):
493 debug( 'MAKER_CHOICE', tree_tuple, printer.indent )
494
495 if is_tree(tree_tuple):
496 # XXX some tree's can be squashed.
497 debug( "MAKER_CHOICE ITEM ", tree_tuple )
498 maker_do_op(token_of(tree_tuple), printer)
499 return
500
501 # choice needs to print a dictionary.
502 was_tuple = False
503 if printer.is_tuple():
504 printer.to_dict()
505 was_tuple = True
506
507 desc.out('{', printer)
508 printer.more_indent()
509
510 if printer.is_optional:
511 desc.out("'optional': %s," % printer.is_optional, printer)
512 desc.out("'choices' : (", printer)
513 desc.out(" # maker_choice", printer)
514
515 if is_list(tree_tuple):
516 debug( "CHOICE LIST", len(tree_tuple), tree_tuple )
517 printer.more_indent()
518 for (n, item) in enumerate(tree_tuple, 1):
519 debug( "CHOICE LIst #%d" % n )
520 debug( " ITEM ", item )
521 maker_items(item, printer)
522 printer.less_indent()
523 elif is_tree(tree_tuple):
524 debug( "CHOICE TREE" )
525 (tree_which, tree) = tree_tuple[1]
526 # tree_which == 'tree'
527 maker_do_op(token_of(tree_tuple), printer.nest(form = 'tuple', incr = 1))
528 elif is_field(tree_tuple):
529 debug( "CHOICE FIELD", tree_tuple[1] )
530 printer.more_indent()
531 maker_field(field_list_of(tree_tuple), printer)
532 printer.less_indent()
533 else:
534 error( 'MAKER_CHOICE CONFUSED' )
535
536 desc.out(")", printer)
537 printer.less_indent()
538 desc.out('},', printer)
539
540 if was_tuple:
541 printer.to_tuple()
542
543
544def maker_do_op(op_tuple, printer):
545 debug( 'OP=> ', op_tuple )
546 (op, operands) = op_tuple
547 debug( 'OP ', op_tuple, op, operands )
548 if op == 'ORDER':
549 debug( "OP IN_ORDER ", operands )
550 maker_in_order(operands, printer)
551 elif op == 'EITHER':
552 # XXX wrong
553 maker_choice(operands, printer)
554 elif op.startswith('CHOICE'):
555 maker_choice(operands, printer)
556 elif op.startswith('OPTION'):
557 was_optional = printer.is_optional
558 printer.is_optional = True
559 debug( 'OP OPTIONAL', operands )
560 maker_items(operands, printer)
561 printer.is_optional = was_optional
562
563
564def maker_trees(trees, printer):
565 (tree_which, op) = trees
566 maker_do_op(op, printer)
567
568
569def maker_items(items, printer):
570 debug( "ITEMS-> ", type(items), items )
571 if type(items) == list:
572 for item in items:
573 if is_tree(item):
574 debug( "TREE ", item )
575 maker_do_op(item, printer)
576 elif is_field(item):
577 desc.out("{", printer)
578 maker_field(field_list_of(item), printer)
579 if printer.need_optional:
580 desc.out("'optional' : %s," % printer.is_optional, printer)
581 desc.out( "},", printer)
582 elif is_token(item):
583 desc.out("{", printer)
584 printer.more_indent()
585 desc.out("'token' : '%s'," % token_of(item), printer)
586 if printer.need_optional:
587 desc.out("'optional' : %s," % printer.is_optional, printer)
588 printer.less_indent()
589 desc.out("},", printer)
590 elif is_tree(items):
591 maker_do_op(token_of(items), printer)
592 elif is_field(items):
593 maker_field(field_list_of(items), printer)
594 elif is_token(items):
595 debug( 'ITEMS TOKEN', items )
596 desc.out( "{", printer)
597 printer.more_indent()
598 desc.out( "'token' : '%s'," % token_of(items), printer)
599 if printer.need_optional:
600 desc.out( "'optional' : %s," % printer.is_optional, printer)
601 printer.less_indent()
602 desc.out( "},", printer)
603 else:
604 error( "ITEMS> STUCK", items )
605
606
607#
608def maker_args(args, printer):
609 debug( "MAKER_ARGS ", args )
610 (pick, item) = args
611 if pick == 'EITHER':
612 debug( "MAKER_ARGS EITHER ", item )
613 if len(item) >= 1:
614 desc.raw_out( ' args : {')
615 if all_tokens(item):
616 for choice in item:
617 desc.raw_out(" '%s'," % token_of(choice, 3))
618 else:
619 maker_trees(item, printer)
620 desc.raw_out( ' },' )
621 else: # exactly one choice
622 desc.raw_out( ' args : { ' )
623 if all_token(item):
624 desc.raw_out(" '%s'," % token_of(item[0]))
625 else:
626 print maker_trees(item, printer)
627 desc.raw_out( ' },' )
628 elif pick.startswith('CHOICE'):
629 debug( "CHOICE", len(item) )
630 if len(item) == 1:
631 maker_args(item)
632 elif is_tree(item) and token_of(item)[0] == 'EITHER':
633 maker_choice(token_of(item), printer)
634 elif is_field(item) or is_token(item):
635 maker_choice(item, printer)
636 else:
637 error( "CHOICE HELP ", item )
638 elif pick.startswith('ORDER'):
639 # ought to choose the form of the printer based on item
640 desc.out("'args' : {", printer)
641 printer.less_indent()
642 maker_in_order(item, printer)
643 printer.less_indent()
644 desc.out('}', printer)
645 elif pick.startswith('OPTION'):
646 printer.is_optional = True
647 desc.raw_out( ' args : {')
648 maker_trees(item, printer)
649 desc.raw_out( ' },')
650 else:
651 error( "MAKER_PICKER HELP ", pick )
652
653saved_input = []
654
655#
656def maker(name_tuple, no_supported, result, printer):
657
658 name = token_of(name_tuple)
659 verbose( 'Name: %s no %s Result %s' % (name, no_supported, result) )
660
661 type = 'add-command-type'
662 mode = 'login'
663 new_mode = 'config-CHANGE'
664 obj_type = None
665 #
666 # command-name@command-type@command-mode@obj-type@new_mode
667 #
668 if name.find('@') >= 0:
669 name_parts = name.split('@')
670 name = name_parts[0]
671 type = name_parts[1]
672 if len(name_parts) > 2:
673 mode = name_parts[2]
674 if len(name_parts) > 3:
675 obj_type = name_parts[3]
676 if len(name_parts) > 4:
677 new_mode = name_parts[4]
678 # name-value pairs?
679
680 debug( "NAME ", (name) )
681 debug( 'command name ', name )
682 desc.raw_out( '%s_%s_COMMAND_DESCRIPTION = {' %
683 (args.n.upper(), name.replace("-","_").upper()))
684 desc.raw_out( " 'name' : '%s'," % name)
685 desc.raw_out( " 'mode' : '%s'," % mode)
686 if no_supported == False:
687 desc.raw_out( " 'no-supported' : False,")
688 desc.raw_out( " 'command-type' : '%s'," % type)
689 if obj_type:
690 desc.raw_out( " 'obj-type' : '%s'," % obj_type)
691 if type == 'config-submode':
692 desc.raw_out( " 'submode-name' : '%s'," % new_mode)
693 desc.raw_out( " 'parent-id' : None,")
694 desc.raw_out( " 'short-help' : 'add-short-command-help',")
695 if args.f:
696 desc.raw_out( " 'feature' : '%s'," % args.f)
697 # if the remaining length is two, ORDER should be popped.
698 desc.raw_out( " 'args' : ( ")
699 maker_items(result, printer)
700 desc.raw_out( " ),")
701 desc.raw_out( "}")
702 return
703
704 if len(order_list) == 2 and len(order) == 1:
705 build_choice = None
706 if is_tree(order_list[1]):
707 debug( "MAKER TREE.", token_of(order_list[1]) )
708 if token_of(order_list[1])[0].startswith('CHOICE'):
709 choice = token_of(order_list[1])
710 build_choice = order_list[1]
711 if build_choice:
712 desc.out("'args' : {", printer)
713 printer.more_indent()
714 maker_choice(build_choice, printer)
715 printer.less_indent()
716 desc.out( '},', printer)
717 else:
718 print "XXX", order_list
719 print "XXX", order_list[1]
720 desc.out("'args' : (", printer)
721 printer.more_indent()
722 printer.to_tuple()
723 maker_in_order(order_list[1], printer)
724 printer.less_indent()
725 desc.out( '),', printer)
726 elif len(order) > 1:
727 maker_args((order[0], order[1:]), printer)
728 else:
729 desc.raw_out('}')
730
731
732parser = argparse.ArgumentParser(prog='desc_maker')
733parser.add_argument('file')
734parser.add_argument('-p')
735parser.add_argument('-d')
736parser.add_argument('-v')
737parser.add_argument('-n', default = "NEW")
738parser.add_argument('-f')
739args = parser.parse_args()
740
741for line in fileinput.input(args.file):
742
743 line_tokens = tokens(line)
744 if len(line_tokens) < 2: # why 2? even blank lines get eol
745 saved_input.append( "# @ %s" % line)
746 continue
747 #
748 # remove '[' 'no' ']' if its there, don't forget the leading '^'
749 no_supported = False
750
751 if len(line_tokens) > 1:
752 if token_of(line_tokens[0]) == '#':
753 saved_input.append( "# @ %s" % line)
754 continue
755 elif token_of(line_tokens[0]) == '[':
756 if len(line_tokens) > 2:
757 if token_of(line_tokens[1]) == 'no':
758 if len(line_tokens) > 3:
759 if token_of(line_tokens[2]) == ']':
760 if len(line_tokens) > 4:
761 no_supported = True
762 name = line_tokens[3]
763 parse_tokens = line_tokens[4:]
764 else:
765 print 'Warning: name required after \[ no \']'
766 continue
767 else:
768 print 'Warning: only \'\[ no \]\' allowed as prefix'
769 continue
770 else:
771 print 'Warning: only \'\[ no \]\' allowed as prefix'
772 continue
773 else:
774 print 'Warning: only single \[ in line'
775 continue
776 else:
777 name = line_tokens[0]
778 parse_tokens = line_tokens[1:]
779
780 saved_input.append( "# @ %s" % line)
781 single_line([front_token] + parse_tokens)
782
783 # should look like ^ tree $
784 if len(stack) == 3 and not is_op(stack[1]):
785 debug( "OK------------------ -> ", token_of(stack[1]) )
786 a_printer = printer('tuple', is_optional = False)
787 desc.out('\n#\n# %s#\n' % line, a_printer)
788 maker(name, no_supported, stack[1], a_printer)
789 else:
790 #
791 # Could peek at the stack to get an idea of the nature of the syntax error
792 print "SYNTAX ERROR", name, stack
793
794#
795#
796
797print "#"
798print "# Command used as input for this run are listed below,"
799print "# Fish the command out by egreping '^# @' "
800print "#"
801
802print ''.join(saved_input)
803print desc.result()