rapydscript-ng 0.7.24 → 0.8.1
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 +28 -0
- package/README.md +80 -26
- package/bin/build.ts +117 -0
- package/bin/package.json +1 -0
- package/bin/rapydscript +65 -62
- package/build_wheels.py +133 -0
- package/editor-plugins/README.md +80 -0
- package/editor-plugins/nvim.lua +321 -0
- package/local-agent.md +28 -0
- package/package.json +4 -5
- package/publish.py +27 -17
- package/release/baselib-plain-pretty.js +448 -326
- package/release/baselib-plain-ugly.js +3 -3
- package/release/compiler.js +9027 -8139
- package/release/signatures.json +27 -27
- package/release/stdlib_modules.json +1 -0
- package/src/ast.pyj +428 -280
- package/src/baselib-builtins.pyj +49 -28
- package/src/baselib-containers.pyj +241 -226
- package/src/baselib-errors.pyj +8 -1
- package/src/baselib-internal.pyj +38 -23
- package/src/baselib-itertools.pyj +13 -7
- package/src/baselib-str.pyj +59 -84
- package/src/compiler.pyj +4 -4
- package/src/errors.pyj +3 -4
- package/src/lib/aes.pyj +46 -32
- package/src/lib/elementmaker.pyj +11 -9
- package/src/lib/encodings.pyj +15 -5
- package/src/lib/gettext.pyj +9 -4
- package/src/lib/math.pyj +106 -45
- package/src/lib/operator.pyj +10 -10
- package/src/lib/pythonize.pyj +2 -1
- package/src/lib/random.pyj +28 -21
- package/src/lib/re.pyj +492 -19
- package/src/lib/traceback.pyj +171 -29
- package/src/lib/uuid.pyj +2 -2
- package/src/output/classes.pyj +28 -27
- package/src/output/codegen.pyj +87 -83
- package/src/output/comments.pyj +8 -8
- package/src/output/exceptions.pyj +20 -21
- package/src/output/functions.pyj +66 -64
- package/src/output/literals.pyj +24 -23
- package/src/output/loops.pyj +84 -142
- package/src/output/modules.pyj +55 -68
- package/src/output/operators.pyj +40 -29
- package/src/output/statements.pyj +21 -15
- package/src/output/stream.pyj +82 -56
- package/src/output/utils.pyj +12 -8
- package/src/parse.pyj +971 -620
- package/src/string_interpolation.pyj +5 -3
- package/src/tokenizer.pyj +188 -148
- package/src/utils.pyj +32 -15
- package/test/_treeshake_mod.pyj +30 -0
- package/test/_treeshake_side_effect.pyj +9 -0
- package/test/ast_serialization.pyj +392 -0
- package/test/async.pyj +95 -0
- package/test/baselib.pyj +1 -2
- package/test/embedded_compiler.pyj +57 -0
- package/test/fmt.pyj +291 -0
- package/test/generic.pyj +16 -3
- package/test/imports.pyj +8 -6
- package/test/internationalization.pyj +38 -37
- package/test/lint.pyj +120 -117
- package/test/lsp.pyj +257 -0
- package/test/repl.pyj +70 -65
- package/test/sourcemaps.pyj +315 -0
- package/test/starargs.pyj +46 -39
- package/test/str.pyj +8 -0
- package/test/traceback.pyj +263 -0
- package/test/treeshaking.pyj +181 -0
- package/test/web_repl_export.pyj +88 -0
- package/tools/ast_serialize.mjs +557 -0
- package/tools/cli.mjs +531 -0
- package/tools/compile.mjs +205 -0
- package/tools/compiler.mjs +159 -0
- package/tools/{completer.js → completer.mjs} +6 -6
- package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
- package/tools/export.js +109 -56
- package/tools/fmt.mjs +975 -0
- package/tools/{gettext.js → gettext.mjs} +46 -53
- package/tools/ini.mjs +175 -0
- package/tools/{lint.js → lint.mjs} +78 -55
- package/tools/lsp.mjs +964 -0
- package/tools/lsp_protocol.mjs +259 -0
- package/tools/lsp_symbols.mjs +418 -0
- package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
- package/tools/{repl.js → repl.mjs} +56 -42
- package/tools/{self.js → self.mjs} +71 -46
- package/tools/sourcemap.mjs +123 -0
- package/tools/test.mjs +252 -0
- package/tools/treeshake.mjs +400 -0
- package/tools/{utils.js → utils.mjs} +26 -23
- package/tools/{web_repl.js → web_repl.mjs} +15 -13
- package/tools/web_repl_export.mjs +97 -0
- package/tree-sitter/README.md +101 -0
- package/tree-sitter/grammar.js +992 -0
- package/tree-sitter/package.json +43 -0
- package/tree-sitter/queries/highlights.scm +232 -0
- package/tree-sitter/queries/indents.scm +64 -0
- package/tree-sitter/queries/injections.scm +14 -0
- package/tree-sitter/queries/locals.scm +108 -0
- package/tree-sitter/src/grammar.json +4976 -0
- package/tree-sitter/src/node-types.json +2901 -0
- package/tree-sitter/src/parser.c +196465 -0
- package/tree-sitter/src/scanner.c +294 -0
- package/tree-sitter/src/tree_sitter/alloc.h +54 -0
- package/tree-sitter/src/tree_sitter/array.h +330 -0
- package/tree-sitter/src/tree_sitter/parser.h +286 -0
- package/tree-sitter/test/corpus/chaining.txt +99 -0
- package/tree-sitter/test/corpus/classes.txt +147 -0
- package/tree-sitter/test/corpus/comprehensions.txt +155 -0
- package/tree-sitter/test/corpus/containers.txt +242 -0
- package/tree-sitter/test/corpus/control_flow.txt +361 -0
- package/tree-sitter/test/corpus/decorators.txt +64 -0
- package/tree-sitter/test/corpus/existential.txt +102 -0
- package/tree-sitter/test/corpus/functions.txt +293 -0
- package/tree-sitter/test/corpus/imports.txt +117 -0
- package/tree-sitter/test/corpus/literals.txt +135 -0
- package/tree-sitter/test/corpus/operators.txt +296 -0
- package/tree-sitter/test/corpus/statements.txt +189 -0
- package/tree-sitter/test/corpus/strings.txt +90 -0
- package/tree-sitter/test/corpus/subscripts.txt +227 -0
- package/tree-sitter/tree-sitter.json +36 -0
- package/web-repl/env.js +35 -23
- package/web-repl/main.js +8 -8
- package/bin/export +0 -75
- package/bin/web-repl-export +0 -102
- package/tools/cli.js +0 -523
- package/tools/compile.js +0 -184
- package/tools/compiler.js +0 -84
- package/tools/ini.js +0 -65
- package/tools/test.js +0 -110
package/tools/fmt.mjs
ADDED
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
/* vim:fileencoding=utf-8
|
|
2
|
+
*
|
|
3
|
+
* fmt.mjs -- A PEP8 style code formatter for RapydScript source code.
|
|
4
|
+
*
|
|
5
|
+
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
|
6
|
+
*
|
|
7
|
+
* Distributed under terms of the BSD license.
|
|
8
|
+
*
|
|
9
|
+
* The formatter deliberately does not reuse the compiler's tokenizer because
|
|
10
|
+
* that tokenizer is lossy for a formatter: it decodes string escapes, converts
|
|
11
|
+
* numbers to JavaScript values, turns regexps into RegExp objects, rewrites
|
|
12
|
+
* f-strings by mutating the source buffer and maps operators (and -> &&, is ->
|
|
13
|
+
* === etc.). Instead we use a small, purpose built, loss-less lexer that
|
|
14
|
+
* preserves the exact source lexeme of every token.
|
|
15
|
+
*
|
|
16
|
+
* RapydScript is close to, but not identical to, Python. In particular it
|
|
17
|
+
* supports multi-line anonymous functions, leading-dot call chaining, verbatim
|
|
18
|
+
* JavaScript string/regex literals and newline sensitive blocks. Reformatting
|
|
19
|
+
* these constructs incorrectly would change program semantics, so the formatter
|
|
20
|
+
* is conservative: "complex" logical lines (those containing anonymous
|
|
21
|
+
* functions, block colons that span lines, semicolon inlined blocks, leading
|
|
22
|
+
* dot chains, backslash continuations or multi-line string/regex literals) have
|
|
23
|
+
* their spacing/indentation/quotes normalized but their physical line structure
|
|
24
|
+
* preserved -- they are never reflowed onto a single line nor wrapped.
|
|
25
|
+
*/
|
|
26
|
+
"use strict";
|
|
27
|
+
|
|
28
|
+
import fs from 'fs';
|
|
29
|
+
import path from 'path';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
import { read_pyproject_config } from './ini.mjs';
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Lexer
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
var ATOMS = new Set(['True', 'False', 'None']);
|
|
38
|
+
// Keywords that stay keywords (word operators like and/or/not/in/is/new/del are
|
|
39
|
+
// classified as operators, mirroring the compiler's tokenizer).
|
|
40
|
+
var KEYWORDS = new Set([
|
|
41
|
+
'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'do',
|
|
42
|
+
'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import',
|
|
43
|
+
'nonlocal', 'pass', 'raise', 'return', 'yield', 'try', 'while', 'with',
|
|
44
|
+
]);
|
|
45
|
+
var WORD_OPS = new Set(['and', 'or', 'not', 'in', 'is', 'new', 'del', 'void', 'typeof', 'instanceof']);
|
|
46
|
+
// After these tokens a `/` begins a regular expression rather than division.
|
|
47
|
+
var KW_BEFORE_EXPR = new Set(['return', 'yield', 'raise', 'elif', 'else', 'if', 'await', 'in', 'assert', 'while']);
|
|
48
|
+
var PUNC_BEFORE_EXPR = new Set(['[', '{', '(', ',', '.', ';', ':']);
|
|
49
|
+
var PUNC_CHARS = new Set(['(', ')', '[', ']', '{', '}', ',', ';', ':', '?']);
|
|
50
|
+
var STRING_MOD = /^[vrufVRUF]+$/;
|
|
51
|
+
|
|
52
|
+
// Symbolic operators, ordered longest-first for greedy matching.
|
|
53
|
+
var OPERATORS = [
|
|
54
|
+
'>>>=', '>>>', '>>=', '<<=', '//=', '**=', '>>', '<<', '//', '**', '<=',
|
|
55
|
+
'>=', '==', '!=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '->',
|
|
56
|
+
'~', '+', '-', '*', '/', '%', '<', '>', '=', '&', '|', '^', '@', '!',
|
|
57
|
+
];
|
|
58
|
+
var OPERATOR_CHARS = new Set('+-*&%=<>!?|~^@/'.split(''));
|
|
59
|
+
|
|
60
|
+
function is_ws(c) { return c === ' ' || c === '\t' || c === '\f' || c === '\v' || c === ' '; }
|
|
61
|
+
function is_digit(c) { return c >= '0' && c <= '9'; }
|
|
62
|
+
function is_ident_start(c) {
|
|
63
|
+
if (c === '_' || c === '$') return true;
|
|
64
|
+
var k = c.charCodeAt(0);
|
|
65
|
+
return (k >= 65 && k <= 90) || (k >= 97 && k <= 122) || k > 127;
|
|
66
|
+
}
|
|
67
|
+
function is_ident_char(c) { return is_ident_start(c) || is_digit(c); }
|
|
68
|
+
|
|
69
|
+
function FormatError(message, line) {
|
|
70
|
+
var e = new Error(message + (line ? ' (line ' + line + ')' : ''));
|
|
71
|
+
e.is_format_error = true;
|
|
72
|
+
return e;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function lex(text) {
|
|
76
|
+
var n = text.length;
|
|
77
|
+
var pos = 0, line = 1;
|
|
78
|
+
var toks = [];
|
|
79
|
+
var prev_sig = null; // previous non-comment token, for regex/kind decisions
|
|
80
|
+
|
|
81
|
+
function regex_allowed() {
|
|
82
|
+
if (!prev_sig) return true;
|
|
83
|
+
if (prev_sig.type === 'op') return true;
|
|
84
|
+
if (prev_sig.type === 'keyword' && KW_BEFORE_EXPR.has(prev_sig.value)) return true;
|
|
85
|
+
if (prev_sig.type === 'punc' && PUNC_BEFORE_EXPR.has(prev_sig.value)) return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function consume_ws() {
|
|
90
|
+
var nlb = 0, had_ws = false, cont = false;
|
|
91
|
+
while (pos < n) {
|
|
92
|
+
var c = text[pos];
|
|
93
|
+
if (c === '\\' && text[pos + 1] === '\n') { pos += 2; line++; nlb++; cont = true; continue; }
|
|
94
|
+
if (c === '\n') { nlb++; line++; pos++; had_ws = true; continue; }
|
|
95
|
+
if (is_ws(c)) { had_ws = true; pos++; continue; }
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
// Leading whitespace of the current physical line.
|
|
99
|
+
var ls = pos;
|
|
100
|
+
while (ls > 0 && text[ls - 1] !== '\n') ls--;
|
|
101
|
+
var ie = ls;
|
|
102
|
+
while (ie < n && is_ws(text[ie])) ie++;
|
|
103
|
+
return { nlb: nlb, sp: (had_ws && nlb === 0), cont: cont, indent: text.slice(ls, ie) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function read_string_body(qpos) {
|
|
107
|
+
// qpos points at the opening quote (prefix already consumed). Advances pos
|
|
108
|
+
// past the closing quote and returns nothing; caller slices the lexeme.
|
|
109
|
+
var q = text[qpos];
|
|
110
|
+
if (text[qpos + 1] === q && text[qpos + 2] === q) {
|
|
111
|
+
pos = qpos + 3;
|
|
112
|
+
while (pos < n) {
|
|
113
|
+
if (text[pos] === '\\') { pos += 2; continue; }
|
|
114
|
+
if (text[pos] === q && text[pos + 1] === q && text[pos + 2] === q) { pos += 3; return; }
|
|
115
|
+
if (text[pos] === '\n') line++;
|
|
116
|
+
pos++;
|
|
117
|
+
}
|
|
118
|
+
throw FormatError('Unterminated triple-quoted string', line);
|
|
119
|
+
}
|
|
120
|
+
pos = qpos + 1;
|
|
121
|
+
while (pos < n) {
|
|
122
|
+
var c = text[pos];
|
|
123
|
+
if (c === '\\') { pos += 2; continue; }
|
|
124
|
+
if (c === q) { pos++; return; }
|
|
125
|
+
if (c === '\n') throw FormatError('End of line while scanning string literal', line);
|
|
126
|
+
pos++;
|
|
127
|
+
}
|
|
128
|
+
throw FormatError('Unterminated string', line);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function read_regexp() {
|
|
132
|
+
var start = pos;
|
|
133
|
+
pos++; // first '/'
|
|
134
|
+
if (text[pos] === '/') {
|
|
135
|
+
pos++;
|
|
136
|
+
if (text[pos] === '/') {
|
|
137
|
+
pos++; // verbose ///.../// regex
|
|
138
|
+
while (pos < n) {
|
|
139
|
+
if (text[pos] === '\\') { pos += 2; continue; }
|
|
140
|
+
if (text[pos] === '/' && text[pos + 1] === '/' && text[pos + 2] === '/') { pos += 3; break; }
|
|
141
|
+
if (text[pos] === '\n') line++;
|
|
142
|
+
pos++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// else: empty regexp //, nothing more to read before modifiers
|
|
146
|
+
} else {
|
|
147
|
+
var in_class = false;
|
|
148
|
+
while (pos < n) {
|
|
149
|
+
var c = text[pos];
|
|
150
|
+
if (c === '\\') { pos += 2; continue; }
|
|
151
|
+
if (c === '[') { in_class = true; pos++; continue; }
|
|
152
|
+
if (c === ']' && in_class) { in_class = false; pos++; continue; }
|
|
153
|
+
if (c === '/' && !in_class) { pos++; break; }
|
|
154
|
+
if (c === '\n') { throw FormatError('Unterminated regular expression', line); }
|
|
155
|
+
pos++;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
while (pos < n && /[a-zA-Z]/.test(text[pos])) pos++;
|
|
159
|
+
return text.slice(start, pos);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function read_number(from_dot) {
|
|
163
|
+
var start = pos;
|
|
164
|
+
if (!from_dot && text[pos] === '0' && (text[pos + 1] === 'x' || text[pos + 1] === 'X')) {
|
|
165
|
+
pos += 2;
|
|
166
|
+
while (pos < n && /[0-9a-fA-F]/.test(text[pos])) pos++;
|
|
167
|
+
} else if (!from_dot && text[pos] === '0' && (text[pos + 1] === 'b' || text[pos + 1] === 'B')) {
|
|
168
|
+
pos += 2;
|
|
169
|
+
while (pos < n && (text[pos] === '0' || text[pos] === '1')) pos++;
|
|
170
|
+
} else {
|
|
171
|
+
if (from_dot) { pos++; while (pos < n && is_digit(text[pos])) pos++; }
|
|
172
|
+
else {
|
|
173
|
+
while (pos < n && is_digit(text[pos])) pos++;
|
|
174
|
+
if (text[pos] === '.') { pos++; while (pos < n && is_digit(text[pos])) pos++; }
|
|
175
|
+
}
|
|
176
|
+
if (text[pos] === 'e' || text[pos] === 'E') {
|
|
177
|
+
pos++;
|
|
178
|
+
if (text[pos] === '+' || text[pos] === '-') pos++;
|
|
179
|
+
while (pos < n && is_digit(text[pos])) pos++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return text.slice(start, pos);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function match_operator() {
|
|
186
|
+
for (var i = 0; i < OPERATORS.length; i++) {
|
|
187
|
+
if (text.startsWith(OPERATORS[i], pos)) return OPERATORS[i];
|
|
188
|
+
}
|
|
189
|
+
return text[pos];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function push(type, value, ws) {
|
|
193
|
+
var t = {
|
|
194
|
+
type: type, value: value, nlb: ws.nlb, sp: ws.sp, cont: ws.cont,
|
|
195
|
+
line_indent: ws.indent, line: line,
|
|
196
|
+
};
|
|
197
|
+
toks.push(t);
|
|
198
|
+
if (type !== 'comment' && type !== 'shebang') prev_sig = t;
|
|
199
|
+
return t;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
while (pos < n) {
|
|
203
|
+
var ws = consume_ws();
|
|
204
|
+
if (pos >= n) break;
|
|
205
|
+
var c = text[pos];
|
|
206
|
+
|
|
207
|
+
if (c === '#') {
|
|
208
|
+
var c_start = pos;
|
|
209
|
+
var eol = text.indexOf('\n', pos);
|
|
210
|
+
if (eol === -1) eol = n;
|
|
211
|
+
var raw = text.slice(pos, eol);
|
|
212
|
+
pos = eol;
|
|
213
|
+
if (c_start === 0 && raw[1] === '!') push('shebang', raw, ws);
|
|
214
|
+
else push('comment', raw, ws);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (c === '"' || c === "'") {
|
|
218
|
+
var s = pos;
|
|
219
|
+
read_string_body(pos);
|
|
220
|
+
push('string', text.slice(s, pos), ws);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (is_ident_start(c)) {
|
|
224
|
+
var ws_start = pos;
|
|
225
|
+
while (pos < n && is_ident_char(text[pos])) pos++;
|
|
226
|
+
var word = text.slice(ws_start, pos);
|
|
227
|
+
if (STRING_MOD.test(word) && (text[pos] === '"' || text[pos] === "'")) {
|
|
228
|
+
var qpos = pos;
|
|
229
|
+
read_string_body(pos);
|
|
230
|
+
push('string', word + text.slice(qpos, pos), ws);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (ATOMS.has(word)) push('atom', word, ws);
|
|
234
|
+
else if (WORD_OPS.has(word)) push('op', word, ws);
|
|
235
|
+
else if (KEYWORDS.has(word)) push('keyword', word, ws);
|
|
236
|
+
else push('name', word, ws);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (is_digit(c)) { push('number', read_number(false), ws); continue; }
|
|
240
|
+
if (c === '.') {
|
|
241
|
+
if (is_digit(text[pos + 1])) { push('number', read_number(true), ws); continue; }
|
|
242
|
+
pos++; push('punc', '.', ws); continue;
|
|
243
|
+
}
|
|
244
|
+
if (c === '/') {
|
|
245
|
+
if (regex_allowed()) { push('regexp', read_regexp(), ws); continue; }
|
|
246
|
+
var op = match_operator(); pos += op.length; push('op', op, ws); continue;
|
|
247
|
+
}
|
|
248
|
+
if (PUNC_CHARS.has(c)) { pos++; push('punc', c, ws); continue; }
|
|
249
|
+
if (OPERATOR_CHARS.has(c)) { var op2 = match_operator(); pos += op2.length; push('op', op2, ws); continue; }
|
|
250
|
+
throw FormatError('Unexpected character «' + c + '»', line);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
push('eof', '', { nlb: 0, sp: false, cont: false, indent: '' });
|
|
254
|
+
return toks;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Token rendering (spacing engine)
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
function is_operand(p) {
|
|
262
|
+
if (!p) return false;
|
|
263
|
+
if (p.type === 'name' || p.type === 'number' || p.type === 'string' || p.type === 'atom' || p.type === 'regexp') return true;
|
|
264
|
+
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function is_call_paren(p) {
|
|
268
|
+
if (!p) return false;
|
|
269
|
+
if (p.type === 'name' || p.type === 'string' || p.type === 'atom') return true;
|
|
270
|
+
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function is_index_bracket(p) {
|
|
274
|
+
if (!p) return false;
|
|
275
|
+
if (p.type === 'name' || p.type === 'string' || p.type === 'number' || p.type === 'atom') return true;
|
|
276
|
+
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function is_unary_here(p, t) {
|
|
280
|
+
if (t.type === 'op') {
|
|
281
|
+
var v = t.value;
|
|
282
|
+
if (v === '+' || v === '-' || v === '~' || v === '*' || v === '**') return !is_operand(p);
|
|
283
|
+
if (v === '@' && p === null) return true;
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function separator(p, t, stack, prev_was_unary, sig_count, first_at) {
|
|
289
|
+
var pv = p.value, tv = t.value, pty = p.type, tty = t.type;
|
|
290
|
+
var top = stack.length ? stack[stack.length - 1] : null;
|
|
291
|
+
if (pty === 'punc' && (pv === '(' || pv === '[' || pv === '{')) return false;
|
|
292
|
+
if (tty === 'punc' && (tv === ')' || tv === ']' || tv === '}')) return false;
|
|
293
|
+
if (tv === ',' || tv === ';') return false;
|
|
294
|
+
if (tv === '.' || pv === '.') return false;
|
|
295
|
+
if (tv === '?' || pv === '?') return !!t.sp; // existential: preserve source spacing
|
|
296
|
+
if (tv === ':') return false;
|
|
297
|
+
if (pv === ':') { if (top && top.kind === 'index') return false; return true; }
|
|
298
|
+
if (pv === '@' && first_at && sig_count === 1) return false; // decorator
|
|
299
|
+
if (tv === '(' && is_call_paren(p)) return false;
|
|
300
|
+
if (tv === '[' && is_index_bracket(p)) return false;
|
|
301
|
+
if ((tv === '=' || pv === '=') && top && top.kind === 'call' && !top.in_def_body) return false; // kwarg / default
|
|
302
|
+
if (prev_was_unary) return false;
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function format_comment(raw) {
|
|
307
|
+
var m = raw.match(/^#+/)[0];
|
|
308
|
+
var rest = raw.slice(m.length).replace(/\s+$/, '');
|
|
309
|
+
if (rest === '') return m;
|
|
310
|
+
if (rest[0] !== ' ' && rest[0] !== '!') rest = ' ' + rest;
|
|
311
|
+
return m + rest;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function normalize_quotes(lex_val, preferred) {
|
|
315
|
+
var i = 0;
|
|
316
|
+
while (i < lex_val.length && /[vrufVRUF]/.test(lex_val[i])) i++;
|
|
317
|
+
var prefix = lex_val.slice(0, i);
|
|
318
|
+
var q = lex_val[i];
|
|
319
|
+
if (q !== "'" && q !== '"') return lex_val;
|
|
320
|
+
var lower = prefix.toLowerCase();
|
|
321
|
+
if (lower.indexOf('v') >= 0 || lower.indexOf('r') >= 0 || lower.indexOf('f') >= 0) return lex_val;
|
|
322
|
+
if (lex_val[i + 1] === q && lex_val[i + 2] === q) return lex_val; // triple-quoted: leave as-is
|
|
323
|
+
var target = preferred;
|
|
324
|
+
if (q === target) return lex_val;
|
|
325
|
+
var body = lex_val.slice(i + 1, lex_val.length - 1);
|
|
326
|
+
var nb = '', j = 0;
|
|
327
|
+
while (j < body.length) {
|
|
328
|
+
var c = body[j];
|
|
329
|
+
if (c === '\\' && j + 1 < body.length) {
|
|
330
|
+
var nx = body[j + 1];
|
|
331
|
+
if (nx === q) { nb += q; j += 2; continue; } // escaped old quote -> unescape
|
|
332
|
+
nb += '\\' + nx; j += 2; continue;
|
|
333
|
+
}
|
|
334
|
+
if (c === target) { nb += '\\' + target; j++; continue; } // must escape target
|
|
335
|
+
nb += c; j++;
|
|
336
|
+
}
|
|
337
|
+
var count = function (s) { var k = 0; for (var x = 0; x < s.length; x++) if (s[x] === '\\') k++; return k; };
|
|
338
|
+
if (count(nb) > count(body)) return lex_val; // don't increase escapes
|
|
339
|
+
return prefix + target + nb + target;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function token_text(t, opts) {
|
|
343
|
+
if (t.type === 'string') return normalize_quotes(t.value, opts.preferred);
|
|
344
|
+
return t.value;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Render a list of tokens to a string. When honor_breaks is true, source line
|
|
348
|
+
// breaks (nlb>0) are preserved as newlines + re-based indentation; otherwise
|
|
349
|
+
// everything is emitted on one line (reflow).
|
|
350
|
+
function render(toks, honor_breaks, src_base, new_base, opts, initial_stack) {
|
|
351
|
+
var out = '';
|
|
352
|
+
var stack = initial_stack ? initial_stack.slice() : [];
|
|
353
|
+
var prev_sig = null, prev_was_unary = false, sig_count = 0, first_at = false;
|
|
354
|
+
// after_def: true after seeing 'def' (and optionally a function name), so the
|
|
355
|
+
// next '(' can be identified as the def's parameter list.
|
|
356
|
+
// prev_closed_def_params: true when we just popped a def parameter list ')'.
|
|
357
|
+
// When the very next ':' is seen we mark the enclosing stack frame in_def_body
|
|
358
|
+
// so that '=' tokens inside the body are not suppressed as kwargs.
|
|
359
|
+
var after_def = false, prev_closed_def_params = false;
|
|
360
|
+
for (var k = 0; k < toks.length; k++) {
|
|
361
|
+
var t = toks[k];
|
|
362
|
+
var did_break = false;
|
|
363
|
+
if (k > 0 && honor_breaks && t.nlb > 0) {
|
|
364
|
+
out += (t.cont ? ' \\\n' : '\n');
|
|
365
|
+
var li = t.line_indent || '';
|
|
366
|
+
// Rebase indentation that shares the statement's base prefix; preserve
|
|
367
|
+
// dedented continuation lines verbatim (e.g. a leading-dot line that
|
|
368
|
+
// binds to an outer block, or a `.while` clause of a do/while loop).
|
|
369
|
+
if (li.startsWith(src_base)) out += new_base + li.slice(src_base.length);
|
|
370
|
+
else out += li;
|
|
371
|
+
did_break = true;
|
|
372
|
+
}
|
|
373
|
+
if (t.type === 'comment' || t.type === 'shebang') {
|
|
374
|
+
var ctext = (t.type === 'shebang') ? t.value : format_comment(t.value);
|
|
375
|
+
if (k === 0 || did_break) out += ctext;
|
|
376
|
+
else out += ' ' + ctext;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (k > 0 && !did_break && prev_sig !== null) {
|
|
380
|
+
if (separator(prev_sig, t, stack, prev_was_unary, sig_count, first_at)) out += ' ';
|
|
381
|
+
}
|
|
382
|
+
out += token_text(t, opts);
|
|
383
|
+
prev_was_unary = is_unary_here(prev_sig, t);
|
|
384
|
+
if (t.type === 'punc') {
|
|
385
|
+
if (t.value === '(') {
|
|
386
|
+
stack.push({ kind: is_call_paren(prev_sig) ? 'call' : 'group', is_def_params: after_def });
|
|
387
|
+
after_def = false; prev_closed_def_params = false;
|
|
388
|
+
} else if (t.value === '[') {
|
|
389
|
+
stack.push({ kind: is_index_bracket(prev_sig) ? 'index' : 'list' });
|
|
390
|
+
after_def = false; prev_closed_def_params = false;
|
|
391
|
+
} else if (t.value === '{') {
|
|
392
|
+
stack.push({ kind: 'dict' });
|
|
393
|
+
after_def = false; prev_closed_def_params = false;
|
|
394
|
+
} else if (t.value === ')' || t.value === ']' || t.value === '}') {
|
|
395
|
+
var popped = stack.length ? stack.pop() : null;
|
|
396
|
+
prev_closed_def_params = !!(popped && popped.is_def_params);
|
|
397
|
+
after_def = false;
|
|
398
|
+
} else if (t.value === ':') {
|
|
399
|
+
if (prev_closed_def_params && stack.length > 0) {
|
|
400
|
+
// Only mark in_def_body for multi-line def bodies. For inline
|
|
401
|
+
// defs (def():stmt; inside a call), the body is on the same
|
|
402
|
+
// line and subsequent same-depth tokens are still kwargs.
|
|
403
|
+
var next_is_newline = false;
|
|
404
|
+
for (var nk = k + 1; nk < toks.length; nk++) {
|
|
405
|
+
if (toks[nk].type === 'eof') break;
|
|
406
|
+
if (toks[nk].type !== 'comment' && toks[nk].type !== 'shebang') {
|
|
407
|
+
next_is_newline = toks[nk].nlb > 0; break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (next_is_newline) stack[stack.length - 1].in_def_body = true;
|
|
411
|
+
}
|
|
412
|
+
prev_closed_def_params = false; after_def = false;
|
|
413
|
+
} else {
|
|
414
|
+
after_def = false; prev_closed_def_params = false;
|
|
415
|
+
}
|
|
416
|
+
} else if (t.type === 'keyword' && t.value === 'def') {
|
|
417
|
+
after_def = true; prev_closed_def_params = false;
|
|
418
|
+
} else if (after_def && t.type === 'name') {
|
|
419
|
+
prev_closed_def_params = false; // named def: keep after_def for next '('
|
|
420
|
+
} else {
|
|
421
|
+
after_def = false; prev_closed_def_params = false;
|
|
422
|
+
}
|
|
423
|
+
if (sig_count === 0 && t.value === '@' && t.type === 'op') first_at = true;
|
|
424
|
+
prev_sig = t; sig_count++;
|
|
425
|
+
}
|
|
426
|
+
return out;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// Line wrapping (conservative)
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
function wrap_line(code, level, opts) {
|
|
434
|
+
// Locate the top-level (depth 0 -> 1) bracket group with commas and the
|
|
435
|
+
// largest span, then explode it across multiple lines with hanging indent.
|
|
436
|
+
var depth = 0, open_stack = [], groups = [];
|
|
437
|
+
for (var i = 0; i < code.length; i++) {
|
|
438
|
+
var v = code[i].value, ty = code[i].type;
|
|
439
|
+
if (ty === 'punc' && (v === '(' || v === '[' || v === '{')) { if (depth === 0) open_stack.push({ open: i, ch: v }); depth++; }
|
|
440
|
+
else if (ty === 'punc' && (v === ')' || v === ']' || v === '}')) { depth--; if (depth === 0 && open_stack.length) { var g = open_stack.pop(); g.close = i; groups.push(g); } }
|
|
441
|
+
}
|
|
442
|
+
var best = null;
|
|
443
|
+
for (var gi = 0; gi < groups.length; gi++) {
|
|
444
|
+
var grp = groups[gi];
|
|
445
|
+
if (grp.close === undefined) continue;
|
|
446
|
+
var d = 0, has_comma = false, is_comprehension = false;
|
|
447
|
+
for (var x = grp.open + 1; x < grp.close; x++) {
|
|
448
|
+
var vv = code[x].value, tt = code[x].type;
|
|
449
|
+
if (tt === 'punc' && (vv === '(' || vv === '[' || vv === '{')) d++;
|
|
450
|
+
else if (tt === 'punc' && (vv === ')' || vv === ']' || vv === '}')) d--;
|
|
451
|
+
else if (d === 0 && tt === 'punc' && vv === ',') has_comma = true;
|
|
452
|
+
else if (d === 0 && tt === 'keyword' && vv === 'for') is_comprehension = true;
|
|
453
|
+
}
|
|
454
|
+
// In a comprehension/generator the top-level commas are tuple unpacking,
|
|
455
|
+
// not element separators, so it is not safe to explode on them.
|
|
456
|
+
if (!has_comma || is_comprehension) continue;
|
|
457
|
+
var span = grp.close - grp.open;
|
|
458
|
+
if (!best || span > best.span) best = { open: grp.open, close: grp.close, ch: grp.ch, span: span };
|
|
459
|
+
}
|
|
460
|
+
if (!best) return null;
|
|
461
|
+
|
|
462
|
+
var base_indent = ' '.repeat(4 * level);
|
|
463
|
+
var child_indent = ' '.repeat(4 * (level + 1));
|
|
464
|
+
var open_ch = best.ch;
|
|
465
|
+
var seed_kind = (open_ch === '(') ? (is_call_paren(best.open > 0 ? code[best.open - 1] : null) ? 'call' : 'group')
|
|
466
|
+
: (open_ch === '[') ? (is_index_bracket(best.open > 0 ? code[best.open - 1] : null) ? 'index' : 'list')
|
|
467
|
+
: 'dict';
|
|
468
|
+
|
|
469
|
+
var head = code.slice(0, best.open + 1);
|
|
470
|
+
var head_str = base_indent + render(head, false, '', base_indent, opts);
|
|
471
|
+
|
|
472
|
+
var inner = code.slice(best.open + 1, best.close);
|
|
473
|
+
var elems = [], cur = [], dd = 0;
|
|
474
|
+
for (var y = 0; y < inner.length; y++) {
|
|
475
|
+
var tk = inner[y], iv = tk.value, it = tk.type;
|
|
476
|
+
if (it === 'punc' && (iv === '(' || iv === '[' || iv === '{')) { dd++; cur.push(tk); }
|
|
477
|
+
else if (it === 'punc' && (iv === ')' || iv === ']' || iv === '}')) { dd--; cur.push(tk); }
|
|
478
|
+
else if (dd === 0 && it === 'punc' && iv === ',') { elems.push(cur); cur = []; }
|
|
479
|
+
else cur.push(tk);
|
|
480
|
+
}
|
|
481
|
+
if (cur.length) elems.push(cur);
|
|
482
|
+
elems = elems.filter(function (e) { return e.length; });
|
|
483
|
+
if (!elems.length) return null;
|
|
484
|
+
|
|
485
|
+
var elem_lines = elems.map(function (e) { return child_indent + render(e, false, '', child_indent, opts, [{ kind: seed_kind }]); });
|
|
486
|
+
var body = elem_lines.join(',\n');
|
|
487
|
+
if (open_ch === '[' || open_ch === '{') body += ','; // magic trailing comma for literals only
|
|
488
|
+
|
|
489
|
+
var tail = code.slice(best.close);
|
|
490
|
+
var tail_str = base_indent + render(tail, false, '', base_indent, opts);
|
|
491
|
+
return head_str + '\n' + body + '\n' + tail_str;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
// Grouping into logical lines / elements
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
function make_stmt(tokens) {
|
|
499
|
+
var sig = tokens.filter(function (t) { return t.type !== 'comment' && t.type !== 'shebang'; });
|
|
500
|
+
var first_sig = sig[0], last_sig = sig[sig.length - 1];
|
|
501
|
+
var has_def_or_class = sig.some(function (t) { return t.type === 'keyword' && (t.value === 'def' || t.value === 'class'); });
|
|
502
|
+
var has_semicolon = sig.some(function (t) { return t.value === ';'; });
|
|
503
|
+
// A logical line that starts with `.` or that contains a physical line
|
|
504
|
+
// beginning with `.` is a leading-dot chain (or a do/while `.while` clause).
|
|
505
|
+
// These bind to an outer block by indentation, so we preserve their layout.
|
|
506
|
+
var leading_dot = (first_sig && first_sig.value === '.') ||
|
|
507
|
+
tokens.some(function (t, i) { return i > 0 && t.nlb > 0 && t.type === 'punc' && t.value === '.'; });
|
|
508
|
+
var used_backslash = tokens.some(function (t) { return t.cont; });
|
|
509
|
+
var multi_physical = tokens.some(function (t, i) { return i > 0 && t.nlb > 0; });
|
|
510
|
+
var colon_then_newline = false;
|
|
511
|
+
for (var i = 0; i < tokens.length - 1; i++) { if (tokens[i].value === ':' && tokens[i + 1].nlb > 0) { colon_then_newline = true; break; } }
|
|
512
|
+
var has_multiline_token = tokens.some(function (t) { return typeof t.value === 'string' && t.value.indexOf('\n') >= 0; });
|
|
513
|
+
var interior_comment = false;
|
|
514
|
+
for (var j = 0; j < tokens.length; j++) {
|
|
515
|
+
if (tokens[j].type === 'comment') {
|
|
516
|
+
if (j < tokens.length - 1) interior_comment = true;
|
|
517
|
+
else if (tokens[j].nlb > 0) interior_comment = true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
var complex = leading_dot || used_backslash || interior_comment || has_def_or_class ||
|
|
521
|
+
has_semicolon || has_multiline_token || (multi_physical && colon_then_newline);
|
|
522
|
+
|
|
523
|
+
var inline_body_colon = false, d = 0;
|
|
524
|
+
for (var s = 0; s < sig.length; s++) {
|
|
525
|
+
var v = sig[s].value, ty = sig[s].type;
|
|
526
|
+
if (ty === 'punc' && (v === '(' || v === '[' || v === '{')) d++;
|
|
527
|
+
else if (ty === 'punc' && (v === ')' || v === ']' || v === '}')) d--;
|
|
528
|
+
else if (d === 0 && v === ':' && s < sig.length - 1) inline_body_colon = true;
|
|
529
|
+
}
|
|
530
|
+
var wrappable = !complex && !has_def_or_class && !has_semicolon && !inline_body_colon;
|
|
531
|
+
var is_def_like = first_sig && ((first_sig.type === 'keyword' && (first_sig.value === 'def' || first_sig.value === 'class')) || first_sig.value === 'async');
|
|
532
|
+
var is_decorator = first_sig && first_sig.value === '@';
|
|
533
|
+
var ends_block = last_sig && last_sig.value === ':';
|
|
534
|
+
return {
|
|
535
|
+
kind: 'stmt', tokens: tokens, indent: tokens[0].line_indent || '',
|
|
536
|
+
blank_before: Math.max(0, tokens[0].nlb - 1), complex: complex, wrappable: wrappable,
|
|
537
|
+
is_def_like: !!is_def_like, is_decorator: !!is_decorator, ends_block: !!ends_block, level: 0,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function group(toks) {
|
|
542
|
+
var elements = [];
|
|
543
|
+
var cur = [], depth = 0;
|
|
544
|
+
function flush() { if (cur.length) { elements.push(make_stmt(cur)); cur = []; } }
|
|
545
|
+
for (var k = 0; k < toks.length; k++) {
|
|
546
|
+
var t = toks[k];
|
|
547
|
+
if (t.type === 'eof') break;
|
|
548
|
+
var is_boundary = depth === 0 && cur.length > 0 && t.nlb > 0 && !t.cont && !(t.value === '.' && t.type === 'punc');
|
|
549
|
+
if (is_boundary) flush();
|
|
550
|
+
if (depth === 0 && cur.length === 0 && (t.type === 'comment' || t.type === 'shebang') && (t.nlb > 0 || k === 0)) {
|
|
551
|
+
elements.push({
|
|
552
|
+
kind: 'comment', value: t.value, is_shebang: t.type === 'shebang',
|
|
553
|
+
indent: t.line_indent || '', blank_before: (k === 0 ? 0 : Math.max(0, t.nlb - 1)),
|
|
554
|
+
level: 0, ends_block: false,
|
|
555
|
+
});
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
cur.push(t);
|
|
559
|
+
if (t.type === 'punc') {
|
|
560
|
+
if (t.value === '(' || t.value === '[' || t.value === '{') depth++;
|
|
561
|
+
else if (t.value === ')' || t.value === ']' || t.value === '}') depth = Math.max(0, depth - 1);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
flush();
|
|
565
|
+
return elements;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function compute_levels(elements) {
|
|
569
|
+
var stack = [{ w: -1 }];
|
|
570
|
+
for (var i = 0; i < elements.length; i++) {
|
|
571
|
+
var el = elements[i];
|
|
572
|
+
if (el.kind !== 'stmt') continue;
|
|
573
|
+
var w = el.indent.length;
|
|
574
|
+
if (w > stack[stack.length - 1].w) stack.push({ w: w });
|
|
575
|
+
else {
|
|
576
|
+
while (stack.length > 1 && w < stack[stack.length - 1].w) stack.pop();
|
|
577
|
+
if (w > stack[stack.length - 1].w) stack.push({ w: w });
|
|
578
|
+
}
|
|
579
|
+
el.level = stack.length - 2;
|
|
580
|
+
if (el.level < 0) el.level = 0;
|
|
581
|
+
}
|
|
582
|
+
// Comments take the level of the following statement (or the previous one at EOF).
|
|
583
|
+
for (var c = 0; c < elements.length; c++) {
|
|
584
|
+
if (elements[c].kind !== 'comment') continue;
|
|
585
|
+
var lvl = null;
|
|
586
|
+
for (var f = c + 1; f < elements.length; f++) { if (elements[f].kind === 'stmt') { lvl = elements[f].level; break; } }
|
|
587
|
+
if (lvl === null) { for (var b = c - 1; b >= 0; b--) { if (elements[b].kind === 'stmt') { lvl = elements[b].level; break; } } }
|
|
588
|
+
elements[c].level = lvl === null ? 0 : lvl;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// Blank line policy helpers
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
function leads_def(elements, idx) {
|
|
597
|
+
var j = idx;
|
|
598
|
+
while (j < elements.length) {
|
|
599
|
+
var e = elements[j];
|
|
600
|
+
if (e.kind === 'comment') {
|
|
601
|
+
if (j + 1 < elements.length && elements[j + 1].blank_before === 0) { j++; continue; }
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
if (e.kind === 'stmt') {
|
|
605
|
+
if (e.is_decorator) { if (j + 1 < elements.length && elements[j + 1].blank_before === 0) { j++; continue; } return false; }
|
|
606
|
+
return !!e.is_def_like;
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function is_visual_def_start(elements, idx) {
|
|
614
|
+
if (!leads_def(elements, idx)) return false;
|
|
615
|
+
if (idx === 0) return true;
|
|
616
|
+
var prev = elements[idx - 1], el = elements[idx];
|
|
617
|
+
if (el.blank_before === 0 && (prev.kind === 'comment' || (prev.kind === 'stmt' && prev.is_decorator))) return false;
|
|
618
|
+
return true;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ---------------------------------------------------------------------------
|
|
622
|
+
// Import organization: __python__ group first, then stdlib group, then other group
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
|
|
625
|
+
// Stdlib module list is generated by self.mjs into dev/stdlib_modules.json by
|
|
626
|
+
// reading src/lib/. Fall back to reading src/lib/ directly when the generated
|
|
627
|
+
// file is absent (e.g. running straight from the release/ tree).
|
|
628
|
+
var STDLIB_MODULES = (function () {
|
|
629
|
+
try {
|
|
630
|
+
return new Set(JSON.parse(fs.readFileSync(
|
|
631
|
+
new URL('../dev/stdlib_modules.json', import.meta.url), 'utf-8')));
|
|
632
|
+
} catch (e) { /* generated file absent; fall through */ }
|
|
633
|
+
try {
|
|
634
|
+
return new Set(fs.readdirSync(fileURLToPath(new URL('../src/lib/', import.meta.url)))
|
|
635
|
+
.filter(function (n) { return n.slice(-4) === '.pyj'; })
|
|
636
|
+
.map(function (n) { return n.slice(0, -4); }));
|
|
637
|
+
} catch (e) { return new Set(); }
|
|
638
|
+
}());
|
|
639
|
+
|
|
640
|
+
function is_import_stmt(el) {
|
|
641
|
+
if (el.kind !== 'stmt') return false;
|
|
642
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
643
|
+
if (!sig.length) return false;
|
|
644
|
+
return sig[0].type === 'keyword' && (sig[0].value === 'import' || sig[0].value === 'from');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function is_python_builtin_import(el) {
|
|
648
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
649
|
+
if (sig.length < 3) return false;
|
|
650
|
+
return sig[0].type === 'keyword' && sig[0].value === 'from' &&
|
|
651
|
+
sig[1].type === 'name' && sig[1].value === '__python__' &&
|
|
652
|
+
sig[2].type === 'keyword' && sig[2].value === 'import';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function import_top_module(el) {
|
|
656
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
657
|
+
var i = 1; // skip leading 'import' or 'from'
|
|
658
|
+
while (i < sig.length && sig[i].type === 'punc' && sig[i].value === '.') i++; // skip relative dots
|
|
659
|
+
if (i < sig.length && sig[i].type === 'name') return sig[i].value;
|
|
660
|
+
return '';
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Returns a string sort key: '0\0<module>\0' for 'import X', '1\0<module>\0<names>' for 'from X import Y'.
|
|
664
|
+
// This puts bare imports before from-imports (isort convention), then sorts alphabetically.
|
|
665
|
+
function import_sort_key(el) {
|
|
666
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
667
|
+
if (!sig.length) return '2\0\0';
|
|
668
|
+
var is_from = sig[0].value === 'from';
|
|
669
|
+
var i = 1, dots = '', mod_parts = [];
|
|
670
|
+
while (i < sig.length && sig[i].type === 'punc' && sig[i].value === '.') { dots += '.'; i++; }
|
|
671
|
+
while (i < sig.length && !(sig[i].type === 'keyword' && sig[i].value === 'import')) {
|
|
672
|
+
if (sig[i].type === 'name') mod_parts.push(sig[i].value);
|
|
673
|
+
i++;
|
|
674
|
+
}
|
|
675
|
+
var mod = (dots + mod_parts.join('.')).toLowerCase();
|
|
676
|
+
if (!is_from) return '0\0' + mod + '\0';
|
|
677
|
+
i++; // skip 'import' keyword
|
|
678
|
+
var names = [];
|
|
679
|
+
while (i < sig.length) { if (sig[i].type === 'name') names.push(sig[i].value.toLowerCase()); i++; }
|
|
680
|
+
return '1\0' + mod + '\0' + names.join(',');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function organize_import_elements(elements) {
|
|
684
|
+
// Identify the contiguous top-level import block: from the first level-0 import
|
|
685
|
+
// statement to the last, stopping at the first level-0 non-import statement.
|
|
686
|
+
var block_start = -1, block_end = -1;
|
|
687
|
+
for (var i = 0; i < elements.length; i++) {
|
|
688
|
+
var el = elements[i];
|
|
689
|
+
if (el.kind !== 'stmt' || el.level !== 0) continue;
|
|
690
|
+
if (is_import_stmt(el)) {
|
|
691
|
+
if (block_start < 0) block_start = i;
|
|
692
|
+
block_end = i;
|
|
693
|
+
} else {
|
|
694
|
+
if (block_start >= 0) break;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (block_start < 0) return elements;
|
|
698
|
+
|
|
699
|
+
var block = elements.slice(block_start, block_end + 1);
|
|
700
|
+
var imports = [], leading_comments = [], seen_import = false;
|
|
701
|
+
for (var j = 0; j < block.length; j++) {
|
|
702
|
+
var bel = block[j];
|
|
703
|
+
if (bel.kind === 'stmt') { seen_import = true; imports.push(bel); }
|
|
704
|
+
else if (bel.kind === 'comment' && !seen_import) leading_comments.push(bel);
|
|
705
|
+
// Comments after the first import are dropped (import organizers conventionally do this).
|
|
706
|
+
}
|
|
707
|
+
if (!imports.length) return elements;
|
|
708
|
+
|
|
709
|
+
var python = [], stdlib = [], other = [];
|
|
710
|
+
for (var k = 0; k < imports.length; k++) {
|
|
711
|
+
if (is_python_builtin_import(imports[k])) python.push(imports[k]);
|
|
712
|
+
else if (STDLIB_MODULES.has(import_top_module(imports[k]))) stdlib.push(imports[k]);
|
|
713
|
+
else other.push(imports[k]);
|
|
714
|
+
}
|
|
715
|
+
function cmp(a, b) { var ka = import_sort_key(a), kb = import_sort_key(b); return ka < kb ? -1 : ka > kb ? 1 : 0; }
|
|
716
|
+
python.sort(cmp);
|
|
717
|
+
stdlib.sort(cmp);
|
|
718
|
+
other.sort(cmp);
|
|
719
|
+
|
|
720
|
+
var new_block = [], first_blank = block[0].blank_before || 0;
|
|
721
|
+
for (var lc = 0; lc < leading_comments.length; lc++) {
|
|
722
|
+
var cm = Object.assign({}, leading_comments[lc]);
|
|
723
|
+
if (lc === 0) cm.blank_before = first_blank;
|
|
724
|
+
new_block.push(cm);
|
|
725
|
+
first_blank = 0;
|
|
726
|
+
}
|
|
727
|
+
for (var p = 0; p < python.length; p++) {
|
|
728
|
+
var pi = Object.assign({}, python[p]);
|
|
729
|
+
pi.blank_before = (p === 0) ? first_blank : 0;
|
|
730
|
+
new_block.push(pi);
|
|
731
|
+
first_blank = 0;
|
|
732
|
+
}
|
|
733
|
+
var has_python = python.length > 0;
|
|
734
|
+
for (var s = 0; s < stdlib.length; s++) {
|
|
735
|
+
var si = Object.assign({}, stdlib[s]);
|
|
736
|
+
si.blank_before = (s === 0 && has_python) ? 1 : (s === 0 ? first_blank : 0);
|
|
737
|
+
new_block.push(si);
|
|
738
|
+
first_blank = 0;
|
|
739
|
+
}
|
|
740
|
+
var has_stdlib_or_python = stdlib.length > 0 || has_python;
|
|
741
|
+
for (var o = 0; o < other.length; o++) {
|
|
742
|
+
var oi = Object.assign({}, other[o]);
|
|
743
|
+
oi.blank_before = (o === 0 && has_stdlib_or_python) ? 1 : (o === 0 ? first_blank : 0);
|
|
744
|
+
new_block.push(oi);
|
|
745
|
+
}
|
|
746
|
+
return elements.slice(0, block_start).concat(new_block).concat(elements.slice(block_end + 1));
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ---------------------------------------------------------------------------
|
|
750
|
+
// Element rendering + assembly
|
|
751
|
+
// ---------------------------------------------------------------------------
|
|
752
|
+
|
|
753
|
+
function render_stmt(el, opts) {
|
|
754
|
+
var base_indent = ' '.repeat(4 * el.level);
|
|
755
|
+
if (el.complex) {
|
|
756
|
+
return base_indent + render(el.tokens, true, el.indent, base_indent, opts);
|
|
757
|
+
}
|
|
758
|
+
var comment = null, code = el.tokens;
|
|
759
|
+
if (code.length && code[code.length - 1].type === 'comment') { comment = code[code.length - 1]; code = code.slice(0, -1); }
|
|
760
|
+
var comment_str = comment ? ' ' + format_comment(comment.value) : '';
|
|
761
|
+
if (!opts.join_lines) {
|
|
762
|
+
// Preserve source line breaks unless a resulting line exceeds the limit,
|
|
763
|
+
// in which case fall through to the collapse+wrap path below.
|
|
764
|
+
var preserved = base_indent + render(code, true, el.indent, base_indent, opts);
|
|
765
|
+
var preserved_full = preserved + comment_str;
|
|
766
|
+
var plines = preserved_full.split('\n');
|
|
767
|
+
var fits = true;
|
|
768
|
+
for (var pi = 0; pi < plines.length; pi++) {
|
|
769
|
+
if (plines[pi].length > opts.line_length) { fits = false; break; }
|
|
770
|
+
}
|
|
771
|
+
if (fits) return preserved_full;
|
|
772
|
+
}
|
|
773
|
+
var line = render(code, false, el.indent, base_indent, opts);
|
|
774
|
+
var full = base_indent + line;
|
|
775
|
+
if ((full.length + comment_str.length) > opts.line_length && el.wrappable) {
|
|
776
|
+
var wrapped = wrap_line(code, el.level, opts);
|
|
777
|
+
if (wrapped !== null) return comment_str ? wrapped + comment_str : wrapped;
|
|
778
|
+
}
|
|
779
|
+
return full + comment_str;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function render_elements(elements, opts) {
|
|
783
|
+
var result = '';
|
|
784
|
+
for (var idx = 0; idx < elements.length; idx++) {
|
|
785
|
+
var el = elements[idx];
|
|
786
|
+
var etext = (el.kind === 'comment')
|
|
787
|
+
? (' '.repeat(4 * el.level) + (el.is_shebang ? el.value : format_comment(el.value)))
|
|
788
|
+
: render_stmt(el, opts);
|
|
789
|
+
var blanks = 0;
|
|
790
|
+
if (idx > 0) {
|
|
791
|
+
var prev = elements[idx - 1];
|
|
792
|
+
var cap = (el.level === 0) ? 2 : 1;
|
|
793
|
+
blanks = Math.min(el.blank_before, cap);
|
|
794
|
+
if (prev.ends_block) blanks = 0;
|
|
795
|
+
if (is_visual_def_start(elements, idx)) {
|
|
796
|
+
var req = (el.level === 0) ? 2 : 1;
|
|
797
|
+
if (!prev.ends_block) blanks = Math.max(blanks, req);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (idx === 0) result += etext;
|
|
801
|
+
else result += '\n' + '\n'.repeat(blanks) + etext;
|
|
802
|
+
}
|
|
803
|
+
if (result === '') return '';
|
|
804
|
+
return result + '\n';
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export function format_string(src, options) {
|
|
808
|
+
var opts = normalize_opts(options);
|
|
809
|
+
var text = src.replace(/\r\n?/g, '\n');
|
|
810
|
+
var toks = lex(text);
|
|
811
|
+
var elements = group(toks);
|
|
812
|
+
compute_levels(elements);
|
|
813
|
+
elements = organize_import_elements(elements);
|
|
814
|
+
return render_elements(elements, opts);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
export function organize_imports(src, options) {
|
|
818
|
+
var opts = normalize_opts(options);
|
|
819
|
+
var text = src.replace(/\r\n?/g, '\n');
|
|
820
|
+
var toks = lex(text);
|
|
821
|
+
var elements = group(toks);
|
|
822
|
+
compute_levels(elements);
|
|
823
|
+
elements = organize_import_elements(elements);
|
|
824
|
+
return render_elements(elements, opts);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function normalize_opts(options) {
|
|
828
|
+
options = options || {};
|
|
829
|
+
var ll = options.line_length;
|
|
830
|
+
if (typeof ll === 'string') ll = parseInt(ll, 10);
|
|
831
|
+
if (!ll || isNaN(ll) || ll < 1) ll = 80;
|
|
832
|
+
var pref = options.preferred;
|
|
833
|
+
if (!pref) {
|
|
834
|
+
var pq = options.preferred_quote;
|
|
835
|
+
pref = (pq === 'double' || pq === '"') ? '"' : "'";
|
|
836
|
+
}
|
|
837
|
+
return { line_length: ll, preferred: pref, join_lines: !!options.join_lines };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// ---------------------------------------------------------------------------
|
|
841
|
+
// check-only reporting
|
|
842
|
+
// ---------------------------------------------------------------------------
|
|
843
|
+
|
|
844
|
+
export function check_report(file, src, formatted, options) {
|
|
845
|
+
var opts = normalize_opts(options);
|
|
846
|
+
var errs = [];
|
|
847
|
+
if (formatted !== src.replace(/\r\n?/g, '\n')) errs.push(file + ': would be reformatted');
|
|
848
|
+
var lines = formatted.split('\n');
|
|
849
|
+
for (var i = 0; i < lines.length; i++) {
|
|
850
|
+
if (lines[i].length > opts.line_length) {
|
|
851
|
+
errs.push(file + ':' + (i + 1) + ': line exceeds ' + opts.line_length + ' characters (' + lines[i].length + ')');
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return errs;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// ---------------------------------------------------------------------------
|
|
858
|
+
// File collection + CLI
|
|
859
|
+
// ---------------------------------------------------------------------------
|
|
860
|
+
|
|
861
|
+
export async function collect_pyj_files(inputs) {
|
|
862
|
+
var files = [];
|
|
863
|
+
async function walk(list, from_dir) {
|
|
864
|
+
for (var i = 0; i < list.length; i++) {
|
|
865
|
+
var f = list[i];
|
|
866
|
+
var st;
|
|
867
|
+
try { st = await fs.promises.lstat(f); }
|
|
868
|
+
catch (e) {
|
|
869
|
+
if (from_dir && (e.code === 'EACCES' || e.code === 'EPERM')) continue;
|
|
870
|
+
throw new Error("can't access: " + f);
|
|
871
|
+
}
|
|
872
|
+
if (st.isDirectory()) {
|
|
873
|
+
var children;
|
|
874
|
+
try {
|
|
875
|
+
children = (await fs.promises.readdir(f)).sort().map(function (x) { return path.join(f, x); });
|
|
876
|
+
} catch (e) {
|
|
877
|
+
if (from_dir && (e.code === 'EACCES' || e.code === 'EPERM')) continue;
|
|
878
|
+
throw new Error("can't read directory: " + f);
|
|
879
|
+
}
|
|
880
|
+
await walk(children, true);
|
|
881
|
+
} else if (st.isFile()) {
|
|
882
|
+
if (from_dir) { if (f.endsWith('.pyj')) files.push(f); }
|
|
883
|
+
else files.push(f);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
await walk(inputs, false);
|
|
888
|
+
return files;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function read_stdin() {
|
|
892
|
+
var chunks = [];
|
|
893
|
+
process.stdin.setEncoding('utf-8');
|
|
894
|
+
await new Promise(function (resolve, reject) {
|
|
895
|
+
process.stdin.on('data', function (chunk) { chunks.push(chunk); });
|
|
896
|
+
process.stdin.on('end', resolve);
|
|
897
|
+
process.stdin.on('error', reject);
|
|
898
|
+
});
|
|
899
|
+
return chunks.join('');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function argv_has_flag(flag_names) {
|
|
903
|
+
var args = process.argv.slice(3); // skip: node, script path, mode
|
|
904
|
+
for (var i = 0; i < args.length; i++) {
|
|
905
|
+
var a = args[i];
|
|
906
|
+
if (a === '--') break;
|
|
907
|
+
for (var j = 0; j < flag_names.length; j++) {
|
|
908
|
+
var f = flag_names[j];
|
|
909
|
+
if (a === f || a.startsWith(f + '=')) return true;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export async function cli(argv, base_path, src_path, lib_path) {
|
|
916
|
+
var ll_from_cli = argv_has_flag(['--line-length', '--line_length', '-l']);
|
|
917
|
+
var q_from_cli = argv_has_flag(['--preferred-quote', '--preferred_quote', '-q']);
|
|
918
|
+
|
|
919
|
+
var effective_ll = ll_from_cli ? (parseInt(argv.line_length, 10) || 80) : null;
|
|
920
|
+
var effective_quote = q_from_cli ? argv.preferred_quote : null;
|
|
921
|
+
|
|
922
|
+
if (!ll_from_cli || !q_from_cli) {
|
|
923
|
+
var pyconf = await read_pyproject_config(process.cwd());
|
|
924
|
+
if (!ll_from_cli && pyconf.line_length) effective_ll = pyconf.line_length;
|
|
925
|
+
if (!q_from_cli && pyconf.preferred_quote) effective_quote = pyconf.preferred_quote;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (!effective_ll) effective_ll = 80;
|
|
929
|
+
if (!effective_quote) effective_quote = 'single';
|
|
930
|
+
|
|
931
|
+
var opts = normalize_opts({ line_length: effective_ll, preferred_quote: effective_quote, join_lines: argv.join_lines });
|
|
932
|
+
var inputs = (argv.files || []).slice();
|
|
933
|
+
|
|
934
|
+
if (inputs.length === 0) {
|
|
935
|
+
var src = await read_stdin();
|
|
936
|
+
var out;
|
|
937
|
+
try { out = format_string(src, opts); }
|
|
938
|
+
catch (e) { console.error('Error formatting stdin: ' + (e.message || e)); process.exit(2); }
|
|
939
|
+
process.stdout.write(out);
|
|
940
|
+
process.exit(0);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
var files;
|
|
944
|
+
try { files = await collect_pyj_files(inputs); }
|
|
945
|
+
catch (e) { console.error('ERROR: ' + (e.message || e)); process.exit(2); }
|
|
946
|
+
|
|
947
|
+
var had_errors = false;
|
|
948
|
+
for (var i = 0; i < files.length; i++) {
|
|
949
|
+
var f = files[i];
|
|
950
|
+
var code;
|
|
951
|
+
try { code = await fs.promises.readFile(f, 'utf-8'); }
|
|
952
|
+
catch (e) { console.error("ERROR: can't read file: " + f); process.exit(2); }
|
|
953
|
+
var formatted;
|
|
954
|
+
try { formatted = format_string(code, opts); }
|
|
955
|
+
catch (e) { console.error(f + ': ' + (e.message || e)); had_errors = true; continue; }
|
|
956
|
+
if (argv.check_only) {
|
|
957
|
+
var errs = check_report(f, code, formatted, opts);
|
|
958
|
+
if (errs.length) { had_errors = true; errs.forEach(function (m) { console.error(m); }); }
|
|
959
|
+
} else {
|
|
960
|
+
if (formatted !== code.replace(/\r\n?/g, '\n')) {
|
|
961
|
+
try { await fs.promises.writeFile(f, formatted); }
|
|
962
|
+
catch (e) { console.error("ERROR: can't write file: " + f); process.exit(2); }
|
|
963
|
+
console.log('reformatted ' + f);
|
|
964
|
+
}
|
|
965
|
+
var flines = formatted.split('\n');
|
|
966
|
+
for (var li = 0; li < flines.length; li++) {
|
|
967
|
+
if (flines[li].length > opts.line_length) {
|
|
968
|
+
console.error(f + ':' + (li + 1) + ': line exceeds ' + opts.line_length + ' characters (' + flines[li].length + ')');
|
|
969
|
+
had_errors = true;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
process.exit(had_errors ? 1 : 0);
|
|
975
|
+
}
|