OILS / frontend / consts_gen.py View on Github | oilshell.org

624 lines, 321 significant
1#!/usr/bin/env python2
2# Copyright 2016 Andy Chu. All rights reserved.
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8"""
9consts_gen.py - Code generation for consts.py, id_kind_def.py, etc.
10"""
11from __future__ import print_function
12
13import collections
14import os
15import sys
16
17from asdl import gen_cpp
18from mycpp.mylib import log
19from frontend import id_kind_def
20from frontend import builtin_def
21from frontend import option_def
22
23
24def _CreateModule(id_spec, ids):
25 """Create a SYNTHETIC ASDL module to generate code from."""
26 from asdl import ast
27
28 id_variants = [ast.Constructor(name) for name, _ in ids]
29 id_sum = ast.SimpleSum(id_variants,
30 generate=['integers', 'no_namespace_suffix'])
31
32 kind_variants = [ast.Constructor(name) for name in id_spec.kind_name_list]
33 kind_sum = ast.SimpleSum(kind_variants, generate=['no_namespace_suffix'])
34
35 id_ = ast.TypeDecl('Id', id_sum)
36 kind_ = ast.TypeDecl('Kind', kind_sum)
37
38 schema_ast = ast.Module('id_kind', [], [id_, kind_])
39 return schema_ast
40
41
42_BUILTINS = builtin_def.All()
43
44
45def GenBuiltinLookup(func_name, kind, f):
46 #log('%r %r', func_name, kind)
47
48 pairs = [(b.name, b.index) for b in _BUILTINS if b.kind == kind]
49
50 GenStringLookup('builtin_t', func_name, pairs, f)
51
52
53def GenStringLookup(type_name, func_name, pairs, f):
54 #log('%s', pairs)
55
56 groups = collections.defaultdict(list)
57 for name, index in pairs:
58 first_char = name[0]
59 groups[first_char].append((name, index))
60
61 if 0:
62 for first_char, pairs in groups.iteritems():
63 log('%s %d', first_char, len(pairs))
64 log('%s', pairs)
65
66 # Note: we could optimize the length check, e.g. have a second level
67 # switch. But we would need to measure the difference. Caching the id on
68 # AST nodes is probably a bigger win, e.g. for loops.
69 #
70 # Size optimization: don't repeat constants literally?
71
72 f.write("""\
73%s %s(BigStr* s) {
74 int length = len(s);
75 if (length == 0) return 0; // consts.NO_INDEX
76
77 const char* data = s->data_;
78 switch (data[0]) {
79""" % (type_name, func_name))
80
81 for first_char in sorted(groups):
82 pairs = groups[first_char]
83 f.write(" case '%s':\n" % first_char)
84 for name, index in pairs:
85 # NOTE: we have to check the length because they're not NUL-terminated
86 f.write('''\
87 if (length == %d && memcmp("%s", data, %d) == 0) return %d;
88''' % (len(name), name, len(name), index))
89 f.write(' break;\n')
90
91 f.write("""\
92 }
93
94 return 0; // consts.NO_INDEX
95}
96
97""")
98
99
100def GenIntStrLookup(func_name, int2str, f):
101 # NOTE: quoting doesn't work, strings must be Identifier Names here
102
103 for i in sorted(int2str):
104 s = int2str[i]
105 f.write('GLOBAL_STR(k%s_%d, "%s");\n' % (func_name, i, s))
106
107 f.write("""\
108
109BigStr* %s(int i) {
110 switch (i) {
111""" % func_name)
112
113 for i in sorted(int2str):
114 s = int2str[i]
115 f.write(' case %d:\n' % i)
116 f.write(' return k%s_%d;\n' % (func_name, i))
117 f.write(' break;\n')
118 f.write("""\
119 default:
120 FAIL(kShouldNotGetHere);
121 }
122}
123
124""")
125
126
127def GenStringMembership(func_name, strs, f):
128 groups = collections.defaultdict(list)
129 for s in strs:
130 first_char = s[0]
131 groups[first_char].append(s)
132
133 f.write("""\
134bool %s(BigStr* s) {
135 int length = len(s);
136 if (length == 0) return false;
137
138 const char* data = s->data_;
139 switch (data[0]) {
140""" % func_name)
141
142 for first_char in sorted(groups):
143 strs = groups[first_char]
144 f.write(" case '%s':\n" % first_char)
145 for s in strs:
146 # NOTE: we have to check the length because they're not NUL-terminated
147 f.write('''\
148 if (length == %d && memcmp("%s", data, %d) == 0) return true;
149''' % (len(s), s, len(s)))
150 f.write(' break;\n')
151
152 f.write("""\
153 }
154
155 return false;
156}
157
158""")
159
160
161C_CHAR = {
162 # '\'' is a single quote in C
163 "'": "\\'",
164 '"': '\\"',
165 '\\': "\\\\",
166 '\t': '\\t',
167 '\r': '\\r',
168 '\n': '\\n',
169 '\v': '\\v',
170 '\0': '\\0',
171 '\a': '\\a',
172 '\b': '\\b',
173 '\f': '\\f',
174 '\x1b': '\\x1b',
175}
176
177
178def CChar(c):
179 return C_CHAR.get(c, c)
180
181
182def GenCharLookup(func_name, lookup, f, required=False):
183 f.write("""\
184BigStr* %s(BigStr* c) {
185 assert(len(c) == 1);
186
187 char ch = c->data_[0];
188
189 // TODO-intern: return value
190 switch (ch) {
191""" % func_name)
192
193 for char_code in sorted(lookup):
194 f.write(" case '%s':\n" % CChar(char_code))
195 f.write(' return StrFromC("%s", 1);\n' % CChar(lookup[char_code]))
196 f.write(" break;\n")
197
198 f.write(" default:\n")
199 if required:
200 f.write(" assert(0);\n")
201 else:
202 f.write(" return nullptr;\n")
203
204 f.write("""
205 }
206}
207""")
208
209
210def GenStrList(l, name, out):
211 element_globals = []
212 for i, elem in enumerate(l):
213 global_name = "k%s_%d" % (name, i)
214 out('GLOBAL_STR(%s, "%s");', global_name, elem)
215 element_globals.append(global_name)
216
217 lit = ' COMMA '.join(element_globals)
218 out('GLOBAL_LIST(%s, BigStr*, %d, {%s});\n', name, len(l), lit)
219
220
221def main(argv):
222 try:
223 action = argv[1]
224 except IndexError:
225 raise RuntimeError('Action required')
226
227 # TODO: Remove duplication in core/meta.py
228 ID_TO_KIND = {}
229 BOOL_ARG_TYPES = {}
230 TEST_UNARY_LOOKUP = {}
231 TEST_BINARY_LOOKUP = {}
232 TEST_OTHER_LOOKUP = {}
233
234 ID_SPEC = id_kind_def.IdSpec(ID_TO_KIND, BOOL_ARG_TYPES)
235
236 id_kind_def.AddKinds(ID_SPEC)
237 id_kind_def.AddBoolKinds(ID_SPEC) # must come second
238
239 id_kind_def.SetupTestBuiltin(ID_SPEC, TEST_UNARY_LOOKUP,
240 TEST_BINARY_LOOKUP, TEST_OTHER_LOOKUP)
241
242 ids = ID_SPEC.id_str2int.items()
243 ids.sort(key=lambda pair: pair[1]) # Sort by ID
244
245 if action == 'c':
246 for name, id_int in ids:
247 print('#define id__%s %s' % (name, id_int))
248
249 elif action == 'cpp':
250 schema_ast = _CreateModule(ID_SPEC, ids)
251
252 out_prefix = argv[2]
253
254 with open(out_prefix + '.h', 'w') as f:
255 f.write("""\
256#ifndef ID_KIND_ASDL_H
257#define ID_KIND_ASDL_H
258
259class BigStr;
260
261namespace id_kind_asdl {
262
263#define ASDL_NAMES struct
264""")
265
266 v = gen_cpp.ClassDefVisitor(f)
267 v.VisitModule(schema_ast)
268
269 f.write("""
270} // namespace id_kind_asdl
271
272#endif // ID_KIND_ASDL_H
273""")
274
275 with open(out_prefix + '.cc', 'w') as f:
276 f.write("""\
277#include <assert.h>
278#include "_gen/frontend/id_kind.asdl.h"
279#include "mycpp/gc_alloc.h" // StrFromC()
280
281namespace id_kind_asdl {
282
283""")
284
285 v = gen_cpp.MethodDefVisitor(f)
286
287 v.VisitModule(schema_ast)
288
289 f.write('} // namespace id_kind_asdl\n')
290
291 elif action == 'mypy':
292 from asdl import gen_python
293
294 schema_ast = _CreateModule(ID_SPEC, ids)
295 #print(schema_ast)
296
297 f = sys.stdout
298
299 f.write("""\
300from asdl import pybase
301
302""")
303 # Minor style issue: we want Id and Kind, not Id_e and Kind_e
304 v = gen_python.GenMyPyVisitor(f)
305 v.VisitModule(schema_ast)
306
307 elif action == 'cpp-consts':
308
309 # Break circular deps
310
311 from core import pyutil
312 from frontend import consts
313 from _devbuild.gen.id_kind_asdl import Id_str, Kind_str
314 from _devbuild.gen.types_asdl import redir_arg_type_str, bool_arg_type_str
315
316 LIST_INT = [
317 'STRICT_ALL',
318 'YSH_UPGRADE',
319 'YSH_ALL',
320 'DEFAULT_TRUE',
321 'PARSE_OPTION_NUMS',
322 'SHOPT_OPTION_NUMS',
323 'SET_OPTION_NUMS',
324 'VISIBLE_SHOPT_NUMS',
325 ]
326
327 prefix = argv[2]
328
329 with open(prefix + '.h', 'w') as f:
330
331 def out(fmt, *args):
332 print(fmt % args, file=f)
333
334 out("""\
335#ifndef CONSTS_H
336#define CONSTS_H
337
338#include "mycpp/runtime.h"
339
340#include "_gen/frontend/id_kind.asdl.h"
341#include "_gen/frontend/option.asdl.h"
342#include "_gen/core/runtime.asdl.h"
343#include "_gen/frontend/types.asdl.h"
344
345namespace consts {
346""")
347
348 for name in LIST_INT:
349 out('extern List<int>* %s;', name)
350
351 out('extern List<BigStr*>* BUILTIN_NAMES;')
352 out('extern List<BigStr*>* OSH_KEYWORD_NAMES;')
353 out('extern List<BigStr*>* SET_OPTION_NAMES;')
354 out('extern List<BigStr*>* SHOPT_OPTION_NAMES;')
355
356 out("""\
357
358extern int NO_INDEX;
359
360extern BigStr* gVersion;
361
362int RedirDefaultFd(id_kind_asdl::Id_t id);
363types_asdl::redir_arg_type_t RedirArgType(id_kind_asdl::Id_t id);
364types_asdl::bool_arg_type_t BoolArgType(id_kind_asdl::Id_t id);
365id_kind_asdl::Kind GetKind(id_kind_asdl::Id_t id);
366
367types_asdl::opt_group_t OptionGroupNum(BigStr* s);
368option_asdl::option_t OptionNum(BigStr* s);
369option_asdl::builtin_t LookupNormalBuiltin(BigStr* s);
370option_asdl::builtin_t LookupAssignBuiltin(BigStr* s);
371option_asdl::builtin_t LookupSpecialBuiltin(BigStr* s);
372bool IsControlFlow(BigStr* s);
373BigStr* ControlFlowName(int i);
374bool IsKeyword(BigStr* s);
375BigStr* LookupCharC(BigStr* c);
376BigStr* LookupCharPrompt(BigStr* c);
377
378BigStr* OptionName(option_asdl::option_t opt_num);
379
380Tuple2<runtime_asdl::state_t, runtime_asdl::emit_t> IfsEdge(runtime_asdl::state_t state, runtime_asdl::char_kind_t ch);
381
382} // namespace consts
383
384#endif // CONSTS_H
385""")
386
387 with open(prefix + '.cc', 'w') as f:
388
389 def out(fmt, *args):
390 print(fmt % args, file=f)
391
392 out("""\
393#include "_gen/frontend/consts.h"
394
395using id_kind_asdl::Id;
396using id_kind_asdl::Kind;
397using types_asdl::redir_arg_type_e;
398using types_asdl::bool_arg_type_e;
399using option_asdl::builtin_t;
400
401namespace consts {
402
403int NO_INDEX = 0; // duplicated from frontend/consts.py
404""")
405
406 # Generate gVersion, which is read by pyutil::GetVersion()
407 this_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
408 root_dir = os.path.join(this_dir, '..') # ~/git/oilshell/oil
409 loader = pyutil._FileResourceLoader(root_dir)
410
411 version_str = pyutil.GetVersion(loader)
412 out('GLOBAL_STR(gVersion, "%s");' % version_str)
413 out('')
414
415 # Note: could use opt_num:: instead of raw ints
416 for name in LIST_INT:
417 val = getattr(consts, name)
418 val_str = ' COMMA '.join(str(i) for i in val)
419 out('GLOBAL_LIST(%s, int, %d, {%s});', name, len(val), val_str)
420
421 out("""\
422
423int RedirDefaultFd(id_kind_asdl::Id_t id) {
424 // relies on "switch lowering"
425 switch (id) {
426""")
427 for id_ in sorted(consts.REDIR_DEFAULT_FD):
428 a = Id_str(id_).replace('.', '::')
429 b = consts.REDIR_DEFAULT_FD[id_]
430 out(' case %s: return %s;' % (a, b))
431 out("""\
432 }
433 FAIL(kShouldNotGetHere);
434}
435""")
436
437 out("""\
438types_asdl::redir_arg_type_t RedirArgType(id_kind_asdl::Id_t id) {
439 // relies on "switch lowering"
440 switch (id) {
441""")
442 for id_ in sorted(consts.REDIR_ARG_TYPES):
443 a = Id_str(id_).replace('.', '::')
444 # redir_arg_type_e::Path, etc.
445 b = redir_arg_type_str(consts.REDIR_ARG_TYPES[id_]).replace(
446 '.', '_e::')
447 out(' case %s: return %s;' % (a, b))
448 out("""\
449 }
450 FAIL(kShouldNotGetHere);
451}
452""")
453
454 out("""\
455types_asdl::bool_arg_type_t BoolArgType(id_kind_asdl::Id_t id) {
456 // relies on "switch lowering"
457 switch (id) {
458""")
459 for id_ in sorted(BOOL_ARG_TYPES):
460 a = Id_str(id_).replace('.', '::')
461 # bool_arg_type_e::BigStr, etc.
462 b = bool_arg_type_str(BOOL_ARG_TYPES[id_]).replace('.', '_e::')
463 out(' case %s: return %s;' % (a, b))
464 out("""\
465 }
466 FAIL(kShouldNotGetHere);
467}
468""")
469
470 out("""\
471Kind GetKind(id_kind_asdl::Id_t id) {
472 // relies on "switch lowering"
473 switch (id) {
474""")
475 for id_ in sorted(ID_TO_KIND):
476 a = Id_str(id_).replace('.', '::')
477 b = Kind_str(ID_TO_KIND[id_]).replace('.', '::')
478 out(' case %s: return %s;' % (a, b))
479 out("""\
480 }
481 FAIL(kShouldNotGetHere);
482}
483""")
484
485 pairs = consts.OPTION_GROUPS.items()
486 GenStringLookup('types_asdl::opt_group_t', 'OptionGroupNum', pairs,
487 f)
488
489 pairs = [(opt.name, opt.index) for opt in option_def.All()]
490 GenStringLookup('option_asdl::option_t', 'OptionNum', pairs, f)
491
492 GenBuiltinLookup('LookupNormalBuiltin', 'normal', f)
493 GenBuiltinLookup('LookupAssignBuiltin', 'assign', f)
494 GenBuiltinLookup('LookupSpecialBuiltin', 'special', f)
495
496 GenStringMembership('IsControlFlow', consts._CONTROL_FLOW_NAMES, f)
497 GenIntStrLookup('ControlFlowName', consts._CONTROL_FLOW_LOOKUP, f)
498
499 GenStringMembership('IsKeyword', consts.OSH_KEYWORD_NAMES, f)
500
501 GenCharLookup('LookupCharC', consts._ONE_CHAR_C, f, required=True)
502 GenCharLookup('LookupCharPrompt', consts._ONE_CHAR_PROMPT, f)
503
504 opt_int2str = {}
505 for opt in option_def.All():
506 opt_int2str[opt.index] = opt.name
507 GenIntStrLookup('OptionName', opt_int2str, f)
508
509 #
510 # Generate a tightly packed 2D array for C, from a Python dict.
511 #
512
513 edges = consts._IFS_EDGES
514 max_state = max(edge[0] for edge in edges)
515 max_char_kind = max(edge[1] for edge in edges)
516
517 edge_array = []
518 for i in xrange(max_state + 1):
519 # unused cells get -1
520 edge_array.append(['-1'] * (max_char_kind + 1))
521
522 for i in xrange(max_state + 1):
523 for j in xrange(max_char_kind + 1):
524 entry = edges.get((i, j))
525 if entry is not None:
526 # pack (new_state, action) into 32 bits
527 edge_array[i][j] = '(%d<<16)|%d' % entry
528
529 parts = []
530 for i in xrange(max_state + 1):
531 parts.append(' {')
532 parts.append(', '.join('%10s' % cell
533 for cell in edge_array[i]))
534 parts.append(' },\n')
535
536 out("""\
537int _IFS_EDGE[%d][%d] = {
538%s
539};
540""" % (max_state + 1, max_char_kind + 1, ''.join(parts)))
541
542 out("""\
543// Note: all of these are integers, e.g. state_i, emit_i, char_kind_i
544using runtime_asdl::state_t;
545using runtime_asdl::emit_t;
546using runtime_asdl::char_kind_t;
547
548Tuple2<state_t, emit_t> IfsEdge(state_t state, runtime_asdl::char_kind_t ch) {
549 int cell = _IFS_EDGE[state][ch];
550 state_t new_state = cell >> 16;
551 emit_t emit = cell & 0xFFFF;
552 return Tuple2<state_t, emit_t>(new_state, emit);
553}
554""")
555
556 GenStrList(consts.BUILTIN_NAMES, 'BUILTIN_NAMES', out)
557 GenStrList(consts.OSH_KEYWORD_NAMES, 'OSH_KEYWORD_NAMES', out)
558 GenStrList(consts.SET_OPTION_NAMES, 'SET_OPTION_NAMES', out)
559 GenStrList(consts.SHOPT_OPTION_NAMES, 'SHOPT_OPTION_NAMES', out)
560
561 out("""\
562} // namespace consts
563""")
564
565 elif action == 'py-consts':
566 # It's kind of weird to use the generated code to generate more code.
567 # Can we do this instead with the parsed module for "id" and "types.asdl"?
568
569 from frontend import consts
570 from _devbuild.gen.id_kind_asdl import Id_str, Kind_str
571 from _devbuild.gen.types_asdl import redir_arg_type_str, bool_arg_type_str
572
573 print("""
574from _devbuild.gen.id_kind_asdl import Id, Kind
575from _devbuild.gen.types_asdl import redir_arg_type_e, bool_arg_type_e
576""")
577
578 print('')
579 print('BOOL_ARG_TYPES = {')
580 for id_ in sorted(BOOL_ARG_TYPES):
581 v = BOOL_ARG_TYPES[id_]
582 # HACK
583 v = bool_arg_type_str(v).replace('.', '_e.')
584 print(' %s: %s,' % (Id_str(id_), v))
585 print('}')
586
587 print('')
588 print('TEST_UNARY_LOOKUP = {')
589 for op_str in sorted(TEST_UNARY_LOOKUP):
590 v = Id_str(TEST_UNARY_LOOKUP[op_str])
591 print(' %r: %s,' % (op_str, v))
592 print('}')
593
594 print('')
595 print('TEST_BINARY_LOOKUP = {')
596 for op_str in sorted(TEST_BINARY_LOOKUP):
597 v = Id_str(TEST_BINARY_LOOKUP[op_str])
598 print(' %r: %s,' % (op_str, v))
599 print('}')
600
601 print('')
602 print('TEST_OTHER_LOOKUP = {')
603 for op_str in sorted(TEST_OTHER_LOOKUP):
604 v = Id_str(TEST_OTHER_LOOKUP[op_str])
605 print(' %r: %s,' % (op_str, v))
606 print('}')
607
608 print('')
609 print('ID_TO_KIND = {')
610 for id_ in sorted(ID_TO_KIND):
611 v = Kind_str(ID_TO_KIND[id_])
612 print(' %s: %s,' % (Id_str(id_), v))
613 print('}')
614
615 else:
616 raise RuntimeError('Invalid action %r' % action)
617
618
619if __name__ == '__main__':
620 try:
621 main(sys.argv)
622 except RuntimeError as e:
623 print('FATAL: %s' % e, file=sys.stderr)
624 sys.exit(1)