import ast, _ast, sys, collections sys.path.append('..') from runac import parser TITLE = 'Language grammar' PARSER_FILE = '../runac/parser.py' INTRO = '''The table below (which is generated from the parser's source code) can serve as a guide to Runa's grammar. Code literals in rules represent regular expressions. The special INDENT and DEDENT tokens are inserted by a secondary pass, after the initial tokenization of source code; they represent the increase and decrease of the indentation level.''' def get_rules(): with open(PARSER_FILE) as f: src = f.read() rules = collections.OrderedDict() for node in ast.parse(src).body: if not isinstance(node, _ast.FunctionDef): continue if not node.decorator_list: continue assert len(node.decorator_list) == 1 decorator = node.decorator_list[0] if not isinstance(decorator, _ast.Call): continue func = decorator.func if not isinstance(func, _ast.Attribute): continue assert func.attr == 'production' ln = decorator.args[0].s name, match = ln.split(' : ', 1) rules.setdefault(name, []).append(tuple(match.split())) return rules def get_tokens(): tokens = {'INDENT': 'INDENT', 'DEDENT': 'DEDENT'} for rule in parser.LEXER.rules: name, pattern = rule.name, rule.re.pattern tokens[rule.name] = rule.re.pattern for word in parser.NAME_LIKE: tokens[word.upper()] = word return tokens def main(): rules, tokens = get_rules(), get_tokens() lines, columns = [], [0, 0] for name, expands in rules.iteritems(): columns[0] = max(columns[0], len(name)) for i, expand in enumerate(expands): bits = list(expand) for idx, s in enumerate(expand): if s.upper() == s: bits[idx] = '``' + tokens[s] + '``' defn = ' '.join(bits) columns[1] = max(columns[1], len(defn)) lines.append((name if not i else '', defn)) separator = ''.join(( '+', '-' * (columns[0] + 2), '+', '-' * (columns[1] + 2), '+', )) fmt = '| %%-%is | %%-%is |' % tuple(columns) print '*' * len(TITLE) print TITLE print '*' * len(TITLE) print print INTRO print for i, ln in enumerate(lines): print separator print fmt % ln print separator if __name__ == '__main__': main()