rapydscript-ng 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/local-agent.md +4 -0
- package/package.json +1 -1
- package/release/compiler.js +5125 -5115
- package/release/signatures.json +6 -6
- package/src/ast.pyj +18 -25
- package/src/output/functions.pyj +111 -13
- package/src/output/loops.pyj +31 -9
- package/src/tokenizer.pyj +17 -5
- package/test/str.pyj +10 -0
- package/tools/cli.mjs +4 -3
- package/tools/compile.mjs +9 -3
- package/tools/lint-worker.mjs +101 -0
- package/tools/lint.mjs +127 -8
- package/tree-sitter/grammar.js +27 -6
- package/tree-sitter/queries/highlights.scm +10 -0
- package/tree-sitter/src/grammar.json +130 -5
- package/tree-sitter/src/node-types.json +73 -0
- package/tree-sitter/src/parser.c +119589 -114993
- package/tree-sitter/src/scanner.c +275 -17
- package/tree-sitter/test/corpus/strings.txt +41 -5
package/release/signatures.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"ast": "
|
|
2
|
+
"ast": "6c918899920124616fcf87f30c2db475bf05d7b4",
|
|
3
3
|
"baselib-builtins": "0e170c877a1bcc67e506df0ba64037bf1a2f6121",
|
|
4
4
|
"baselib-containers": "39d05f99eb7cc741e44871ccc6acea868ee70ec8",
|
|
5
5
|
"baselib-errors": "7f786439249e7d06cc010757f1bbaa6a2c3ea019",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
"output/codegen": "ff6f50e4533271e30ca55aff80c18f244d1c0f22",
|
|
14
14
|
"output/comments": "b3baa47c00c1983ee54e912c602b58318e9c2bde",
|
|
15
15
|
"output/exceptions": "7b9548eab7d67991c4932746a0ce766e07e9ac8d",
|
|
16
|
-
"output/functions": "
|
|
16
|
+
"output/functions": "1a88ea89a36366d958d0a6dcf5d3477775197702",
|
|
17
17
|
"output/literals": "50a034dbdf6fca16de7b5147a7fd64faeb461255",
|
|
18
|
-
"output/loops": "
|
|
18
|
+
"output/loops": "567b34e2f81c92512482cdbdfa59e90c5cb1ea38",
|
|
19
19
|
"output/modules": "e2c2d92c714c31face3470de7ae8a61275a95fcd",
|
|
20
20
|
"output/operators": "f45450104ed5def1298cf9a4962492ffeca02e63",
|
|
21
21
|
"output/statements": "a56a432dee96d9ed0e490fc024ed8a99d30a5d0c",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"output/utils": "97837ab9091710059e2ba01e245ee05fec82cd29",
|
|
24
24
|
"parse": "0965e0416ea81d2b4bce422729b662022c0c4eb4",
|
|
25
25
|
"string_interpolation": "9cc04ded68b82174728873f7df96aed60a4fb478",
|
|
26
|
-
"tokenizer": "
|
|
26
|
+
"tokenizer": "3c70e79fbe95b1f2d3164da4c96274b0d352d533",
|
|
27
27
|
"unicode_aliases": "79ac6eaa5e6be44a5397d62c561f854a8fe7528e",
|
|
28
28
|
"utils": "0cbca1fa76442212e31644f95a4f6e9a0317234f",
|
|
29
|
-
"#compiler#": "
|
|
30
|
-
"#compiled_with#": "
|
|
29
|
+
"#compiler#": "b3aec6fc48ac89b11c2c190d5fc03c3e688ede69",
|
|
30
|
+
"#compiled_with#": "b3aec6fc48ac89b11c2c190d5fc03c3e688ede69"
|
|
31
31
|
}
|
package/src/ast.pyj
CHANGED
|
@@ -129,15 +129,13 @@ class AST_Directive(AST_Statement):
|
|
|
129
129
|
'Represents a directive, like "use strict";'
|
|
130
130
|
properties = {
|
|
131
131
|
'value': "[string] The value of this directive as a plain string (it's not an AST_String!)",
|
|
132
|
-
'scope': '[AST_Scope/S] The scope that this directive affects'
|
|
132
|
+
'scope': '[AST_Scope/S] The scope that this directive affects',
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
class AST_SimpleStatement(AST_Statement):
|
|
137
137
|
'A statement consisting of an expression, i.e. a = 1 + 2'
|
|
138
|
-
properties = {
|
|
139
|
-
'body': '[AST_Node] an expression node (should not be instanceof AST_Statement)'
|
|
140
|
-
}
|
|
138
|
+
properties = {'body': '[AST_Node] an expression node (should not be instanceof AST_Statement)'}
|
|
141
139
|
|
|
142
140
|
def _walk(self, visitor):
|
|
143
141
|
return visitor._visit(self, def ():
|
|
@@ -196,9 +194,7 @@ class AST_EmptyStatement(AST_Statement):
|
|
|
196
194
|
|
|
197
195
|
class AST_StatementWithBody(AST_Statement):
|
|
198
196
|
'Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`'
|
|
199
|
-
properties = {
|
|
200
|
-
'body': "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"
|
|
201
|
-
}
|
|
197
|
+
properties = {'body': "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement"}
|
|
202
198
|
|
|
203
199
|
def _walk(self, visitor):
|
|
204
200
|
return visitor._visit(self, def ():
|
|
@@ -208,9 +204,7 @@ class AST_StatementWithBody(AST_Statement):
|
|
|
208
204
|
|
|
209
205
|
class AST_DWLoop(AST_StatementWithBody):
|
|
210
206
|
'Base class for do/while statements'
|
|
211
|
-
properties = {
|
|
212
|
-
'condition': '[AST_Node] the loop condition. Should not be instanceof AST_Statement'
|
|
213
|
-
}
|
|
207
|
+
properties = {'condition': '[AST_Node] the loop condition. Should not be instanceof AST_Statement'}
|
|
214
208
|
|
|
215
209
|
def _walk(self, visitor):
|
|
216
210
|
return visitor._visit(self, def ():
|
|
@@ -256,7 +250,7 @@ class AST_ListComprehension(AST_ForIn):
|
|
|
256
250
|
'A list comprehension expression'
|
|
257
251
|
properties = {
|
|
258
252
|
'condition': '[AST_Node] the `if` condition',
|
|
259
|
-
'statement': '[AST_Node] statement to perform on each element before returning it'
|
|
253
|
+
'statement': '[AST_Node] statement to perform on each element before returning it',
|
|
260
254
|
}
|
|
261
255
|
|
|
262
256
|
def _walk(self, visitor):
|
|
@@ -498,9 +492,7 @@ class AST_Jump(AST_Statement):
|
|
|
498
492
|
|
|
499
493
|
class AST_Exit(AST_Jump):
|
|
500
494
|
'Base class for “exits” (`return` and `throw`)'
|
|
501
|
-
properties = {
|
|
502
|
-
'value': '[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return'
|
|
503
|
-
}
|
|
495
|
+
properties = {'value': '[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return'}
|
|
504
496
|
|
|
505
497
|
def _walk(self, visitor):
|
|
506
498
|
return visitor._visit(self, def ():
|
|
@@ -555,7 +547,7 @@ class AST_If(AST_StatementWithBody):
|
|
|
555
547
|
'A `if` statement'
|
|
556
548
|
properties = {
|
|
557
549
|
'condition': '[AST_Node] the `if` condition',
|
|
558
|
-
'alternative': '[AST_Statement?] the `else` part, or null if not present'
|
|
550
|
+
'alternative': '[AST_Statement?] the `else` part, or null if not present',
|
|
559
551
|
}
|
|
560
552
|
|
|
561
553
|
def _walk(self, visitor):
|
|
@@ -840,7 +832,7 @@ class AST_Splice(AST_PropAccess):
|
|
|
840
832
|
'Index-style property access, i.e. `a[3:5]`'
|
|
841
833
|
properties = {
|
|
842
834
|
'property2': '[AST_Node] the 2nd property to access - typically ending index for the array.',
|
|
843
|
-
'assignment': '[AST_Node] The data being spliced in.'
|
|
835
|
+
'assignment': '[AST_Node] The data being spliced in.',
|
|
844
836
|
}
|
|
845
837
|
|
|
846
838
|
def _walk(self, visitor):
|
|
@@ -919,10 +911,10 @@ class AST_Assign(AST_Binary):
|
|
|
919
911
|
'An assignment expression — `a = b + 5`'
|
|
920
912
|
|
|
921
913
|
def is_chained(self):
|
|
922
|
-
return is_node_type(
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
)
|
|
914
|
+
return is_node_type(
|
|
915
|
+
self.right,
|
|
916
|
+
AST_Assign
|
|
917
|
+
) or (is_node_type(self.right, AST_Seq) and (is_node_type(self.right.car, AST_Assign) or is_node_type(self.right.cdr, AST_Assign)))
|
|
926
918
|
|
|
927
919
|
def traverse_chain(self):
|
|
928
920
|
right = self.right
|
|
@@ -1056,7 +1048,7 @@ class AST_Symbol(AST_Node):
|
|
|
1056
1048
|
properties = {
|
|
1057
1049
|
'name': '[string] name of this symbol',
|
|
1058
1050
|
'scope': '[AST_Scope/S] the current scope (not necessarily the definition scope)',
|
|
1059
|
-
'thedef': '[SymbolDef/S] the definition of this symbol'
|
|
1051
|
+
'thedef': '[SymbolDef/S] the definition of this symbol',
|
|
1060
1052
|
}
|
|
1061
1053
|
|
|
1062
1054
|
|
|
@@ -1088,9 +1080,7 @@ class AST_SymbolNonlocal(AST_SymbolDeclaration):
|
|
|
1088
1080
|
|
|
1089
1081
|
class AST_SymbolFunarg(AST_SymbolVar):
|
|
1090
1082
|
'Symbol naming a function argument, possibly with an annotation.'
|
|
1091
|
-
properties = {
|
|
1092
|
-
'annotation': '[AST_Node?] The annotation provided for this argument (if any)'
|
|
1093
|
-
}
|
|
1083
|
+
properties = {'annotation': '[AST_Node?] The annotation provided for this argument (if any)'}
|
|
1094
1084
|
|
|
1095
1085
|
|
|
1096
1086
|
class AST_SymbolDefun(AST_SymbolDeclaration):
|
|
@@ -1242,7 +1232,10 @@ class TreeWalker:
|
|
|
1242
1232
|
if is_node_type(p, AST_If) and p.condition is self
|
|
1243
1233
|
or is_node_type(p, AST_Conditional) and p.condition is self
|
|
1244
1234
|
or is_node_type(p, AST_DWLoop) and p.condition is self
|
|
1245
|
-
or is_node_type(
|
|
1235
|
+
or is_node_type(
|
|
1236
|
+
p,
|
|
1237
|
+
AST_UnaryPrefix
|
|
1238
|
+
) and p.operator is '!' and p.expression is self:
|
|
1246
1239
|
return True
|
|
1247
1240
|
if not (is_node_type(p, AST_Binary) and (p.operator is '&&' or p.operator is '||')):
|
|
1248
1241
|
return False
|
package/src/output/functions.pyj
CHANGED
|
@@ -5,7 +5,6 @@ from __python__ import hash_literals
|
|
|
5
5
|
from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, is_node_type
|
|
6
6
|
from output.operators import print_getattr
|
|
7
7
|
from output.statements import print_bracketed
|
|
8
|
-
from output.stream import OutputStream
|
|
9
8
|
from output.utils import create_doctring
|
|
10
9
|
|
|
11
10
|
anonfunc = 'ρσ_anonfunc'
|
|
@@ -63,14 +62,59 @@ def function_preamble(node, output, offset):
|
|
|
63
62
|
output.space()
|
|
64
63
|
output.assign(arg)
|
|
65
64
|
if Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
|
|
66
|
-
output.spaced(
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
output.spaced(
|
|
66
|
+
'(arguments[' + i + ']',
|
|
67
|
+
'===',
|
|
68
|
+
'undefined',
|
|
69
|
+
'||',
|
|
70
|
+
'(',
|
|
71
|
+
i,
|
|
72
|
+
'===',
|
|
73
|
+
'arguments.length-1',
|
|
74
|
+
'&&',
|
|
75
|
+
kw,
|
|
76
|
+
'!==',
|
|
77
|
+
'null',
|
|
78
|
+
'&&',
|
|
79
|
+
'typeof',
|
|
80
|
+
kw,
|
|
81
|
+
'===',
|
|
82
|
+
'"object"',
|
|
83
|
+
'&&',
|
|
84
|
+
kw,
|
|
85
|
+
'[ρσ_kwargs_symbol]',
|
|
86
|
+
'===',
|
|
87
|
+
'true))',
|
|
88
|
+
'?',
|
|
89
|
+
''
|
|
90
|
+
)
|
|
69
91
|
output.print(fname + '.__defaults__.'), arg.print(output)
|
|
70
92
|
output.space(), output.print(':'), output.space()
|
|
71
93
|
else:
|
|
72
|
-
output.spaced(
|
|
73
|
-
|
|
94
|
+
output.spaced(
|
|
95
|
+
'(',
|
|
96
|
+
i,
|
|
97
|
+
'===',
|
|
98
|
+
'arguments.length-1',
|
|
99
|
+
'&&',
|
|
100
|
+
kw,
|
|
101
|
+
'!==',
|
|
102
|
+
'null',
|
|
103
|
+
'&&',
|
|
104
|
+
'typeof',
|
|
105
|
+
kw,
|
|
106
|
+
'===',
|
|
107
|
+
'"object"',
|
|
108
|
+
'&&',
|
|
109
|
+
kw,
|
|
110
|
+
'[ρσ_kwargs_symbol]',
|
|
111
|
+
'===',
|
|
112
|
+
'true)',
|
|
113
|
+
'?',
|
|
114
|
+
'undefined',
|
|
115
|
+
':',
|
|
116
|
+
''
|
|
117
|
+
)
|
|
74
118
|
output.print('arguments[' + i + ']')
|
|
75
119
|
output.end_statement()
|
|
76
120
|
if a.kwargs or a.has_defaults:
|
|
@@ -81,13 +125,35 @@ def function_preamble(node, output, offset):
|
|
|
81
125
|
output.end_statement()
|
|
82
126
|
# Ensure kwargs is the options object
|
|
83
127
|
output.indent()
|
|
84
|
-
output.spaced(
|
|
128
|
+
output.spaced(
|
|
129
|
+
'if',
|
|
130
|
+
'(' + kw,
|
|
131
|
+
'===',
|
|
132
|
+
'null',
|
|
133
|
+
'||',
|
|
134
|
+
'typeof',
|
|
135
|
+
kw,
|
|
136
|
+
'!==',
|
|
137
|
+
'"object"',
|
|
138
|
+
'||',
|
|
139
|
+
kw,
|
|
140
|
+
'[ρσ_kwargs_symbol]',
|
|
141
|
+
'!==',
|
|
142
|
+
'true)',
|
|
143
|
+
kw,
|
|
144
|
+
'=',
|
|
145
|
+
'{}'
|
|
146
|
+
)
|
|
85
147
|
output.end_statement()
|
|
86
148
|
# Read values from the kwargs object for any formal parameters
|
|
87
149
|
if a.has_defaults:
|
|
88
150
|
for dname in Object.keys(a.defaults):
|
|
89
151
|
output.indent()
|
|
90
|
-
output.spaced(
|
|
152
|
+
output.spaced(
|
|
153
|
+
'if',
|
|
154
|
+
'(Object.prototype.hasOwnProperty.call(' + kw + ',',
|
|
155
|
+
'"' + dname + '"))'
|
|
156
|
+
)
|
|
91
157
|
output.with_block(def ():
|
|
92
158
|
output.indent()
|
|
93
159
|
output.spaced(dname, '=', kw + '.' + dname)
|
|
@@ -103,11 +169,33 @@ def function_preamble(node, output, offset):
|
|
|
103
169
|
# Define the *args parameter, putting in whatever is left after assigning the formal parameters and the options object
|
|
104
170
|
nargs = a.args.length - offset
|
|
105
171
|
output.indent()
|
|
106
|
-
output.spaced(
|
|
172
|
+
output.spaced(
|
|
173
|
+
'var',
|
|
174
|
+
a.starargs.name,
|
|
175
|
+
'=',
|
|
176
|
+
'Array.prototype.slice.call(arguments,',
|
|
177
|
+
nargs + ')'
|
|
178
|
+
)
|
|
107
179
|
output.end_statement()
|
|
108
180
|
# Remove the options object, if present
|
|
109
181
|
output.indent()
|
|
110
|
-
output.spaced(
|
|
182
|
+
output.spaced(
|
|
183
|
+
'if',
|
|
184
|
+
'(' + kw,
|
|
185
|
+
'!==',
|
|
186
|
+
'null',
|
|
187
|
+
'&&',
|
|
188
|
+
'typeof',
|
|
189
|
+
kw,
|
|
190
|
+
'===',
|
|
191
|
+
'"object"',
|
|
192
|
+
'&&',
|
|
193
|
+
kw,
|
|
194
|
+
'[ρσ_kwargs_symbol]',
|
|
195
|
+
'===',
|
|
196
|
+
'true)',
|
|
197
|
+
a.starargs.name
|
|
198
|
+
)
|
|
111
199
|
output.print('.pop()')
|
|
112
200
|
output.end_statement()
|
|
113
201
|
|
|
@@ -183,7 +271,11 @@ def function_annotation(self, output, strip_first, name):
|
|
|
183
271
|
output.indent()
|
|
184
272
|
# Only define the properties if they were not already defined, which
|
|
185
273
|
# can happen if the function definition is inside a loop, for example.
|
|
186
|
-
output.spaced(
|
|
274
|
+
output.spaced(
|
|
275
|
+
'if',
|
|
276
|
+
'(!' + fname + '.' + names[0] + ')',
|
|
277
|
+
'Object.defineProperties(' + fname
|
|
278
|
+
)
|
|
187
279
|
output.comma()
|
|
188
280
|
output.with_block(def ():
|
|
189
281
|
for v'var i = 0; i < names.length; i++':
|
|
@@ -235,7 +327,10 @@ def function_definition(self, output, strip_first, as_expression):
|
|
|
235
327
|
if as_expression:
|
|
236
328
|
output.end_statement()
|
|
237
329
|
function_annotation(self, output, strip_first, anonfunc)
|
|
238
|
-
output.indent(), output.spaced(
|
|
330
|
+
output.indent(), output.spaced(
|
|
331
|
+
'return',
|
|
332
|
+
anonfunc
|
|
333
|
+
), output.end_statement()
|
|
239
334
|
output.set_indentation(orig_indent)
|
|
240
335
|
output.indent(), output.print('})()')
|
|
241
336
|
|
|
@@ -386,7 +481,10 @@ def print_function_call(self, output):
|
|
|
386
481
|
|
|
387
482
|
is_repeatable = is_new or not has_calls(self.expression)
|
|
388
483
|
if not is_repeatable:
|
|
389
|
-
output.assign('(ρσ_expr_temp'), print_this(
|
|
484
|
+
output.assign('(ρσ_expr_temp'), print_this(
|
|
485
|
+
self.expression,
|
|
486
|
+
output
|
|
487
|
+
), output.comma()
|
|
390
488
|
|
|
391
489
|
if has_kwargs:
|
|
392
490
|
if is_new:
|
package/src/output/loops.pyj
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __python__ import hash_literals
|
|
4
4
|
|
|
5
5
|
from ast import AST_BaseCall, AST_SymbolRef, AST_Array, AST_Unary, AST_Number, has_calls, AST_Seq, AST_ListComprehension, is_node_type
|
|
6
|
-
from output.stream import OutputStream
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
def unpack_tuple(elems, output, in_statement):
|
|
@@ -55,10 +54,7 @@ def is_simple_for(self):
|
|
|
55
54
|
not (is_node_type(self.init, AST_Array))):
|
|
56
55
|
a = self.object.args.args
|
|
57
56
|
l = a.length
|
|
58
|
-
if l < 3 or (
|
|
59
|
-
is_node_type(a[2].value, AST_Number) or (
|
|
60
|
-
is_node_type(a[2].value, AST_Unary) and a[2].value.operator is '-' and is_node_type(a[2].value.expression, AST_Number)
|
|
61
|
-
)):
|
|
57
|
+
if l < 3 or (is_node_type(a[2].value, AST_Number) or (is_node_type(a[2].value, AST_Unary) and a[2].value.operator is '-' and is_node_type(a[2].value.expression, AST_Number))):
|
|
62
58
|
if (l is 1 and not has_calls(a[0].value)) or (l > 1 and not has_calls(a[1].value)):
|
|
63
59
|
return True
|
|
64
60
|
return False
|
|
@@ -99,8 +95,24 @@ def print_for_loop_body(output):
|
|
|
99
95
|
|
|
100
96
|
def init_es6_itervar(output, itervar):
|
|
101
97
|
output.indent()
|
|
102
|
-
output.spaced(
|
|
103
|
-
|
|
98
|
+
output.spaced(
|
|
99
|
+
itervar,
|
|
100
|
+
'=',
|
|
101
|
+
'((typeof',
|
|
102
|
+
itervar + '[Symbol.iterator]',
|
|
103
|
+
'===',
|
|
104
|
+
'"function")',
|
|
105
|
+
'?',
|
|
106
|
+
'(' + itervar,
|
|
107
|
+
'instanceof',
|
|
108
|
+
'Map',
|
|
109
|
+
'?',
|
|
110
|
+
itervar + '.keys()',
|
|
111
|
+
':',
|
|
112
|
+
itervar + ')',
|
|
113
|
+
':',
|
|
114
|
+
'Object.keys(' + itervar + '))'
|
|
115
|
+
)
|
|
104
116
|
output.end_statement()
|
|
105
117
|
|
|
106
118
|
|
|
@@ -176,7 +188,13 @@ def print_for_in(self, output):
|
|
|
176
188
|
output.end_statement()
|
|
177
189
|
init_es6_itervar(output, itervar)
|
|
178
190
|
output.indent()
|
|
179
|
-
output.spaced(
|
|
191
|
+
output.spaced(
|
|
192
|
+
'for',
|
|
193
|
+
'(var',
|
|
194
|
+
'ρσ_Index' + output.index_counter,
|
|
195
|
+
'of',
|
|
196
|
+
itervar + ')'
|
|
197
|
+
)
|
|
180
198
|
|
|
181
199
|
output.space()
|
|
182
200
|
self._do_print_body(output)
|
|
@@ -184,7 +202,11 @@ def print_for_in(self, output):
|
|
|
184
202
|
|
|
185
203
|
def print_list_comprehension(self, output):
|
|
186
204
|
tname = self.constructor.name.slice(4)
|
|
187
|
-
result_obj = {
|
|
205
|
+
result_obj = {
|
|
206
|
+
'ListComprehension': '[]',
|
|
207
|
+
'DictComprehension': ('Object.create(null)' if self.is_jshash else '{}'),
|
|
208
|
+
'SetComprehension': 'ρσ_set()',
|
|
209
|
+
}[tname]
|
|
188
210
|
is_generator = tname is 'GeneratorComprehension'
|
|
189
211
|
if tname is 'DictComprehension':
|
|
190
212
|
if self.is_pydict:
|
package/src/tokenizer.pyj
CHANGED
|
@@ -503,9 +503,15 @@ def tokenizer(raw_text, filename, recover_errors):
|
|
|
503
503
|
parts = v'[quoted_string(string)]'
|
|
504
504
|
# Look ahead for consecutive string literals to concatenate (e.g. f'a'f'b' or f'a''b')
|
|
505
505
|
while True:
|
|
506
|
-
# Skip horizontal whitespace
|
|
507
|
-
while S.pos < S.text.length
|
|
508
|
-
|
|
506
|
+
# Skip horizontal whitespace; also skip newlines inside brackets where indentation is irrelevant
|
|
507
|
+
while S.pos < S.text.length:
|
|
508
|
+
wch = S.text.charAt(S.pos)
|
|
509
|
+
if wch is ' ' or wch is '\t':
|
|
510
|
+
next()
|
|
511
|
+
elif (wch is '\n' or wch is '\r') and not S.indentation_matters[-1]:
|
|
512
|
+
next()
|
|
513
|
+
else:
|
|
514
|
+
break
|
|
509
515
|
ch = S.text.charAt(S.pos)
|
|
510
516
|
if not ch:
|
|
511
517
|
break
|
|
@@ -676,8 +682,14 @@ def tokenizer(raw_text, filename, recover_errors):
|
|
|
676
682
|
# otherwise be parsed as a call expression "a"(...) after the tokenizer
|
|
677
683
|
# injects the f-string result as a parenthesised expression.
|
|
678
684
|
j = S.pos
|
|
679
|
-
while j < S.text.length
|
|
680
|
-
|
|
685
|
+
while j < S.text.length:
|
|
686
|
+
jch = S.text.charAt(j)
|
|
687
|
+
if jch is ' ' or jch is '\t':
|
|
688
|
+
j += 1
|
|
689
|
+
elif (jch is '\n' or jch is '\r') and not S.indentation_matters[-1]:
|
|
690
|
+
j += 1
|
|
691
|
+
else:
|
|
692
|
+
break
|
|
681
693
|
if j < S.text.length and is_identifier_start(S.text.charAt(j).charCodeAt(0)):
|
|
682
694
|
k = j
|
|
683
695
|
while k < S.text.length and is_identifier_char(S.text.charAt(k)):
|
package/test/str.pyj
CHANGED
|
@@ -65,6 +65,16 @@ def test_interpolation():
|
|
|
65
65
|
ae('a' f'{a}' 'b', 'a1b')
|
|
66
66
|
ae('a' f'{a}' f'{a}', 'a11')
|
|
67
67
|
ae(rf'raw{a}' f' and {a}', 'raw1 and 1')
|
|
68
|
+
# Test f-string + string concatenation across newlines inside brackets
|
|
69
|
+
ae(f'{a}'
|
|
70
|
+
"b", '1b')
|
|
71
|
+
ae(f'{a}'
|
|
72
|
+
f'{a}', '11')
|
|
73
|
+
ae('a'
|
|
74
|
+
f'{a}', 'a1')
|
|
75
|
+
ae(f'{a}'
|
|
76
|
+
"b"
|
|
77
|
+
f'{a}', '1b1')
|
|
68
78
|
|
|
69
79
|
somevar = 33
|
|
70
80
|
test('somevar=33', '{somevar=}', somevar=somevar)
|
package/tools/cli.mjs
CHANGED
|
@@ -318,11 +318,12 @@ command prompt.`);
|
|
|
318
318
|
opt("no_js", '', 'bool', false, `Do not display the compiled JavaScript before executing
|
|
319
319
|
it.`);
|
|
320
320
|
|
|
321
|
-
create_group('lint', "[input1.pyj
|
|
321
|
+
create_group('lint', "[input1.pyj dir1 ...]", `Run the RapydScript linter. This will find various
|
|
322
322
|
possible problems in the .pyj files you specify and
|
|
323
323
|
write messages about them to stdout. Use - to read from STDIN.
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
You can specify directories to recursively lint all .pyj
|
|
325
|
+
files within them. The main check it performs is for
|
|
326
|
+
unused/undefined symbols, like pyflakes does for python.`,
|
|
326
327
|
`In addition to the command line options listed below,
|
|
327
328
|
you can also control the linter in a couple of other ways.
|
|
328
329
|
|
package/tools/compile.mjs
CHANGED
|
@@ -111,6 +111,12 @@ export default async function(start_time, argv, base_path, src_path, lib_path) {
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
function write_to_stream(stream, data) {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
stream.write(data + '\n', 'utf8', err => err ? reject(err) : resolve());
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
async function write_output(js_output, output_stream) {
|
|
115
121
|
if (argv.source_map && output_stream) {
|
|
116
122
|
var segments = output_stream.get_source_map_segments();
|
|
@@ -123,11 +129,11 @@ export default async function(start_time, argv, base_path, src_path, lib_path) {
|
|
|
123
129
|
}
|
|
124
130
|
if (argv.output) {
|
|
125
131
|
// Node's filesystem module cannot write directly to /dev/stdout
|
|
126
|
-
if (argv.output == '/dev/stdout')
|
|
127
|
-
else if (argv.output == '/dev/stderr')
|
|
132
|
+
if (argv.output == '/dev/stdout') await write_to_stream(process.stdout, js_output);
|
|
133
|
+
else if (argv.output == '/dev/stderr') await write_to_stream(process.stderr, js_output);
|
|
128
134
|
else await fs.promises.writeFile(argv.output, js_output, "utf8");
|
|
129
135
|
} else if (!argv.execute){
|
|
130
|
-
|
|
136
|
+
await write_to_stream(process.stdout, js_output);
|
|
131
137
|
}
|
|
132
138
|
if (argv.execute) {
|
|
133
139
|
vm.runInNewContext(js_output, {'console':console, 'require':require}, {'filename':files[0]});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* vim:fileencoding=utf-8
|
|
2
|
+
*
|
|
3
|
+
* Copyright (C) 2015 Kovid Goyal <kovid at kovidgoyal.net>
|
|
4
|
+
*
|
|
5
|
+
* Distributed under terms of the BSD license
|
|
6
|
+
*
|
|
7
|
+
* Worker thread for parallel linting. Each worker owns its own compiler
|
|
8
|
+
* instance so parsing can run on multiple CPU cores simultaneously.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { parentPort } from 'worker_threads';
|
|
12
|
+
import { create_compiler } from './compiler.mjs';
|
|
13
|
+
import { read_config } from './ini.mjs';
|
|
14
|
+
import * as utils from './utils.mjs';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
|
|
18
|
+
const merge = utils.merge;
|
|
19
|
+
const has_prop = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
|
|
20
|
+
|
|
21
|
+
// Initialize the compiler first — lint.mjs reads globalThis.create_rapydscript_compiler
|
|
22
|
+
// at module evaluation time, so the global must be set before the dynamic import.
|
|
23
|
+
const compiler = await create_compiler();
|
|
24
|
+
globalThis.create_rapydscript_compiler = () => compiler;
|
|
25
|
+
|
|
26
|
+
// Safe to import now.
|
|
27
|
+
const { lint_code } = await import('./lint.mjs');
|
|
28
|
+
|
|
29
|
+
// Per-worker ini cache keyed by directory. Stores promises to deduplicate
|
|
30
|
+
// concurrent lookups for the same directory.
|
|
31
|
+
const ini_cache = {};
|
|
32
|
+
|
|
33
|
+
function get_ini(dir) {
|
|
34
|
+
if (!has_prop(ini_cache, dir)) {
|
|
35
|
+
ini_cache[dir] = read_config(dir).then(r => r.rapydscript || {});
|
|
36
|
+
}
|
|
37
|
+
return ini_cache[dir];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Signal to the main thread that this worker is ready to receive jobs.
|
|
41
|
+
parentPort.postMessage({ type: 'ready' });
|
|
42
|
+
|
|
43
|
+
parentPort.on('message', async (msg) => {
|
|
44
|
+
if (msg.type !== 'lint') return;
|
|
45
|
+
const { id, filename, base_builtins, base_noqa } = msg;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const code = await fs.promises.readFile(filename, 'utf-8');
|
|
49
|
+
|
|
50
|
+
const final_builtins = merge(base_builtins);
|
|
51
|
+
const final_noqa = merge(base_noqa);
|
|
52
|
+
|
|
53
|
+
const rl = await get_ini(path.dirname(filename));
|
|
54
|
+
const g = {};
|
|
55
|
+
(rl.globals || rl.builtins || '').split(',').forEach(function(x) { g[x.trim()] = true; });
|
|
56
|
+
Object.assign(final_builtins, g);
|
|
57
|
+
|
|
58
|
+
const ng = {};
|
|
59
|
+
(rl.noqa || '').split(',').forEach(function(x) { ng[x.trim()] = true; });
|
|
60
|
+
Object.assign(final_noqa, ng);
|
|
61
|
+
|
|
62
|
+
code.split('\n', 20).forEach(function(line) {
|
|
63
|
+
var lq = line.replace(/\s+/g, '');
|
|
64
|
+
if (lq.startsWith('#globals:')) {
|
|
65
|
+
(lq.split(':', 2)[1] || '').split(',').forEach(function(item) { final_builtins[item] = true; });
|
|
66
|
+
} else if (lq.startsWith('#noqa:')) {
|
|
67
|
+
(lq.split(':', 2)[1] || '').split(',').forEach(function(item) { final_noqa[item] = true; });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Suppress per-file output; the main thread reports in file order.
|
|
72
|
+
const messages = await lint_code(code, {
|
|
73
|
+
filename,
|
|
74
|
+
builtins: final_builtins,
|
|
75
|
+
noqa: final_noqa,
|
|
76
|
+
errorformat: false,
|
|
77
|
+
report: function() {},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Strip code_lines — it is never used by the CLI report functions and
|
|
81
|
+
// transferring it across threads would be wasteful.
|
|
82
|
+
const clean = messages.map(function(m) {
|
|
83
|
+
return {
|
|
84
|
+
filename: m.filename,
|
|
85
|
+
start_line: m.start_line,
|
|
86
|
+
start_col: m.start_col,
|
|
87
|
+
end_line: m.end_line,
|
|
88
|
+
end_col: m.end_col,
|
|
89
|
+
ident: m.ident,
|
|
90
|
+
message: m.message,
|
|
91
|
+
level: m.level,
|
|
92
|
+
name: m.name,
|
|
93
|
+
other_line: m.other_line,
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
parentPort.postMessage({ type: 'result', id, messages: clean });
|
|
98
|
+
} catch (e) {
|
|
99
|
+
parentPort.postMessage({ type: 'error', id, error: e.message || String(e) });
|
|
100
|
+
}
|
|
101
|
+
});
|