1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
"""generate_js_parser_tables.py - Generate tables from the ES grammar."""
import argparse
import os
import jsparagus.gen
import jsparagus.grammar
from . import load_es_grammar
def hack_grammar(g):
# We throw away most of the boolean parameters in the grammar, as the
# current parser generator's approach of fully expanding them is a huge
# pain.
PARAM_WHITELIST = ['In', 'Default']
def filter_params(params):
return tuple(p for p in params if p in PARAM_WHITELIST)
def filter_args(args):
return tuple(pair for pair in args if pair[0] in PARAM_WHITELIST)
def filter_element(e):
""" Strip nt arguments. """
if isinstance(e, jsparagus.grammar.Nt):
return jsparagus.grammar.Nt(e.name, filter_args(e.args))
elif isinstance(e, jsparagus.grammar.Optional):
return jsparagus.grammar.Optional(filter_element(e.inner))
else:
return e
def filter_condition(c):
if c is None or c[0] not in PARAM_WHITELIST:
return None
return c
def filter_production(p):
""" Discard production conditions and nt arguments. """
body = [filter_element(e) for e in p.body]
return jsparagus.grammar.Production(body, p.reducer,
condition=filter_condition(p.condition))
nonterminals = {}
for nt, nt_def in g.nonterminals.items():
params = tuple(filter_params(nt_def.params))
rhs_list = [filter_production(p) for p in nt_def.rhs_list]
nonterminals[nt] = jsparagus.grammar.NtDef(params, rhs_list, nt_def.type)
return g.with_nonterminals(nonterminals)
def main():
# Read command-line options.
parser = argparse.ArgumentParser(
description='Ponder the ECMAScript grammar.',
allow_abbrev=False)
default_filename = os.path.join(os.path.dirname(__file__),
"es-simplified.esgrammar")
parser.add_argument(
'filename', metavar='FILE', nargs='?', default=default_filename,
help=".esgrammar (or .jsparagus_dump) input file")
parser.add_argument(
'handler_info', metavar='HANDLER_INFO', nargs='?',
help="JSON file that contains information about handler")
parser.add_argument(
'-e', '--extend', action='append', default=[],
help="name of a files which contains a grammar_extension Rust macro.")
parser.add_argument(
'-o', '--output', metavar='FILE', default='/dev/stdout',
help="output filename for parser tables")
parser.add_argument(
'-v', '--verbose', action='store_true',
help="print some debug output")
parser.add_argument(
'--progress', action='store_true',
help="print a dot each time a state is analyzed (thousands of them)")
parser.add_argument(
'--debug', action='store_true',
help="annotate the generated code with grammar productions")
args = parser.parse_args()
# Check filenames.
in_filename = args.filename
if in_filename.endswith('.esgrammar'):
from_source = True
elif in_filename.endswith('.jsparagus_dump'):
from_source = False
else:
raise ValueError("input file extension should be .esgrammar or .jsparagus_dump")
out_filename = args.output
if out_filename.endswith('.py'):
target = 'python'
elif out_filename.endswith('.rs'):
target = 'rust'
elif out_filename.endswith('.jsparagus_dump'):
target = 'dump'
else:
raise ValueError("-o file extension should be .py, .rs, or .jsparagus_dump")
in_extend = args.extend
if from_source:
assert all(f.endswith('.rs') for f in in_extend), "Extension are only supposed to be Rust files."
else:
assert in_extend == [], "Cannot add extensions to the generated parse table."
# Load input and analyze it.
if from_source:
grammar = load_es_grammar.load_syntactic_grammar(in_filename, in_extend)
grammar = hack_grammar(grammar)
if args.verbose:
grammar.dump()
states = jsparagus.gen.generate_parser_states(
grammar, verbose=args.verbose, progress=args.progress)
else:
states = jsparagus.gen.ParseTable.load(in_filename)
# Generate output.
try:
if target in ('python', 'rust'):
with open(out_filename, 'w') as f:
jsparagus.gen.generate_parser(f, states,
target=target,
verbose=args.verbose,
debug=args.debug,
handler_info=args.handler_info)
else:
assert target == 'dump'
states.save(out_filename)
except Exception:
# On failure, don't leave a partial output file lying around.
try:
os.remove(out_filename)
except Exception:
pass
raise
if __name__ == '__main__':
main()
|