Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/python-wasm
Path: blob/main/python/pylang/src/parse.py
1396 views
1
# vim:fileencoding=utf-8
2
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
# globals: readfile
4
from __python__ import hash_literals # type: ignore
5
6
from utils import make_predicate, array_to_hash, defaults, has_prop, cache_file_name
7
from errors import SyntaxError, ImportError
8
from ast_types import (
9
AST_Array, AST_Assign, AST_Binary, AST_BlockStatement, AST_Break, AST_Call,
10
AST_Catch, AST_Class, AST_ClassCall, AST_Conditional, AST_Constant,
11
AST_Continue, AST_DWLoop, AST_Debugger, AST_Decorator, AST_Definitions,
12
AST_DictComprehension, AST_Directive, AST_Do, AST_Dot, AST_EllipsesRange,
13
AST_Else, AST_EmptyStatement, AST_Except, AST_ExpressiveObject, AST_False,
14
AST_Finally, AST_ForIn, AST_ForJS, AST_Function,
15
AST_GeneratorComprehension, AST_Hole, AST_If, AST_Import, AST_ImportedVar,
16
AST_Imports, AST_ListComprehension, AST_Method, AST_New, AST_Null,
17
AST_Number, AST_Object, AST_ObjectKeyVal, AST_PropAccess, AST_RegExp,
18
AST_Return, AST_Scope, AST_Set, AST_SetComprehension, AST_SetItem, AST_Seq,
19
AST_SimpleStatement, AST_Splice, AST_String, AST_Sub, AST_ItemAccess,
20
AST_SymbolAlias, AST_SymbolCatch, AST_SymbolDefun, AST_SymbolFunarg,
21
AST_SymbolLambda, AST_SymbolNonlocal, AST_SymbolRef, AST_SymbolVar,
22
AST_This, AST_Throw, AST_Toplevel, AST_True, AST_Try, AST_UnaryPrefix,
23
AST_Undefined, AST_Var, AST_VarDef, AST_Verbatim, AST_While, AST_With,
24
AST_WithClause, AST_Yield, AST_Assert, AST_Existential, is_node_type)
25
from tokenizer import tokenizer, is_token, RESERVED_WORDS
26
from js import js_new
27
28
COMPILER_VERSION = '__COMPILER_VERSION__'
29
PYTHON_FLAGS = {
30
'exponent':
31
True, # support a^b-->a**b (and a^^b = a xor b), which is very math friendly (no performance impact)
32
'ellipses':
33
True, # support the [a..b] = range(a, b+1) notation, which is very math friendly (no performance impact)
34
'numbers':
35
True, # numbers are arbitrary precision (potentially massive performance impact and breakage!)
36
'annotations':
37
False, # if true, set function annotations; off by default since breaks mypy
38
'dict_literals':
39
True, # MASSIVELY performance impact! -- e.g., 100x -- careful
40
'overload_getitem': True,
41
'bound_methods': True,
42
'hash_literals': True
43
}
44
45
46
def get_compiler_version():
47
return COMPILER_VERSION
48
49
50
def static_predicate(names):
51
return {k: True for k in names.split(' ')}
52
53
54
NATIVE_CLASSES = {
55
'Image': {},
56
'FileReader': {},
57
'RegExp': {},
58
'Error': {},
59
'EvalError': {},
60
'InternalError': {},
61
'RangeError': {},
62
'RumtimeError': {},
63
'ReferenceError': {},
64
'SyntaxError': {},
65
'TypeError': {},
66
'URIError': {},
67
'Object': {
68
'static':
69
static_predicate(
70
'getOwnPropertyNames getOwnPropertyDescriptor getOwnPropertyDescriptors'
71
' getOwnPropertySymbols keys entries values create defineProperty'
72
' defineProperties getPrototypeOf setPrototypeOf assign'
73
' seal isSealed is preventExtensions isExtensible'
74
' freeze isFrozen')
75
},
76
'String': {
77
'static': static_predicate("fromCharCode")
78
},
79
'Array': {
80
'static': static_predicate("isArray from of")
81
},
82
'Function': {},
83
'Date': {
84
'static': static_predicate("UTC now parse")
85
},
86
'ArrayBuffer': {
87
'static': static_predicate('isView transfer')
88
},
89
'DataView': {},
90
'Float32Array': {},
91
'Float64Array': {},
92
'Int16Array': {},
93
'Int32Array': {},
94
'Int8Array': {},
95
'Uint16Array': {},
96
'Uint32Array': {},
97
'Uint8Array': {},
98
'Uint8ClampedArray': {},
99
'Map': {},
100
'WeakMap': {},
101
'Proxy': {},
102
'Set': {},
103
'WeakSet': {},
104
'Promise': {
105
'static': static_predicate('all race reject resolve')
106
},
107
'WebSocket': {},
108
'XMLHttpRequest': {},
109
'TextEncoder': {},
110
'TextDecoder': {},
111
'MouseEvent': {},
112
'Event': {},
113
'CustomEvent': {},
114
'Blob': {},
115
}
116
ERROR_CLASSES = {
117
'Exception': {},
118
'AttributeError': {},
119
'IndexError': {},
120
'KeyError': {},
121
'ValueError': {},
122
'UnicodeDecodeError': {},
123
'AssertionError': {},
124
'ZeroDivisionError': {},
125
}
126
COMMON_STATIC = static_predicate('call apply bind toString')
127
FORBIDDEN_CLASS_VARS = 'prototype constructor'.split(' ')
128
129
# -----[ Parser (constants) ]-----
130
UNARY_PREFIX = make_predicate('typeof void delete ~ - + ! @')
131
132
ASSIGNMENT = make_predicate('= += -= /= //= *= %= >>= <<= >>>= |= ^= &=')
133
134
135
def operator_to_precedence(a):
136
"""
137
Compute map from operator to its precendence number.
138
"""
139
op_to_prec = {}
140
for i in range(a.length):
141
b = a[i]
142
for j in range(b.length):
143
op_to_prec[b[j]] = i + 1
144
return op_to_prec
145
146
147
PRECEDENCE = operator_to_precedence([
148
# lowest precedence
149
["||"],
150
["&&"],
151
["|"],
152
["^"],
153
["&"],
154
["==", "===", "!=", "!=="],
155
["<", ">", "<=", ">=", "in", "nin", "instanceof"],
156
[">>", "<<", ">>>"],
157
["+", "-"],
158
["*", "/", "//", "%"],
159
["**"]
160
# highest precedence
161
])
162
163
STATEMENTS_WITH_LABELS = array_to_hash(["for", "do", "while", "switch"])
164
165
ATOMIC_START_TOKEN = array_to_hash(
166
["atom", "num", "string", "regexp", "name", "js"])
167
168
compile_time_decorators = ['staticmethod', 'external', 'property']
169
170
171
def has_simple_decorator(decorators, name):
172
remove = []
173
for i in range(decorators.length):
174
s = decorators[i]
175
if is_node_type(s, AST_SymbolRef) and not s.parens and s.name is name:
176
remove.push(i)
177
if remove.length:
178
remove.reverse()
179
for i in range(remove.length):
180
decorators.splice(remove[i], 1)
181
return True
182
return False
183
184
185
def has_setter_decorator(decorators, name):
186
remove = []
187
for i in range(decorators.length):
188
s = decorators[i]
189
if is_node_type(s, AST_Dot) and is_node_type(
190
s.expression, AST_SymbolRef
191
) and s.expression.name is name and s.property is 'setter':
192
remove.push(i)
193
if remove.length:
194
remove.reverse()
195
for i in range(remove.length):
196
decorators.splice(remove[i], 1)
197
return True
198
return False
199
200
201
# -----[ Parser ]-----
202
def create_parser_ctx(S, import_dirs, module_id, baselib_items,
203
imported_module_ids, imported_modules, importing_modules,
204
options):
205
def next():
206
S.prev = S.token
207
if S.peeked.length:
208
S.token = S.peeked.shift()
209
else:
210
S.token = S.input()
211
212
if options.tokens:
213
print("token", S.token.type, S.token.value)
214
return S.token
215
216
def is_(type, value):
217
return is_token(S.token, type, value)
218
219
def peek():
220
if not S.peeked.length:
221
S.peeked.push(S.input())
222
return S.peeked[0]
223
224
def prev():
225
return S.prev
226
227
def croak(msg, line, col, pos, is_eof):
228
# note: undefined means nothing was passed in, None/null means a null value was passed in
229
ctx = S.input.context()
230
raise SyntaxError(msg, ctx.filename,
231
(line if line is not undefined else ctx.tokline),
232
(col if col is not undefined else ctx.tokcol),
233
(pos if pos is not undefined else ctx.tokpos),
234
is_eof)
235
236
def token_error(token, msg):
237
is_eof = token.type is 'eof'
238
croak(msg, token.line, token.col, undefined, is_eof)
239
240
def unexpected(token):
241
if token is undefined:
242
token = S.token
243
if token.type is 'operator' and token.value == '^^':
244
croak(
245
"Use 'from __python__ import exponent' to support the a^^b is a xor b and a^b is a**b"
246
)
247
token_error(
248
token,
249
"Unexpected token: " + token.type + " '" + token.value + "'")
250
251
def expect_token(type, val):
252
if is_(type, val):
253
return next()
254
token_error(
255
S.token, "Unexpected token: found type='" + S.token.type +
256
"', value='" + S.token.value + "'" + "; expected: '" + type +
257
"', value='" + val + "'")
258
259
def expect(punc):
260
return expect_token("punc", punc)
261
262
def semicolon():
263
if is_("punc", ";"):
264
next()
265
S.token.nlb = True
266
267
def embed_tokens(parser):
268
def with_embedded_tokens():
269
start = S.token
270
expr = parser()
271
if expr is undefined:
272
unexpected()
273
end = prev()
274
expr.start = start
275
expr.end = end
276
return expr
277
278
return with_embedded_tokens
279
280
def scan_for_top_level_callables(body):
281
ans = r'%js []'
282
# Get the named functions and classes
283
if Array.isArray(body):
284
for obj in body:
285
if is_node_type(obj, AST_Function) or is_node_type(
286
obj, AST_Class):
287
if obj.name:
288
ans.push(obj.name.name)
289
else:
290
token_error(obj.start,
291
"Top-level functions must have names")
292
else:
293
# skip inner scopes
294
if is_node_type(obj, AST_Scope):
295
continue
296
for x in ['body', 'alternative']:
297
opt = obj[x]
298
if opt:
299
ans = ans.concat(scan_for_top_level_callables(opt))
300
301
if is_node_type(opt, AST_Assign) and not (is_node_type(
302
opt.right, AST_Scope)):
303
ans = ans.concat(
304
scan_for_top_level_callables(opt.right))
305
306
elif body.body:
307
# recursive descent into wrapper statements that contain body blocks
308
ans = ans.concat(scan_for_top_level_callables(body.body))
309
if body.alternative:
310
ans = ans.concat(scan_for_top_level_callables(
311
body.alternative))
312
313
return ans
314
315
def scan_for_classes(body):
316
ans = {}
317
for obj in body:
318
if is_node_type(obj, AST_Class):
319
ans[obj.name.name] = obj
320
return ans
321
322
def scan_for_local_vars(body):
323
"""
324
Pick out all variables being assigned to from within this scope, we'll mark them as local
325
326
body body to be scanned
327
"""
328
localvars = r'%js []'
329
seen = {}
330
331
def push(x):
332
if has_prop(seen, x):
333
return
334
seen[x] = True
335
localvars.push(x)
336
337
def extend(arr):
338
for x in arr:
339
push(x)
340
341
def scan_in_array(arr):
342
for x in arr:
343
if is_node_type(x, AST_Seq):
344
x = x.to_array()
345
elif is_node_type(x, AST_Array):
346
x = x.elements
347
if Array.isArray(x):
348
scan_in_array(x)
349
else:
350
if not is_node_type(x, AST_PropAccess):
351
push(x.name)
352
353
def add_assign_lhs(lhs):
354
if is_node_type(lhs, AST_Seq):
355
lhs = AST_Array({'elements': lhs.to_array()})
356
if is_node_type(lhs, AST_Array):
357
# assignment to an implicit tuple
358
push("ρσ_unpack")
359
scan_in_array(lhs.elements)
360
elif lhs.name:
361
# assignment to a single variable
362
push(lhs.name)
363
364
def add_for_in(stmt):
365
if is_node_type(stmt.init, AST_Array):
366
# iteration via implicit tuple
367
push("ρσ_unpack")
368
scan_in_array(stmt.init.elements)
369
else:
370
# iteration via a single variable
371
push(stmt.init.name)
372
373
if Array.isArray(body):
374
# this is a body of statements
375
for stmt in body:
376
# skip inner scopes
377
if is_node_type(stmt, AST_Scope):
378
continue
379
380
# recursive descent into conditional, loop and exception bodies
381
for option in ('body', 'alternative', 'bcatch', 'condition'):
382
opt = stmt[option]
383
if opt:
384
extend(scan_for_local_vars(opt))
385
386
if is_node_type(opt, AST_Assign) and not (is_node_type(
387
opt.right, AST_Scope)):
388
extend(scan_for_local_vars(opt.right))
389
390
# pick up iterators from loops
391
if is_node_type(stmt, AST_ForIn):
392
add_for_in(stmt)
393
elif is_node_type(stmt, AST_DWLoop):
394
extend(scan_for_local_vars(stmt))
395
elif is_node_type(stmt, AST_With):
396
push('ρσ_with_exception'), push('ρσ_with_suppress')
397
for clause in stmt.clauses:
398
if clause.alias:
399
push(clause.alias.name)
400
401
elif body.body:
402
# recursive descent into wrapper statements that contain body blocks
403
extend(scan_for_local_vars(body.body))
404
if body.alternative:
405
extend(scan_for_local_vars(body.alternative))
406
407
elif is_node_type(body, AST_Assign):
408
# this is a single assignment operator
409
if body.is_chained():
410
is_compound_assign = False
411
for lhs in body.traverse_chain()[0]:
412
add_assign_lhs(lhs)
413
if is_node_type(lhs, AST_Seq) or is_node_type(
414
lhs, AST_Array):
415
is_compound_assign = True
416
break
417
if is_compound_assign:
418
push('ρσ_chain_assign_temp')
419
else:
420
add_assign_lhs(body.left)
421
if not is_node_type(body.right, AST_Scope):
422
extend(scan_for_local_vars(body.right))
423
424
elif is_node_type(body, AST_ForIn):
425
add_for_in(body)
426
427
return localvars
428
429
def scan_for_nonlocal_defs(body):
430
vars = r'%js []'
431
if Array.isArray(body):
432
for stmt in body:
433
if is_node_type(stmt, AST_Scope):
434
continue
435
436
# don't invade nested scopes
437
if is_node_type(stmt, AST_Definitions):
438
for vardef in stmt.definitions:
439
vars.push(vardef.name.name)
440
441
for option in ('body', 'alternative'):
442
nonlocal vars
443
opt = stmt[option]
444
if opt:
445
vars = vars.concat(scan_for_nonlocal_defs(opt))
446
447
elif body.body:
448
vars = vars.concat(scan_for_nonlocal_defs(body.body))
449
if body.alternative:
450
vars = vars.concat(scan_for_nonlocal_defs(body.alternative))
451
452
return vars
453
454
def return_():
455
if is_('punc', ';'):
456
semicolon()
457
value = None
458
else:
459
is_end_of_statement = S.token.nlb or is_("eof") or is_("punc", "}")
460
if is_end_of_statement:
461
value = None
462
else:
463
value = expression(True)
464
semicolon()
465
return value
466
467
@embed_tokens
468
def statement():
469
# From Kovid: The next three lines were a hack to try to support statements
470
# starting with a regexp literal. However, it did not work, for example:
471
# echo 'f=1\n/asd/.test()' | rs -> parse error
472
# So we just accept that this cannot be supported in RS, and avoid hacks that mess
473
# with the internal state of S. In any case,
474
# statements starting with a literal are very rare.
475
if S.token.type is 'operator' and S.token.value.substr(0, 1) is '/':
476
token_error(
477
S.token,
478
'RapydScript does not support statements starting with regexp literals'
479
)
480
481
S.statement_starting_token = S.token
482
tmp_ = S.token.type
483
p = prev()
484
if p and not S.token.nlb and ATOMIC_START_TOKEN[p.type] and not is_(
485
'punc', ':') and not is_('punc', ';'):
486
unexpected()
487
if tmp_ is "string":
488
return simple_statement()
489
elif tmp_ is "shebang":
490
tmp_ = S.token.value
491
next()
492
return AST_Directive({'value': tmp_})
493
elif tmp_ is "num" or tmp_ is "regexp" or tmp_ is "operator" or tmp_ is "atom" or tmp_ is "js":
494
return simple_statement()
495
elif tmp_ is "punc":
496
tmp_ = S.token.value
497
if tmp_ is ":":
498
return AST_BlockStatement({
499
'start': S.token,
500
'body': block_(),
501
'end': prev()
502
})
503
elif tmp_ is "{" or tmp_ is "[" or tmp_ is "(":
504
return simple_statement()
505
elif tmp_ is ";":
506
next()
507
return AST_EmptyStatement({
508
'stype': ';',
509
'start': prev(),
510
'end': prev()
511
})
512
else:
513
unexpected()
514
elif tmp_ is "name":
515
if is_token(peek(), 'punc', ':'):
516
token_error(peek(), 'invalid syntax, colon not allowed here')
517
return simple_statement()
518
elif tmp_ is "keyword":
519
tmp_ = S.token.value
520
next()
521
if tmp_ is "break":
522
return break_cont(AST_Break)
523
elif tmp_ is "continue":
524
return break_cont(AST_Continue)
525
elif tmp_ is "debugger":
526
semicolon()
527
return AST_Debugger()
528
elif tmp_ is "do":
529
530
def get_condition():
531
expect(".")
532
expect_token("keyword", "while")
533
tmp = expression(True)
534
if is_node_type(tmp, AST_Assign):
535
croak(
536
'Assignments in do loop conditions are not allowed'
537
)
538
semicolon()
539
return tmp
540
541
return AST_Do({
542
'body': in_loop(statement),
543
'condition': get_condition()
544
})
545
elif tmp_ is "while":
546
while_cond = expression(True)
547
if is_node_type(while_cond, AST_Assign):
548
croak(
549
'Assignments in while loop conditions are not allowed')
550
if not is_('punc', ':'):
551
croak('Expected a colon after the while statement')
552
return AST_While({
553
'condition': while_cond,
554
'body': in_loop(statement)
555
})
556
elif tmp_ is "for":
557
if is_('js'):
558
return for_js()
559
return for_()
560
elif tmp_ is "from":
561
return import_(True)
562
elif tmp_ is "import":
563
return import_(False)
564
elif tmp_ is "class":
565
return class_()
566
elif tmp_ is "def":
567
start = prev()
568
func = function_(S.in_class[-1], False, False)
569
func.start = start
570
func.end = prev()
571
chain = subscripts(func, True)
572
if chain is func:
573
return func
574
else:
575
return AST_SimpleStatement({
576
'start': start,
577
'body': chain,
578
'end': prev()
579
})
580
elif tmp_ is 'assert':
581
start = prev()
582
cond = expression(False)
583
msg = None
584
if is_('punc', ','):
585
next()
586
msg = expression(False)
587
return AST_Assert({
588
'start': start,
589
'condition': cond,
590
'message': msg,
591
'end': prev()
592
})
593
elif tmp_ is "if":
594
return if_()
595
elif tmp_ is "pass":
596
semicolon()
597
return AST_EmptyStatement({
598
'stype': 'pass',
599
'start': prev(),
600
'end': prev()
601
})
602
elif tmp_ is "return":
603
if S.in_function is 0:
604
croak("'return' outside of function")
605
if S.functions[-1].is_generator:
606
croak("'return' not allowed in a function with yield")
607
S.functions[-1].is_generator = False
608
609
return AST_Return({'value': return_()})
610
elif tmp_ is "yield":
611
return yield_()
612
elif tmp_ is "raise":
613
if S.token.nlb:
614
return AST_Throw(
615
{'value': AST_SymbolCatch({'name': "ρσ_Exception"})})
616
617
tmp = expression(True)
618
semicolon()
619
return AST_Throw({'value': tmp})
620
elif tmp_ is "try":
621
return try_()
622
elif tmp_ is "nonlocal":
623
tmp = nonlocal_()
624
semicolon()
625
return tmp
626
elif tmp_ is 'global':
627
tmp = nonlocal_(True)
628
semicolon()
629
return tmp
630
elif tmp_ is "with":
631
return with_()
632
else:
633
unexpected()
634
635
def with_():
636
clauses = r'%js []'
637
start = S.token
638
while True:
639
if is_('eof'):
640
unexpected()
641
expr = expression()
642
alias = None
643
if is_('keyword', 'as'):
644
next()
645
alias = as_symbol(AST_SymbolAlias)
646
clauses.push(AST_WithClause({'expression': expr, 'alias': alias}))
647
if is_('punc', ','):
648
next()
649
continue
650
if not is_('punc', ':'):
651
unexpected()
652
break
653
654
if not clauses.length:
655
token_error(start, 'with statement must have at least one clause')
656
body = statement()
657
658
return AST_With({'clauses': clauses, 'body': body})
659
660
def simple_statement(tmp):
661
tmp = expression(True)
662
semicolon()
663
return AST_SimpleStatement({'body': tmp})
664
665
def break_cont(t):
666
if S.in_loop is 0:
667
croak(t.name.slice(4) + " not inside a loop or switch")
668
semicolon()
669
return js_new(t)
670
671
def yield_():
672
if S.in_function is 0:
673
croak("'yield' outside of function")
674
if S.functions[-1].is_generator is False:
675
croak("'yield' not allowed in a function with return")
676
S.functions[-1].is_generator = True
677
is_yield_from = is_('keyword', 'from')
678
if is_yield_from:
679
next()
680
return AST_Yield({'is_yield_from': is_yield_from, 'value': return_()})
681
682
def for_(list_comp):
683
# expect("(")
684
init = None
685
if not is_("punc", ";"):
686
init = expression(True, True)
687
# standardize AST_Seq into array now for consistency
688
if is_node_type(init, AST_Seq):
689
if is_node_type(init.car, AST_SymbolRef) and is_node_type(
690
init.cdr, AST_SymbolRef):
691
# Optimization to prevent runtime call to ρσ_flatten when init is simply (a, b)
692
tmp = init.to_array()
693
else:
694
tmp = [init]
695
init = AST_Array({
696
'start': init.start,
697
'elements': tmp,
698
'end': init.end
699
})
700
701
if is_("operator", "in"):
702
if is_node_type(init, AST_Var) and init.definitions.length > 1:
703
croak(
704
"Only one variable declaration allowed in for..in loop"
705
)
706
next()
707
return for_in(init, list_comp)
708
709
unexpected()
710
711
def for_in(init, list_comp):
712
lhs = init.definitions[0].name if is_node_type(init, AST_Var) else None
713
obj = expression(True)
714
# expect(")")
715
if list_comp:
716
return {'init': init, 'name': lhs, 'object': obj}
717
718
return AST_ForIn({
719
'init': init,
720
'name': lhs,
721
'object': obj,
722
'body': in_loop(statement)
723
})
724
725
# A native JavaScript for loop - for v"var i=0; i<5000; i++":
726
def for_js():
727
condition = as_atom_node()
728
return AST_ForJS({'condition': condition, 'body': in_loop(statement)})
729
730
# scan function/class body for nested class declarations
731
def get_class_in_scope(expr):
732
# TODO: Currently if a local variable shadows a class name defined in
733
# an outerscope, the logic below will identify that variable as a
734
# class. This bug was always present. Fixing it will require the parser
735
# to maintain a list of local variables for every AST_Scope and provide
736
# an easy way to walk the ast tree upwards.
737
if is_node_type(expr, AST_SymbolRef):
738
# check Native JS classes
739
if has_prop(NATIVE_CLASSES, expr.name):
740
return NATIVE_CLASSES[expr.name]
741
if has_prop(ERROR_CLASSES, expr.name):
742
return ERROR_CLASSES[expr.name]
743
744
# traverse in reverse to check local variables first
745
for s in range(S.classes.length - 1, -1, -1):
746
if has_prop(S.classes[s], expr.name):
747
return S.classes[s][expr.name]
748
749
elif is_node_type(expr, AST_Dot):
750
referenced_path = []
751
# this one is for detecting classes inside modules and eventually nested classes
752
while is_node_type(expr, AST_Dot):
753
referenced_path.unshift(expr.property)
754
expr = expr.expression
755
if is_node_type(expr, AST_SymbolRef):
756
referenced_path.unshift(expr.name)
757
# now 'referenced_path' should contain the full path of potential class
758
if len(referenced_path) > 1:
759
class_name = referenced_path.join('.')
760
for s in range(S.classes.length - 1, -1, -1):
761
if has_prop(S.classes[s], class_name):
762
return S.classes[s][class_name]
763
return False
764
765
def import_error(message):
766
ctx = S.input.context()
767
raise ImportError(message, ctx.filename, ctx.tokline, ctx.tokcol,
768
ctx.tokpos)
769
770
def do_import(key):
771
if has_prop(imported_modules, key):
772
return
773
if has_prop(importing_modules, key) and importing_modules[key]:
774
import_error('Detected a recursive import of: ' + key +
775
' while importing: ' + module_id)
776
777
# Ensure that the package containing this module is also imported
778
package_module_id = key.split('.')[:-1].join('.')
779
if len(package_module_id) > 0:
780
do_import(package_module_id)
781
782
if options.for_linting:
783
imported_modules[key] = {
784
'is_cached': True,
785
'classes': {},
786
'module_id': key,
787
'exports': [],
788
'nonlocalvars': [],
789
'baselib': {},
790
'outputs': {},
791
'discard_asserts': options.discard_asserts
792
}
793
return
794
795
def safe_read(base_path):
796
# Attention: the length of this list is hardcoded in two ifs below!
797
for i, path in enumerate(
798
[base_path + '.py', base_path + '/__init__.py']):
799
try:
800
return [readfile(path, "utf-8"), path] # noqa:undef
801
except:
802
if i is 1:
803
return None, None
804
805
src_code = filename = None
806
modpath = key.replace(r"%js /\./g", '/')
807
808
for location in import_dirs:
809
if location:
810
data, filename = safe_read(location + '/' + modpath)
811
if data is not None:
812
src_code = data
813
break
814
if src_code is None:
815
import_error(
816
"Failed Import: '" + key +
817
"' module doesn't exist in any of the import directories: " +
818
import_dirs.join(':'))
819
820
try:
821
cached = JSON.parse(
822
readfile(cache_file_name(filename, options.module_cache_dir),
823
'utf-8'))
824
except:
825
cached = None
826
827
srchash = sha1sum(src_code) # noqa:undef
828
if cached and cached.version is COMPILER_VERSION and cached.signature is srchash and cached.discard_asserts is r'%js !!options.discard_asserts':
829
for ikey in cached.imported_module_ids:
830
do_import(
831
ikey
832
) # Ensure all modules imported by the cached module are also imported
833
imported_modules[key] = {
834
'is_cached': True,
835
'classes': cached.classes,
836
'outputs': cached.outputs,
837
'module_id': key,
838
'import_order': Object.keys(imported_modules).length,
839
'nonlocalvars': cached.nonlocalvars,
840
'baselib': cached.baselib,
841
'exports': cached.exports,
842
'discard_asserts': options.discard_asserts,
843
'imported_module_ids': cached.imported_module_ids,
844
}
845
else:
846
parse(
847
src_code, {
848
'filename': filename,
849
'toplevel': None,
850
'basedir': options.basedir,
851
'libdir': options.libdir,
852
'import_dirs': options.import_dirs,
853
'module_id': key,
854
'imported_modules': imported_modules,
855
'importing_modules': importing_modules,
856
'discard_asserts': options.discard_asserts,
857
'module_cache_dir': options.module_cache_dir
858
}
859
) # This function will add the module to imported_modules itself
860
861
imported_modules[key].srchash = srchash
862
863
for bitem in Object.keys(imported_modules[key].baselib):
864
baselib_items[bitem] = True
865
866
def read_python_flags():
867
expect_token("keyword", "import")
868
bracketed = is_('punc', '(')
869
if bracketed:
870
next()
871
while True:
872
if not is_('name'):
873
croak('Name expected')
874
name = S.token.value
875
val = False if name.startsWith('no_') else True
876
if not val:
877
name = name.slice(3)
878
if not PYTHON_FLAGS:
879
croak('Unknown __python__ flag: ' + name)
880
if name == 'exponent':
881
S.scoped_flags.set('exponent', val)
882
S.input.context()['exponent'] = val # tell tokenizer
883
elif name == 'ellipses':
884
S.scoped_flags.set('ellipses', val)
885
elif name == 'annotations':
886
S.scoped_flags.set('annotations', val)
887
elif name == 'numbers':
888
S.scoped_flags.set('numbers', val)
889
else:
890
S.scoped_flags.set(name, val)
891
next()
892
if is_('punc', ','):
893
next()
894
else:
895
if bracketed:
896
if is_('punc', ')'):
897
next()
898
else:
899
continue
900
break
901
return AST_EmptyStatement({
902
'stype': 'scoped_flags',
903
'start': prev(),
904
'end': prev()
905
})
906
907
def mock_typing_module():
908
# This enables us to fully use mypy with pylang code.
909
# See test/typing_.py for an example.
910
expect_token("keyword", "import")
911
bracketed = is_('punc', '(')
912
if bracketed:
913
next()
914
while True:
915
if not is_('name'):
916
croak('Name expected')
917
name = S.token.value
918
next()
919
if is_('punc', ','):
920
next()
921
else:
922
if bracketed:
923
if is_('punc', ')'):
924
next()
925
else:
926
continue
927
break
928
return AST_EmptyStatement({'start': prev(), 'end': prev()})
929
930
def import_(from_import):
931
ans = AST_Imports({'imports': []})
932
while True:
933
tok = tmp = name = last_tok = expression(False)
934
key = ''
935
while is_node_type(tmp, AST_Dot):
936
key = "." + tmp.property + key
937
tmp = last_tok = tmp.expression
938
key = tmp.name + key
939
if from_import and key is '__python__':
940
return read_python_flags()
941
if from_import and key is 'typing':
942
return mock_typing_module()
943
alias = None
944
if not from_import and is_('keyword', 'as'):
945
next()
946
alias = as_symbol(AST_SymbolAlias)
947
948
def body():
949
return imported_modules[key]
950
951
aimp = AST_Import({
952
'module': name,
953
'key': key,
954
'alias': alias,
955
'argnames': None,
956
'body': body
957
})
958
aimp.start, aimp.end = tok.start, last_tok.end
959
ans.imports.push(aimp)
960
if from_import:
961
break
962
if is_('punc', ','):
963
next()
964
else:
965
break
966
967
for imp in ans['imports']:
968
do_import(imp.key)
969
if imported_module_ids.indexOf(imp.key) is -1:
970
imported_module_ids.push(imp.key)
971
classes = imported_modules[key].classes
972
if from_import:
973
expect_token("keyword", "import")
974
imp.argnames = argnames = []
975
bracketed = is_('punc', '(')
976
if bracketed:
977
next()
978
exports = {}
979
for symdef in imported_modules[key].exports:
980
exports[symdef.name] = True
981
while True:
982
aname = as_symbol(AST_ImportedVar)
983
if not options.for_linting and not has_prop(
984
exports, aname.name):
985
import_error('The symbol "' + aname.name +
986
'" is not exported from the module: ' +
987
key)
988
if is_('keyword', 'as'):
989
next()
990
aname.alias = as_symbol(AST_SymbolAlias)
991
argnames.push(aname)
992
if is_('punc', ','):
993
next()
994
else:
995
if bracketed:
996
if is_('punc', ')'):
997
next()
998
else:
999
continue
1000
break
1001
1002
# Put imported class names in the outermost scope
1003
for argvar in argnames:
1004
obj = classes[argvar.name]
1005
if obj:
1006
key = argvar.alias.name if argvar.alias else argvar.name
1007
S.classes[-1][key] = {
1008
"static": obj.static,
1009
'bound': obj.bound,
1010
'classvars': obj.classvars
1011
}
1012
else:
1013
for cname in Object.keys(classes):
1014
obj = classes[cname]
1015
key = imp.alias.name if imp.alias else imp.key
1016
S.classes[-1][key + '.' + obj.name.name] = {
1017
'static': obj.static,
1018
'bound': obj.bound,
1019
'classvars': obj.classvars
1020
}
1021
1022
return ans
1023
1024
def class_():
1025
name = as_symbol(AST_SymbolDefun)
1026
if not name:
1027
unexpected()
1028
1029
# detect external classes
1030
externaldecorator = has_simple_decorator(S.decorators, 'external')
1031
1032
class_details = {
1033
"static": {},
1034
'bound': r'%js []',
1035
'classvars': {},
1036
'processing': name.name,
1037
'provisional_classvars': {},
1038
}
1039
bases = r'%js []'
1040
class_parent = None
1041
1042
# read the bases of the class, if any
1043
if is_("punc", "("):
1044
S.in_parenthesized_expr = True
1045
next()
1046
while True:
1047
if is_('punc', ')'):
1048
S.in_parenthesized_expr = False
1049
next()
1050
break
1051
a = expr_atom(False)
1052
if class_parent is None:
1053
class_parent = a
1054
bases.push(a)
1055
if is_('punc', ','):
1056
next()
1057
continue
1058
1059
docstrings = r'%js []'
1060
1061
def decorators():
1062
d = []
1063
for decorator in S.decorators:
1064
d.push(AST_Decorator({'expression': decorator}))
1065
S.decorators = r'%js []'
1066
return d
1067
1068
def body(loop, labels):
1069
# navigate to correct location in the module tree and append the class
1070
S.in_class.push(name.name)
1071
S.classes[S.classes.length - 1][name.name] = class_details
1072
S.classes.push({})
1073
S.scoped_flags.push()
1074
S.in_function += 1
1075
S.in_loop = 0
1076
S.labels = []
1077
a = block_(docstrings)
1078
S.in_function -= 1
1079
S.scoped_flags.pop()
1080
S.classes.pop()
1081
S.in_class.pop()
1082
S.in_loop = loop
1083
S.labels = labels
1084
return a
1085
1086
definition = AST_Class({
1087
'name': name,
1088
'docstrings': docstrings,
1089
'module_id': module_id,
1090
'dynamic_properties': Object.create(None),
1091
'parent': class_parent,
1092
'bases': bases,
1093
'localvars': [],
1094
'classvars': class_details.classvars,
1095
'static': class_details.static,
1096
'external': externaldecorator,
1097
'bound': class_details.bound,
1098
'statements': [],
1099
'decorators': decorators(),
1100
'body': body(S.in_loop, S.labels)
1101
})
1102
class_details.processing = False
1103
# find the constructor
1104
for stmt in definition.body:
1105
if is_node_type(stmt, AST_Method):
1106
if stmt.is_getter or stmt.is_setter:
1107
descriptor = definition.dynamic_properties[stmt.name.name]
1108
if not descriptor:
1109
descriptor = definition.dynamic_properties[
1110
stmt.name.name] = {}
1111
descriptor['getter' if stmt.is_getter else 'setter'] = stmt
1112
elif stmt.name.name is "__init__":
1113
definition.init = stmt
1114
# find the class variables
1115
class_var_names = {}
1116
1117
# Ensure that if a class variable refers to another class variable in
1118
# its initialization, the referenced variables' names is correctly
1119
# mangled.
1120
def walker():
1121
def visit_node(node, descend):
1122
if is_node_type(node, AST_Method):
1123
class_var_names[node.name.name] = True
1124
return
1125
if is_node_type(node, AST_Function):
1126
return
1127
if is_node_type(node, AST_Assign) and is_node_type(
1128
node.left, AST_SymbolRef):
1129
varname = node.left.name
1130
if FORBIDDEN_CLASS_VARS.indexOf(varname) is not -1:
1131
token_error(
1132
node.left.start, varname +
1133
' is not allowed as a class variable name')
1134
class_var_names[varname] = True
1135
definition.classvars[varname] = True
1136
elif is_node_type(node, AST_SymbolRef) and has_prop(
1137
class_var_names, node.name):
1138
node.thedef = AST_SymbolDefun(
1139
{'name': name.name + '.prototype.' + node.name})
1140
if descend:
1141
descend.call(node)
1142
1143
this._visit = visit_node
1144
1145
visitor = js_new(walker)
1146
1147
for stmt in definition.body:
1148
if not is_node_type(stmt, AST_Class):
1149
stmt.walk(visitor)
1150
definition.statements.push(stmt)
1151
return definition
1152
1153
def function_(in_class, is_expression, is_lambda):
1154
if is_lambda:
1155
if in_class or not is_expression:
1156
# Note: is_lambda implies is_expression and not in_class.
1157
croak(
1158
'Compiler bug -- lambda must be an expression and not in a class'
1159
)
1160
# Lambda functions are always anonymous.
1161
is_anonymous = True
1162
name = None
1163
else:
1164
name = as_symbol(AST_SymbolDefun if in_class else AST_SymbolLambda
1165
) if is_('name') else None
1166
if in_class and not name:
1167
croak('Cannot use anonymous function as class methods')
1168
is_anonymous = not name
1169
1170
staticmethod = property_getter = property_setter = False
1171
if in_class:
1172
staticloc = has_simple_decorator(S.decorators, 'staticmethod')
1173
property_getter = has_simple_decorator(S.decorators, 'property')
1174
property_setter = has_setter_decorator(S.decorators, name.name)
1175
if staticloc:
1176
if property_getter or property_setter:
1177
croak(
1178
'A method cannot be both static and a property getter/setter'
1179
)
1180
S.classes[S.classes.length -
1181
2][in_class].static[name.name] = True
1182
staticmethod = True
1183
elif name.name is not "__init__" and S.scoped_flags.get(
1184
'bound_methods'):
1185
S.classes[S.classes.length - 2][in_class].bound.push(name.name)
1186
1187
if not is_lambda:
1188
expect("(")
1189
S.in_parenthesized_expr = True
1190
ctor = AST_Method if in_class else AST_Function
1191
return_annotation = None
1192
is_generator = r'%js []'
1193
docstrings = r'%js []'
1194
1195
def argnames():
1196
a = r'%js []'
1197
defaults = {}
1198
first = True
1199
seen_names = {}
1200
def_line = S.input.context().tokline
1201
current_arg_name = None
1202
name_token = None
1203
1204
def get_arg():
1205
nonlocal current_arg_name, name_token
1206
current_arg_name = S.token.value
1207
if has_prop(seen_names, current_arg_name):
1208
token_error(prev(), "Can't repeat parameter names")
1209
if current_arg_name is 'arguments':
1210
token_error(
1211
prev(),
1212
"Can't use the name arguments as a parameter name, it is reserved by JavaScript"
1213
)
1214
seen_names[current_arg_name] = True
1215
# save these in order to move back if we have an annotation
1216
name_token = S.token
1217
name_ctx = S.input.context()
1218
# check if we have an argument annotation
1219
ntok = peek()
1220
if ntok.type is 'punc' and ntok.value is ':' and not is_lambda:
1221
next()
1222
expect(':')
1223
annotation = maybe_conditional()
1224
1225
# and now, do as_symbol without the next() at the end
1226
# since we are already at the next comma (or end bracket)
1227
if not is_token(name_token, "name"):
1228
# assuming the previous context in case
1229
# the annotation was over the line
1230
croak("Name expected", name_ctx.tokline)
1231
return None
1232
1233
sym = AST_SymbolFunarg({
1234
'name': name_token.value,
1235
'start': S.token,
1236
'end': S.token,
1237
'annotation': annotation
1238
})
1239
return sym
1240
else:
1241
if not is_("name"):
1242
# there is no name, which is an error we should report on the
1243
# same line as the definition, so move to that is we're not already there.
1244
if S.input.context().tokline is not def_line:
1245
croak("Name expected", def_line)
1246
else:
1247
croak("Name expected")
1248
return None
1249
1250
sym = AST_SymbolFunarg({
1251
'name': current_arg_name,
1252
'start': S.token,
1253
'end': S.token,
1254
'annotation': None
1255
})
1256
next()
1257
return sym
1258
1259
end_punctuation = ':' if is_lambda else ')'
1260
while not is_("punc", end_punctuation):
1261
if first:
1262
first = False
1263
else:
1264
expect(",")
1265
if is_('punc', end_punctuation):
1266
break
1267
if is_('operator', '**'):
1268
# **kwargs
1269
next()
1270
if a.kwargs:
1271
token_error(
1272
name_token,
1273
"Can't define multiple **kwargs in function definition"
1274
)
1275
a.kwargs = get_arg()
1276
elif is_('operator', '*'):
1277
# *args
1278
next()
1279
if a.starargs:
1280
token_error(
1281
name_token,
1282
"Can't define multiple *args in function definition"
1283
)
1284
if a.kwargs:
1285
token_error(
1286
name_token,
1287
"Can't define *args after **kwargs in function definition"
1288
)
1289
a.starargs = get_arg()
1290
else:
1291
if a.starargs or a.kwargs:
1292
token_error(
1293
name_token,
1294
"Can't define a formal parameter after *args or **kwargs"
1295
)
1296
a.push(get_arg())
1297
if is_("operator", "="):
1298
if a.kwargs:
1299
token_error(
1300
name_token,
1301
"Can't define an optional formal parameter after **kwargs"
1302
)
1303
next()
1304
defaults[current_arg_name] = expression(False)
1305
a.has_defaults = True
1306
else:
1307
if a.has_defaults:
1308
token_error(
1309
name_token,
1310
"Can't define required formal parameters after optional formal parameters"
1311
)
1312
1313
next()
1314
1315
# Check if we have a return type annotation.
1316
# Note: lambda does not allow for type annotation:
1317
# https://stackoverflow.com/questions/33833881/is-it-possible-to-type-hint-a-lambda-function
1318
if not is_lambda and is_("punc", "->"):
1319
next()
1320
nonlocal return_annotation
1321
return_annotation = maybe_conditional()
1322
if not is_lambda:
1323
S.in_parenthesized_expr = False
1324
a.defaults = defaults
1325
a.is_simple_func = not a.starargs and not a.kwargs and not a.has_defaults
1326
return a
1327
1328
def decorators():
1329
d = r'%js []'
1330
for decorator in S.decorators:
1331
d.push(AST_Decorator({'expression': decorator}))
1332
S.decorators = r'%js []'
1333
return d
1334
1335
def body(loop, labels):
1336
S.in_class.push(False)
1337
S.classes.push({})
1338
S.scoped_flags.push()
1339
S.in_function += 1
1340
S.functions.push({})
1341
S.in_loop = 0
1342
S.labels = []
1343
if is_lambda:
1344
a = expression(False, True)
1345
else:
1346
a = block_(docstrings)
1347
S.in_function -= 1
1348
S.scoped_flags.pop()
1349
is_generator.push(bool(S.functions.pop().is_generator))
1350
S.classes.pop()
1351
S.in_class.pop()
1352
S.in_loop = loop
1353
S.labels = labels
1354
return a
1355
1356
args = {
1357
'name': name,
1358
'is_lambda': is_lambda,
1359
'is_expression': is_expression,
1360
'is_anonymous': is_anonymous,
1361
'annotations':
1362
S.scoped_flags.get('annotations'), # whether or not to annotate
1363
'argnames': argnames(),
1364
'localvars': [],
1365
'decorators': decorators(),
1366
'docstrings': docstrings,
1367
'body': body(S.in_loop, S.labels)
1368
}
1369
definition = js_new(ctor, args)
1370
definition.return_annotation = return_annotation
1371
definition.is_generator = is_generator[0]
1372
if is_node_type(definition, AST_Method):
1373
definition.static = staticmethod
1374
definition.is_getter = property_getter
1375
definition.is_setter = property_setter
1376
if definition.argnames.length < 1 and not definition.static:
1377
croak(
1378
'Methods of a class must have at least one argument, traditionally named self'
1379
)
1380
if definition.name and definition.name.name is '__init__':
1381
if definition.is_generator:
1382
croak(
1383
'The __init__ method of a class cannot be a generator (yield not allowed)'
1384
)
1385
if property_getter or property_setter:
1386
croak(
1387
'The __init__ method of a class cannot be a property getter/setter'
1388
)
1389
if definition.is_generator:
1390
baselib_items['yield'] = True
1391
1392
# detect local variables, strip function arguments
1393
assignments = scan_for_local_vars(definition.body)
1394
for i in range(assignments.length):
1395
for j in range(definition.argnames.length + 1):
1396
if j is definition.argnames.length:
1397
definition.localvars.push(
1398
new_symbol(AST_SymbolVar, assignments[i]))
1399
elif j < definition.argnames.length and assignments[
1400
i] is definition.argnames[j].name:
1401
break
1402
1403
nonlocals = scan_for_nonlocal_defs(definition.body)
1404
nonlocals = {name for name in nonlocals}
1405
1406
def does_not_have(v):
1407
return not nonlocals.has(v.name)
1408
1409
definition.localvars = definition.localvars.filter(does_not_have)
1410
return definition
1411
1412
def if_():
1413
cond = expression(True)
1414
body = statement()
1415
belse = None
1416
if is_("keyword", "elif") or is_("keyword", "else"):
1417
if is_("keyword", "else"):
1418
next()
1419
else:
1420
S.token.value = "if"
1421
# effectively converts 'elif' to 'else if'
1422
belse = statement()
1423
1424
return AST_If({'condition': cond, 'body': body, 'alternative': belse})
1425
1426
def is_docstring(stmt):
1427
if is_node_type(stmt, AST_SimpleStatement):
1428
if is_node_type(stmt.body, AST_String):
1429
return stmt.body
1430
return False
1431
1432
def block_(docstrings):
1433
prev_whitespace = S.token.leading_whitespace
1434
expect(":")
1435
a = r'%js []'
1436
if not S.token.nlb:
1437
while not S.token.nlb:
1438
if is_("eof"):
1439
unexpected()
1440
stmt = statement()
1441
if docstrings:
1442
ds = is_docstring(stmt)
1443
if ds:
1444
docstrings.push(ds)
1445
continue
1446
a.push(stmt)
1447
else:
1448
current_whitespace = S.token.leading_whitespace
1449
if current_whitespace.length is 0 or prev_whitespace is current_whitespace:
1450
croak('Expected an indented block')
1451
while not is_("punc", "}"):
1452
if is_("eof"):
1453
# end of file, terminate block automatically
1454
return a
1455
stmt = statement()
1456
if docstrings:
1457
ds = is_docstring(stmt)
1458
if ds:
1459
docstrings.push(ds)
1460
continue
1461
a.push(stmt)
1462
next()
1463
return a
1464
1465
def try_():
1466
body = block_()
1467
bcatch = r'%js []'
1468
bfinally = None
1469
belse = None
1470
while is_("keyword", "except"):
1471
start = S.token
1472
next()
1473
exceptions = []
1474
if not is_("punc", ":") and not is_("keyword", "as"):
1475
exceptions.push(as_symbol(AST_SymbolVar))
1476
while is_("punc", ","):
1477
next()
1478
exceptions.push(as_symbol(AST_SymbolVar))
1479
1480
name = None
1481
if is_("keyword", "as"):
1482
next()
1483
name = as_symbol(AST_SymbolCatch)
1484
1485
bcatch.push(
1486
AST_Except({
1487
'start': start,
1488
'argname': name,
1489
'errors': exceptions,
1490
'body': block_(),
1491
'end': prev()
1492
}))
1493
1494
if is_("keyword", "else"):
1495
start = S.token
1496
next()
1497
belse = AST_Else({'start': start, 'body': block_(), 'end': prev()})
1498
1499
if is_("keyword", "finally"):
1500
start = S.token
1501
next()
1502
bfinally = AST_Finally({
1503
'start': start,
1504
'body': block_(),
1505
'end': prev()
1506
})
1507
1508
if not bcatch.length and not bfinally:
1509
croak("Missing except/finally blocks")
1510
1511
return AST_Try({
1512
'body':
1513
body,
1514
'bcatch': (AST_Catch({'body': bcatch}) if bcatch.length else None),
1515
'bfinally':
1516
bfinally,
1517
'belse':
1518
belse
1519
})
1520
1521
def vardefs(symbol_class):
1522
a = []
1523
while True:
1524
a.push(
1525
AST_VarDef({
1526
'start':
1527
S.token,
1528
'name':
1529
as_symbol(symbol_class),
1530
'value':
1531
(next(),
1532
expression(False)) if is_('operator', '=') else None,
1533
'end':
1534
prev()
1535
}))
1536
if not is_("punc", ","):
1537
break
1538
next()
1539
1540
return a
1541
1542
def nonlocal_(is_global):
1543
defs = vardefs(AST_SymbolNonlocal)
1544
if is_global:
1545
for vardef in defs:
1546
S.globals.push(vardef.name.name)
1547
return AST_Var({'start': prev(), 'definitions': defs, 'end': prev()})
1548
1549
def new_():
1550
start = S.token
1551
expect_token("operator", "new")
1552
newexp = expr_atom(False)
1553
1554
if is_("punc", "("):
1555
S.in_parenthesized_expr = True
1556
next()
1557
args = func_call_list()
1558
S.in_parenthesized_expr = False
1559
else:
1560
args = func_call_list(True)
1561
return subscripts(
1562
AST_New({
1563
'start': start,
1564
'expression': newexp,
1565
'args': args,
1566
'end': prev()
1567
}), True)
1568
1569
def string_():
1570
strings = []
1571
start = S.token
1572
while True:
1573
strings.push(S.token.value)
1574
if peek().type is not 'string':
1575
break
1576
next()
1577
return AST_String({
1578
'start': start,
1579
'end': S.token,
1580
'value': strings.join('')
1581
})
1582
1583
def token_as_atom_node():
1584
tok = S.token
1585
tmp_ = tok.type
1586
if tmp_ is "name":
1587
return token_as_symbol(tok, AST_SymbolRef)
1588
elif tmp_ is "num":
1589
if not S.scoped_flags.get('numbers'):
1590
return AST_Number({
1591
'start': tok,
1592
'end': tok,
1593
'value': tok.value
1594
})
1595
return AST_Call({
1596
'expression':
1597
AST_SymbolRef({'name': 'Number'}),
1598
'args':
1599
[AST_String({
1600
'start': tok,
1601
'end': tok,
1602
'value': str(tok.value)
1603
})]
1604
})
1605
elif tmp_ is "string":
1606
return string_()
1607
elif tmp_ is "regexp":
1608
return AST_RegExp({'start': tok, 'end': tok, 'value': tok.value})
1609
elif tmp_ is "atom":
1610
tmp__ = tok.value
1611
if tmp__ is "False":
1612
return AST_False({'start': tok, 'end': tok})
1613
elif tmp__ is "True":
1614
return AST_True({'start': tok, 'end': tok})
1615
elif tmp__ is "None":
1616
return AST_Null({'start': tok, 'end': tok})
1617
elif tmp_ is "js":
1618
return AST_Verbatim({
1619
'start': tok,
1620
'end': tok,
1621
'value': tok.value,
1622
})
1623
token_error(
1624
tok,
1625
'Expecting an atomic token (number/string/bool/regexp/js/None)')
1626
1627
def as_atom_node():
1628
ret = token_as_atom_node()
1629
next()
1630
return ret
1631
1632
def expr_atom(allow_calls):
1633
if is_("operator", "new"):
1634
return new_()
1635
1636
start = S.token
1637
if is_("punc"):
1638
tmp_ = start.value
1639
if tmp_ is "(":
1640
S.in_parenthesized_expr = True
1641
next()
1642
if is_('punc', ')'):
1643
next()
1644
# since we don't have tuples in pylang (yet?)...
1645
return AST_Array({'elements': []})
1646
ex = expression(True)
1647
if is_('keyword', 'for'):
1648
ret = read_comprehension(
1649
AST_GeneratorComprehension({'statement': ex}), ')')
1650
S.in_parenthesized_expr = False
1651
return ret
1652
ex.start = start
1653
ex.end = S.token
1654
if is_node_type(ex, AST_SymbolRef):
1655
ex.parens = True
1656
if not is_node_type(ex, AST_GeneratorComprehension):
1657
expect(")")
1658
if is_node_type(ex, AST_UnaryPrefix):
1659
ex.parenthesized = True
1660
S.in_parenthesized_expr = False
1661
return subscripts(ex, allow_calls)
1662
elif tmp_ is "[":
1663
return subscripts(array_(), allow_calls)
1664
elif tmp_ is "{":
1665
return subscripts(object_(), allow_calls)
1666
1667
unexpected()
1668
1669
if is_("keyword", "class"):
1670
next()
1671
cls = class_()
1672
cls.start = start
1673
cls.end = prev()
1674
return subscripts(cls, allow_calls)
1675
1676
if is_("keyword", "def"):
1677
next()
1678
func = function_(False, True, False)
1679
func.start = start
1680
func.end = prev()
1681
return subscripts(func, allow_calls)
1682
1683
if is_("keyword", "lambda"):
1684
next()
1685
func = function_(False, True, True)
1686
func.start = start
1687
func.end = prev()
1688
return subscripts(func, allow_calls)
1689
1690
if is_('keyword', 'yield'):
1691
next()
1692
return yield_()
1693
1694
if ATOMIC_START_TOKEN[S.token.type]:
1695
return subscripts(as_atom_node(), allow_calls)
1696
1697
unexpected()
1698
1699
def expr_list(closing, allow_trailing_comma, allow_empty, func_call):
1700
first = True
1701
a = []
1702
saw_starargs = False
1703
while not is_("punc", closing):
1704
if saw_starargs:
1705
token_error(
1706
prev(),
1707
"*args must be the last argument in a function call")
1708
1709
if first:
1710
first = False
1711
else:
1712
expect(",")
1713
if allow_trailing_comma and is_("punc", closing):
1714
break
1715
1716
if is_("operator", "*") and func_call:
1717
saw_starargs = True
1718
next()
1719
1720
if is_("punc", ",") and allow_empty:
1721
a.push(AST_Hole({'start': S.token, 'end': S.token}))
1722
else:
1723
a.push(expression(False))
1724
1725
if func_call:
1726
tmp = []
1727
tmp.kwargs = []
1728
for arg in a:
1729
if is_node_type(arg, AST_Assign):
1730
tmp.kwargs.push([arg.left, arg.right])
1731
else:
1732
tmp.push(arg)
1733
a = tmp
1734
1735
next()
1736
if saw_starargs:
1737
a.starargs = True
1738
return a
1739
1740
def func_call_list(empty):
1741
a = r'%js []'
1742
first = True
1743
a.kwargs = r'%js []'
1744
a.kwarg_items = r'%js []'
1745
a.starargs = False
1746
if empty:
1747
return a
1748
single_comprehension = False
1749
while not is_("punc", ')') and not is_('eof'):
1750
if not first:
1751
expect(",")
1752
if is_('punc', ')'):
1753
break
1754
if is_('operator', '*'):
1755
next()
1756
arg = expression(False)
1757
arg.is_array = True
1758
a.push(arg)
1759
a.starargs = True
1760
elif is_('operator', '**'):
1761
next()
1762
a.kwarg_items.push(as_symbol(AST_SymbolRef, False))
1763
a.starargs = True
1764
else:
1765
arg = expression(False)
1766
if is_node_type(arg, AST_Assign):
1767
a.kwargs.push([arg.left, arg.right])
1768
else:
1769
if is_('keyword', 'for'):
1770
if not first:
1771
croak(
1772
'Generator expression must be parenthesized if not sole argument'
1773
)
1774
a.push(
1775
read_comprehension(
1776
AST_GeneratorComprehension({'statement': arg}),
1777
')'))
1778
single_comprehension = True
1779
break
1780
a.push(arg)
1781
first = False
1782
if not single_comprehension:
1783
next()
1784
return a
1785
1786
@embed_tokens
1787
def array_():
1788
expect("[")
1789
expr = []
1790
if not is_("punc", "]"):
1791
expr.push(expression(False))
1792
if is_("punc", ".."):
1793
if not S.scoped_flags.get('ellipses'):
1794
croak(
1795
"Use 'from __python__ import ellipses' to support the [a..b] syntax"
1796
)
1797
# ellipses range
1798
return read_ellipses_range(
1799
AST_EllipsesRange({'first': expr[0]}), ']')
1800
1801
if is_("keyword", "for"):
1802
# list comprehension
1803
return read_comprehension(
1804
AST_ListComprehension({'statement': expr[0]}), ']')
1805
1806
if not is_("punc", "]"):
1807
expect(",")
1808
1809
return AST_Array({'elements': expr.concat(expr_list("]", True, True))})
1810
1811
@embed_tokens
1812
def object_():
1813
expect("{")
1814
first = True
1815
has_non_const_keys = False
1816
is_pydict = S.scoped_flags.get('dict_literals', False)
1817
is_jshash = S.scoped_flags.get('hash_literals', False)
1818
a = []
1819
while not is_("punc", "}"):
1820
if not first:
1821
expect(",")
1822
if is_("punc", "}"):
1823
# allow trailing comma
1824
break
1825
first = False
1826
1827
start = S.token
1828
ctx = S.input.context()
1829
orig = ctx.expecting_object_literal_key
1830
ctx.expecting_object_literal_key = True
1831
try:
1832
left = expression(False)
1833
finally:
1834
ctx.expecting_object_literal_key = orig
1835
if is_('keyword', 'for'):
1836
# is_pydict is irrelevant here
1837
return read_comprehension(
1838
AST_SetComprehension({'statement': left}), '}')
1839
if a.length is 0 and (is_('punc', ',') or is_('punc', '}')):
1840
end = prev()
1841
return set_(start, end, left)
1842
if not is_node_type(left, AST_Constant):
1843
has_non_const_keys = True
1844
expect(":")
1845
a.push(
1846
AST_ObjectKeyVal({
1847
'start': start,
1848
'key': left,
1849
'value': expression(False),
1850
'end': prev()
1851
}))
1852
if a.length is 1 and is_('keyword', 'for'):
1853
return dict_comprehension(a, is_pydict, is_jshash)
1854
1855
next()
1856
args = {
1857
'properties': a,
1858
'is_pydict': is_pydict,
1859
'is_jshash': is_jshash,
1860
}
1861
if has_non_const_keys:
1862
return AST_ExpressiveObject(args)
1863
else:
1864
return AST_Object(args)
1865
1866
def set_(start, end, expr):
1867
ostart = start
1868
a = [AST_SetItem({'start': start, 'end': end, 'value': expr})]
1869
while not is_("punc", "}"):
1870
expect(",")
1871
start = S.token
1872
if is_("punc", "}"):
1873
# allow trailing comma
1874
break
1875
a.push(
1876
AST_SetItem({
1877
'start': start,
1878
'value': expression(False),
1879
'end': prev()
1880
}))
1881
next()
1882
return AST_Set({'items': a, 'start': ostart, 'end': prev()})
1883
1884
def read_ellipses_range(obj, terminator):
1885
next()
1886
obj['last'] = expression(False)
1887
expect("]")
1888
return obj
1889
1890
def read_comprehension(obj, terminator):
1891
if is_node_type(obj, AST_GeneratorComprehension):
1892
baselib_items['yield'] = True
1893
S.in_comprehension = True
1894
S.in_parenthesized_expr = False # in case we are already in a parenthesized expression
1895
expect_token('keyword', 'for')
1896
forloop = for_(True)
1897
obj.init = forloop.init
1898
obj.name = forloop.name
1899
obj.object = forloop.object
1900
obj.condition = None if is_('punc', terminator) else (expect_token(
1901
"keyword", "if"), expression(True))
1902
expect(terminator)
1903
S.in_comprehension = False
1904
return obj
1905
1906
def dict_comprehension(a, is_pydict, is_jshash):
1907
if a.length:
1908
left, right = a[0].key, a[0].value
1909
else:
1910
left = expression(False)
1911
if not is_('punc', ':'):
1912
return read_comprehension(
1913
AST_SetComprehension({'statement': left}), '}')
1914
expect(':')
1915
right = expression(False)
1916
return read_comprehension(
1917
AST_DictComprehension({
1918
'statement': left,
1919
'value_statement': right,
1920
'is_pydict': is_pydict,
1921
'is_jshash': is_jshash
1922
}), '}')
1923
1924
def as_name():
1925
tmp = S.token
1926
next()
1927
tmp_ = tmp.type
1928
if tmp_ is "name" or tmp_ is "operator" or tmp_ is "keyword" or tmp_ is "atom":
1929
return tmp.value
1930
else:
1931
unexpected()
1932
1933
def token_as_symbol(tok, ttype):
1934
name = tok.value
1935
if RESERVED_WORDS[name] and name is not 'this':
1936
croak(name + ' is a reserved word')
1937
args = {'name': r"%js String(tok.value)", 'start': tok, 'end': tok}
1938
if name is 'this':
1939
return AST_This(args)
1940
else:
1941
return js_new(ttype, args)
1942
1943
def as_symbol(ttype, noerror):
1944
if not is_("name"):
1945
if not noerror:
1946
croak("Name expected")
1947
return None
1948
1949
sym = token_as_symbol(S.token, ttype)
1950
next()
1951
return sym
1952
1953
# for generating/inserting a symbol
1954
def new_symbol(type, name):
1955
args = {'name': r"%js String(name)", 'start': None, 'end': None}
1956
if name is 'this':
1957
return AST_This(args)
1958
else:
1959
return js_new(type, args)
1960
1961
def is_static_method(cls, method):
1962
if has_prop(COMMON_STATIC,
1963
method) or (cls.static and has_prop(cls.static, method)):
1964
return True
1965
else:
1966
return False
1967
1968
def getitem(expr, allow_calls):
1969
start = expr.start
1970
next()
1971
is_py_sub = S.scoped_flags.get('overload_getitem', False)
1972
slice_bounds = r'%js []'
1973
is_slice = False
1974
if is_("punc", ":"):
1975
# slice [:n]
1976
slice_bounds.push(None)
1977
else:
1978
slice_bounds.push(expression(False))
1979
1980
if is_("punc", ":"):
1981
# slice [n:m?]
1982
is_slice = True
1983
next()
1984
if is_("punc", ":"):
1985
slice_bounds.push(None)
1986
elif not is_("punc", "]"):
1987
slice_bounds.push(expression(False))
1988
1989
if is_("punc", ":"):
1990
# slice [n:m:o?]
1991
next()
1992
if is_("punc", "]"):
1993
unexpected()
1994
else:
1995
slice_bounds.push(expression(False))
1996
1997
# multi-index notation, e.g., [n,m,r]
1998
# I added parsing it so that mypy can be used.
1999
while is_("punc", ","):
2000
next()
2001
slice_bounds.push(expression(False))
2002
2003
expect("]")
2004
2005
if is_slice:
2006
if is_("operator", '='):
2007
# splice-assignment (arr[start:end] = ...)
2008
next() # swallow the assignment
2009
return subscripts(
2010
AST_Splice({
2011
'start':
2012
start,
2013
'expression':
2014
expr,
2015
'property':
2016
slice_bounds[0] or AST_Number({'value': 0}),
2017
'property2':
2018
slice_bounds[1],
2019
'assignment':
2020
expression(True),
2021
'end':
2022
prev()
2023
}), allow_calls)
2024
elif slice_bounds.length is 3:
2025
# extended slice (arr[start:end:step])
2026
slice_bounds.unshift(slice_bounds.pop())
2027
if not slice_bounds[-1]:
2028
slice_bounds.pop()
2029
if not slice_bounds[-1]:
2030
slice_bounds.pop()
2031
elif not slice_bounds[-2]:
2032
slice_bounds[-2] = AST_Undefined()
2033
return subscripts(
2034
AST_Call({
2035
'start':
2036
start,
2037
'expression':
2038
AST_SymbolRef({
2039
'name':
2040
'ρσ_delslice' if S.in_delete else "ρσ_eslice"
2041
}),
2042
'args': [expr].concat(slice_bounds),
2043
'end':
2044
prev()
2045
}), allow_calls)
2046
else:
2047
# regular slice (arr[start:end])
2048
slice_bounds = [
2049
AST_Number({'value': 0}) if i is None else i
2050
for i in slice_bounds
2051
]
2052
if S.in_delete:
2053
return subscripts(
2054
AST_Call({
2055
'start':
2056
start,
2057
'expression':
2058
AST_SymbolRef({'name': 'ρσ_delslice'}),
2059
'args': [expr,
2060
AST_Number({'value':
2061
1})].concat(slice_bounds),
2062
'end':
2063
prev()
2064
}), allow_calls)
2065
2066
return subscripts(
2067
AST_Call({
2068
'start':
2069
start,
2070
'expression':
2071
AST_Dot({
2072
'start': start,
2073
'expression': expr,
2074
'property': "slice",
2075
'end': prev()
2076
}),
2077
'args':
2078
slice_bounds,
2079
'end':
2080
prev()
2081
}), allow_calls)
2082
else:
2083
# regular index (arr[index])
2084
if len(slice_bounds) == 1:
2085
prop = slice_bounds[0] or AST_Number({'value': 0})
2086
else:
2087
# arr[index1,index2]
2088
prop = AST_Array({'elements': slice_bounds})
2089
if is_py_sub:
2090
assignment = None
2091
if is_("operator") and S.token.value is "=":
2092
next()
2093
assignment = expression(True)
2094
return subscripts(
2095
AST_ItemAccess({
2096
'start': start,
2097
'expression': expr,
2098
'property': prop,
2099
'assignment': assignment,
2100
'end': prev()
2101
}), allow_calls)
2102
2103
return subscripts(
2104
AST_Sub({
2105
'start': start,
2106
'expression': expr,
2107
'property': prop,
2108
'end': prev()
2109
}), allow_calls)
2110
2111
def call_(expr):
2112
start = expr.start
2113
S.in_parenthesized_expr = True
2114
next()
2115
if not expr.parens and get_class_in_scope(expr):
2116
# this is an object being created using a class
2117
ret = subscripts(
2118
AST_New({
2119
'start': start,
2120
'expression': expr,
2121
'args': func_call_list(),
2122
'end': prev()
2123
}), True)
2124
S.in_parenthesized_expr = False
2125
return ret
2126
else:
2127
if is_node_type(expr, AST_Dot):
2128
c = get_class_in_scope(expr.expression)
2129
2130
if c:
2131
# generate class call
2132
funcname = expr
2133
2134
ret = subscripts(
2135
AST_ClassCall({
2136
'start':
2137
start,
2138
"class":
2139
expr.expression,
2140
'method':
2141
funcname.property,
2142
"static":
2143
is_static_method(c, funcname.property),
2144
'args':
2145
func_call_list(),
2146
'end':
2147
prev()
2148
}), True)
2149
S.in_parenthesized_expr = False
2150
return ret
2151
elif is_node_type(expr, AST_SymbolRef):
2152
tmp_ = expr.name
2153
if tmp_ is "jstype":
2154
ret = AST_UnaryPrefix({
2155
'start': start,
2156
'operator': "typeof",
2157
'expression': func_call_list()[0],
2158
'end': prev()
2159
})
2160
S.in_parenthesized_expr = False
2161
return ret
2162
elif tmp_ is "isinstance":
2163
args = func_call_list()
2164
if args.length is not 2:
2165
croak(
2166
'isinstance() must be called with exactly two arguments'
2167
)
2168
ret = AST_Binary({
2169
'start': start,
2170
'left': args[0],
2171
'operator': 'instanceof',
2172
'right': args[1],
2173
'end': prev()
2174
})
2175
S.in_parenthesized_expr = False
2176
return ret
2177
2178
# fall-through to basic function call
2179
ret = subscripts(
2180
AST_Call({
2181
'start': start,
2182
'expression': expr,
2183
'args': func_call_list(),
2184
'end': prev()
2185
}), True)
2186
S.in_parenthesized_expr = False
2187
return ret
2188
2189
def get_attr(expr, allow_calls):
2190
next()
2191
prop = as_name()
2192
c = get_class_in_scope(expr)
2193
if c:
2194
classvars = c.provisional_classvars if c.processing else c.classvars
2195
if classvars and r'%js classvars[prop]':
2196
prop = 'prototype.' + prop
2197
2198
return subscripts(
2199
AST_Dot({
2200
'start': expr.start,
2201
'expression': expr,
2202
'property': prop,
2203
'end': prev()
2204
}), allow_calls)
2205
2206
def existential(expr, allow_calls):
2207
ans = AST_Existential({
2208
'start': expr.start,
2209
'end': S.token,
2210
'expression': expr
2211
})
2212
next()
2213
ttype = S.token.type
2214
val = S.token.value
2215
if S.token.nlb or ttype is 'keyword' or ttype is 'operator' or ttype is 'eof':
2216
ans.after = None
2217
return ans
2218
if ttype is 'punc':
2219
if val is '.':
2220
ans.after = '.'
2221
elif val is '[':
2222
is_py_sub = S.scoped_flags.get('overload_getitem', False)
2223
ans.after = 'g' if is_py_sub else '['
2224
elif val is '(':
2225
if not allow_calls:
2226
unexpected()
2227
ans.after = '('
2228
else:
2229
ans.after = None
2230
return ans
2231
return subscripts(ans, allow_calls)
2232
2233
ans.after = expression()
2234
return ans
2235
2236
def subscripts(expr, allow_calls):
2237
if is_("punc", "."):
2238
return get_attr(expr, allow_calls)
2239
2240
if is_("punc", "[") and not S.token.nlb:
2241
return getitem(expr, allow_calls)
2242
2243
if allow_calls and is_("punc", "(") and not S.token.nlb:
2244
return call_(expr)
2245
2246
if is_('punc', '?'):
2247
return existential(expr, allow_calls)
2248
2249
return expr
2250
2251
def maybe_unary(allow_calls):
2252
start = S.token
2253
if is_('operator', '@'):
2254
if S.parsing_decorator:
2255
croak('Nested decorators are not allowed')
2256
next()
2257
S.parsing_decorator = True
2258
expr = expression()
2259
S.parsing_decorator = False
2260
S.decorators.push(expr)
2261
return AST_EmptyStatement({
2262
'stype': '@',
2263
'start': prev(),
2264
'end': prev()
2265
})
2266
if is_("operator") and UNARY_PREFIX[start.value]:
2267
next()
2268
is_parenthesized = is_('punc', '(')
2269
S.in_delete = start.value is 'delete'
2270
expr = maybe_unary(allow_calls)
2271
S.in_delete = False
2272
ex = make_unary(AST_UnaryPrefix, start.value, expr,
2273
is_parenthesized)
2274
ex.start = start
2275
ex.end = prev()
2276
return ex
2277
2278
val = expr_atom(allow_calls)
2279
return val
2280
2281
def make_unary(ctor, op, expr, is_parenthesized):
2282
return js_new(ctor, {
2283
'operator': op,
2284
'expression': expr,
2285
'parenthesized': is_parenthesized
2286
})
2287
2288
def expr_op(left, min_prec, no_in):
2289
op = S.token.value if is_('operator') else None
2290
if op is "!" and peek().type is "operator" and peek().value is "in":
2291
next()
2292
S.token.value = op = 'nin'
2293
2294
if no_in and (op is "in" or op is 'nin'):
2295
op = None
2296
2297
prec = PRECEDENCE[op] if op is not None else None
2298
if prec is not None and prec > min_prec:
2299
next()
2300
right = expr_op(maybe_unary(True), prec, no_in)
2301
ret = AST_Binary({
2302
'start': left.start,
2303
'left': left,
2304
'operator': op,
2305
'right': right,
2306
'end': right.end
2307
})
2308
return expr_op(ret, min_prec, no_in)
2309
return left
2310
2311
def expr_ops(no_in):
2312
return expr_op(maybe_unary(True), 0, no_in)
2313
2314
def maybe_conditional(no_in):
2315
start = S.token
2316
expr = expr_ops(no_in)
2317
if (is_('keyword', 'if')
2318
and (S.in_parenthesized_expr or
2319
(S.statement_starting_token is not S.token
2320
and not S.in_comprehension and not S.token.nlb))):
2321
next()
2322
ne = expression(False)
2323
expect_token('keyword', 'else')
2324
conditional = AST_Conditional({
2325
'start':
2326
start,
2327
'condition':
2328
ne,
2329
'consequent':
2330
expr,
2331
'alternative':
2332
expression(False, no_in),
2333
'end':
2334
peek()
2335
})
2336
return conditional
2337
return expr
2338
2339
def create_assign(data):
2340
if data.right and is_node_type(data.right, AST_Seq) and (is_node_type(
2341
data.right.car, AST_Assign) or is_node_type(
2342
data.right.cdr, AST_Assign)) and data.operator is not '=':
2343
token_error(
2344
data.start,
2345
'Invalid assignment operator for chained assignment: ' +
2346
data.operator)
2347
ans = AST_Assign(data)
2348
if S.in_class.length and S.in_class[-1]:
2349
class_name = S.in_class[-1]
2350
if is_node_type(ans.left, AST_SymbolRef) and S.classes.length > 1:
2351
c = S.classes[-2][class_name]
2352
if c:
2353
if ans.is_chained():
2354
for lhs in ans.traverse_chain()[0]:
2355
c.provisional_classvars[lhs.name] = True
2356
else:
2357
c.provisional_classvars[ans.left.name] = True
2358
return ans
2359
2360
def maybe_assign(no_in, only_plain_assignment):
2361
start = S.token
2362
left = maybe_conditional(no_in)
2363
val = S.token.value
2364
if is_("operator") and ASSIGNMENT[val]:
2365
if only_plain_assignment and val is not '=':
2366
croak('Invalid assignment operator for chained assignment: ' +
2367
val)
2368
next()
2369
return create_assign({
2370
'start': start,
2371
'left': left,
2372
'operator': val,
2373
'right': maybe_assign(no_in, True),
2374
'end': prev()
2375
})
2376
return left
2377
2378
def expression(commas, no_in):
2379
# if there is an assignment, we want the sequences to pivot
2380
# around it to allow for tuple packing/unpacking
2381
start = S.token
2382
expr = maybe_assign(no_in)
2383
2384
def build_seq(a):
2385
if a.length is 1:
2386
return a[0]
2387
2388
return AST_Seq({
2389
'start': start,
2390
'car': a.shift(),
2391
'cdr': build_seq(a),
2392
'end': peek()
2393
})
2394
2395
if commas:
2396
left = r'%js [ expr ]'
2397
while is_("punc", ","):
2398
next()
2399
if is_node_type(expr, AST_Assign):
2400
left[-1] = left[-1].left
2401
return create_assign({
2402
'start':
2403
start,
2404
'left': (left[0] if left.length is 1 else AST_Array(
2405
{'elements': left})),
2406
'operator':
2407
expr.operator,
2408
'right':
2409
AST_Seq({
2410
'car': expr.right,
2411
'cdr': expression(True, no_in)
2412
}),
2413
'end':
2414
peek()
2415
})
2416
2417
expr = maybe_assign(no_in)
2418
left.push(expr)
2419
2420
# if last one was an assignment, fix it
2421
if left.length > 1 and is_node_type(left[-1], AST_Assign):
2422
left[-1] = left[-1].left
2423
return create_assign({
2424
'start': start,
2425
'left': AST_Array({'elements': left}),
2426
'operator': expr.operator,
2427
'right': expr.right,
2428
'end': peek()
2429
})
2430
2431
return build_seq(left)
2432
return expr
2433
2434
def in_loop(cont):
2435
S.in_loop += 1
2436
ret = cont()
2437
S.in_loop -= 1
2438
return ret
2439
2440
def run_parser():
2441
start = S.token = next()
2442
body = r'%js []'
2443
docstrings = r'%js []'
2444
first_token = True
2445
toplevel = options.toplevel
2446
while not is_("eof"):
2447
element = statement()
2448
if first_token and is_node_type(
2449
element,
2450
AST_Directive) and element.value.indexOf('#!') is 0:
2451
shebang = element.value
2452
else:
2453
ds = not toplevel and is_docstring(
2454
element
2455
) # do not process strings as docstrings if we are concatenating toplevels
2456
if ds:
2457
docstrings.push(ds)
2458
else:
2459
body.push(element)
2460
first_token = False
2461
2462
end = prev()
2463
if toplevel:
2464
toplevel.body = toplevel.body.concat(body)
2465
toplevel.end = end
2466
toplevel.docstrings
2467
else:
2468
toplevel = AST_Toplevel({
2469
'start': start,
2470
'body': body,
2471
'shebang': shebang,
2472
'end': end,
2473
'docstrings': docstrings,
2474
})
2475
2476
toplevel.nonlocalvars = scan_for_nonlocal_defs(toplevel.body).concat(
2477
S.globals)
2478
toplevel.localvars = []
2479
toplevel.exports = []
2480
seen_exports = {}
2481
2482
def add_item(item, isvar):
2483
if (toplevel.nonlocalvars.indexOf(item) < 0):
2484
symbol = new_symbol(AST_SymbolVar, item)
2485
if isvar:
2486
toplevel.localvars.push(symbol)
2487
if not has_prop(seen_exports, item):
2488
toplevel.exports.push(symbol)
2489
seen_exports[item] = True
2490
2491
for item in scan_for_local_vars(toplevel.body):
2492
add_item(item, True)
2493
for item in scan_for_top_level_callables(toplevel.body):
2494
add_item(item, False)
2495
2496
toplevel.filename = options.filename
2497
toplevel.imported_module_ids = imported_module_ids
2498
toplevel.classes = scan_for_classes(toplevel.body)
2499
toplevel.import_order = Object.keys(imported_modules).length
2500
toplevel.module_id = module_id
2501
imported_modules[module_id] = toplevel
2502
toplevel.imports = imported_modules
2503
toplevel.baselib = baselib_items
2504
toplevel.scoped_flags = S.scoped_flags.stack[0]
2505
importing_modules[module_id] = False
2506
toplevel.comments_after = S.token.comments_before or r'%js []'
2507
return toplevel
2508
2509
return run_parser
2510
2511
2512
def parse(text, options):
2513
options = defaults(
2514
options,
2515
{
2516
'filename': None, # name of the file being parsed
2517
'module_id': '__main__', # The id of the module being parsed
2518
'toplevel': None,
2519
'for_linting':
2520
False, # If True certain actions are not performed, such as importing modules
2521
'import_dirs': r'%js []',
2522
'classes':
2523
undefined, # Map of class names to AST_Class that are available in the global namespace (used by the REPL)
2524
'scoped_flags': {}, # Global scoped flags (used by the REPL)
2525
'discard_asserts': False,
2526
'module_cache_dir': '',
2527
'jsage':
2528
False, # if true, do some of what the Sage preparser does, e.g., ^ --> **.
2529
'tokens': False, # if true, show every token as it is parsed
2530
})
2531
import_dirs = [x for x in options.import_dirs]
2532
for location in r'%js [options.libdir, options.basedir]':
2533
if location:
2534
import_dirs.push(location)
2535
module_id = options.module_id
2536
baselib_items = {}
2537
imported_module_ids = []
2538
imported_modules = options.imported_modules or {}
2539
importing_modules = options.importing_modules or {}
2540
importing_modules[module_id] = True
2541
2542
def push():
2543
this.stack.push(Object.create(None))
2544
2545
def pop():
2546
this.stack.pop()
2547
2548
def get(name, defval):
2549
for i in range(this.stack.length - 1, -1, -1):
2550
d = this.stack[i]
2551
q = d[name]
2552
if q:
2553
return q
2554
return defval
2555
2556
def set(name, val):
2557
this.stack[-1][name] = val
2558
2559
# The internal state of the parser
2560
S = {
2561
'input':
2562
tokenizer(text, options.filename) \
2563
if jstype(text) is 'string' else text,
2564
'token':
2565
None,
2566
'prev':
2567
None,
2568
'peeked': [],
2569
'in_function':
2570
0,
2571
'statement_starting_token':
2572
None,
2573
'in_comprehension':
2574
False,
2575
'in_parenthesized_expr':
2576
False,
2577
'in_delete':
2578
False,
2579
'in_loop':
2580
0,
2581
'in_class': [False],
2582
'classes': [{}],
2583
'functions': [{}],
2584
'labels': [],
2585
'decorators':
2586
r'%js []',
2587
'parsing_decorator':
2588
False,
2589
'globals':
2590
r'%js []',
2591
'scoped_flags': {
2592
'stack': r'%js [options.scoped_flags || Object.create(null)]',
2593
'push': push,
2594
'pop': pop,
2595
'get': get,
2596
'set': set
2597
},
2598
}
2599
2600
if options.jsage:
2601
# Set all the jsage compiler options; this is only used in the repl.
2602
for name in ['exponent', 'ellipses', 'numbers']:
2603
S.scoped_flags.set(name, True)
2604
2605
if S.scoped_flags.get('exponent'):
2606
# Since exponent parsing partly happens at the
2607
# tokenizer, we have to tell it.
2608
S.input.context()['exponent'] = True
2609
2610
if options.classes:
2611
for cname in options.classes:
2612
obj = options.classes[cname]
2613
S.classes[0][cname] = {
2614
'static': obj.static,
2615
'bound': obj.bound,
2616
'classvars': obj.classvars
2617
}
2618
2619
return create_parser_ctx(S, import_dirs, module_id, baselib_items,
2620
imported_module_ids, imported_modules,
2621
importing_modules, options)()
2622
2623