rapydscript-ng 0.8.0 → 0.8.2
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 +14 -0
- package/README.md +8 -0
- package/bin/build.ts +117 -0
- package/build_wheels.py +133 -0
- package/editor-plugins/README.md +80 -0
- package/editor-plugins/nvim.lua +321 -0
- package/package.json +1 -1
- package/publish.py +27 -17
- package/release/compiler.js +8125 -8093
- package/release/signatures.json +27 -27
- package/release/stdlib_modules.json +1 -0
- package/src/ast.pyj +379 -279
- package/src/baselib-builtins.pyj +47 -26
- package/src/baselib-containers.pyj +105 -92
- package/src/baselib-errors.pyj +8 -1
- package/src/baselib-internal.pyj +35 -20
- package/src/baselib-itertools.pyj +13 -7
- package/src/baselib-str.pyj +55 -80
- 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 +490 -17
- package/src/lib/traceback.pyj +7 -2
- package/src/lib/uuid.pyj +2 -2
- package/src/output/classes.pyj +28 -27
- package/src/output/codegen.pyj +80 -83
- package/src/output/comments.pyj +8 -8
- package/src/output/exceptions.pyj +20 -21
- package/src/output/functions.pyj +37 -26
- package/src/output/literals.pyj +14 -10
- package/src/output/loops.pyj +63 -59
- package/src/output/modules.pyj +52 -23
- package/src/output/operators.pyj +40 -29
- package/src/output/statements.pyj +20 -14
- package/src/output/stream.pyj +33 -34
- package/src/output/utils.pyj +12 -8
- package/src/parse.pyj +234 -233
- package/src/string_interpolation.pyj +5 -3
- package/src/tokenizer.pyj +176 -148
- package/src/utils.pyj +31 -14
- package/test/fmt.pyj +94 -4
- package/test/generic.pyj +9 -0
- package/test/lsp.pyj +35 -0
- package/test/str.pyj +8 -0
- package/tools/cli.mjs +25 -4
- package/tools/compile.mjs +7 -3
- package/tools/compiler.mjs +34 -2
- package/tools/fmt.mjs +269 -22
- package/tools/ini.mjs +112 -1
- package/tools/lsp.mjs +56 -6
- package/tools/repl.mjs +5 -2
- package/tools/self.mjs +15 -0
- package/tools/test.mjs +100 -0
- package/tools/web_repl_export.mjs +4 -0
- package/tree-sitter/package.json +1 -1
- package/tree-sitter/tree-sitter.json +1 -1
- package/editor-plugins/nvim/rapydscript/README.md +0 -184
- package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
- package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +0 -10
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +0 -88
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +0 -279
- /package/tree-sitter/queries/{rapydscript/highlights.scm → highlights.scm} +0 -0
- /package/tree-sitter/queries/{rapydscript/indents.scm → indents.scm} +0 -0
- /package/tree-sitter/queries/{rapydscript/injections.scm → injections.scm} +0 -0
- /package/tree-sitter/queries/{rapydscript/locals.scm → locals.scm} +0 -0
package/tools/fmt.mjs
CHANGED
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
import fs from 'fs';
|
|
29
29
|
import path from 'path';
|
|
30
|
+
import { fileURLToPath } from 'url';
|
|
31
|
+
import { read_pyproject_config } from './ini.mjs';
|
|
30
32
|
|
|
31
33
|
// ---------------------------------------------------------------------------
|
|
32
34
|
// Lexer
|
|
@@ -265,7 +267,6 @@ function is_operand(p) {
|
|
|
265
267
|
function is_call_paren(p) {
|
|
266
268
|
if (!p) return false;
|
|
267
269
|
if (p.type === 'name' || p.type === 'string' || p.type === 'atom') return true;
|
|
268
|
-
if (p.value === 'def') return true;
|
|
269
270
|
return p.value === ')' || p.value === ']' || p.value === '}';
|
|
270
271
|
}
|
|
271
272
|
|
|
@@ -297,7 +298,7 @@ function separator(p, t, stack, prev_was_unary, sig_count, first_at) {
|
|
|
297
298
|
if (pv === '@' && first_at && sig_count === 1) return false; // decorator
|
|
298
299
|
if (tv === '(' && is_call_paren(p)) return false;
|
|
299
300
|
if (tv === '[' && is_index_bracket(p)) return false;
|
|
300
|
-
if ((tv === '=' || pv === '=') && top && top.kind === 'call') return false; // kwarg / default
|
|
301
|
+
if ((tv === '=' || pv === '=') && top && top.kind === 'call' && !top.in_def_body) return false; // kwarg / default
|
|
301
302
|
if (prev_was_unary) return false;
|
|
302
303
|
return true;
|
|
303
304
|
}
|
|
@@ -350,6 +351,12 @@ function render(toks, honor_breaks, src_base, new_base, opts, initial_stack) {
|
|
|
350
351
|
var out = '';
|
|
351
352
|
var stack = initial_stack ? initial_stack.slice() : [];
|
|
352
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;
|
|
353
360
|
for (var k = 0; k < toks.length; k++) {
|
|
354
361
|
var t = toks[k];
|
|
355
362
|
var did_break = false;
|
|
@@ -375,10 +382,43 @@ function render(toks, honor_breaks, src_base, new_base, opts, initial_stack) {
|
|
|
375
382
|
out += token_text(t, opts);
|
|
376
383
|
prev_was_unary = is_unary_here(prev_sig, t);
|
|
377
384
|
if (t.type === 'punc') {
|
|
378
|
-
if (t.value === '(')
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
else if (t.value === '
|
|
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;
|
|
382
422
|
}
|
|
383
423
|
if (sig_count === 0 && t.value === '@' && t.type === 'op') first_at = true;
|
|
384
424
|
prev_sig = t; sig_count++;
|
|
@@ -578,6 +618,141 @@ function is_visual_def_start(elements, idx) {
|
|
|
578
618
|
return true;
|
|
579
619
|
}
|
|
580
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
|
+
var em = globalThis.__rapydscript_embedded__;
|
|
630
|
+
if (em) {
|
|
631
|
+
if (em.stdlib_modules) return new Set(em.stdlib_modules);
|
|
632
|
+
if (em.stdlib) return new Set(Object.keys(em.stdlib)
|
|
633
|
+
.filter(function (n) { return n.slice(-4) === '.pyj'; })
|
|
634
|
+
.map(function (n) { return n.slice(0, -4); }));
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
return new Set(JSON.parse(fs.readFileSync(
|
|
638
|
+
new URL('../dev/stdlib_modules.json', import.meta.url), 'utf-8')));
|
|
639
|
+
} catch (e) { /* generated file absent; fall through */ }
|
|
640
|
+
try {
|
|
641
|
+
return new Set(fs.readdirSync(fileURLToPath(new URL('../src/lib/', import.meta.url)))
|
|
642
|
+
.filter(function (n) { return n.slice(-4) === '.pyj'; })
|
|
643
|
+
.map(function (n) { return n.slice(0, -4); }));
|
|
644
|
+
} catch (e) { return new Set(); }
|
|
645
|
+
}());
|
|
646
|
+
|
|
647
|
+
function is_import_stmt(el) {
|
|
648
|
+
if (el.kind !== 'stmt') return false;
|
|
649
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
650
|
+
if (!sig.length) return false;
|
|
651
|
+
return sig[0].type === 'keyword' && (sig[0].value === 'import' || sig[0].value === 'from');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function is_python_builtin_import(el) {
|
|
655
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
656
|
+
if (sig.length < 3) return false;
|
|
657
|
+
return sig[0].type === 'keyword' && sig[0].value === 'from' &&
|
|
658
|
+
sig[1].type === 'name' && sig[1].value === '__python__' &&
|
|
659
|
+
sig[2].type === 'keyword' && sig[2].value === 'import';
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function import_top_module(el) {
|
|
663
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
664
|
+
var i = 1; // skip leading 'import' or 'from'
|
|
665
|
+
while (i < sig.length && sig[i].type === 'punc' && sig[i].value === '.') i++; // skip relative dots
|
|
666
|
+
if (i < sig.length && sig[i].type === 'name') return sig[i].value;
|
|
667
|
+
return '';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Returns a string sort key: '0\0<module>\0' for 'import X', '1\0<module>\0<names>' for 'from X import Y'.
|
|
671
|
+
// This puts bare imports before from-imports (isort convention), then sorts alphabetically.
|
|
672
|
+
function import_sort_key(el) {
|
|
673
|
+
var sig = el.tokens.filter(function (t) { return t.type !== 'comment'; });
|
|
674
|
+
if (!sig.length) return '2\0\0';
|
|
675
|
+
var is_from = sig[0].value === 'from';
|
|
676
|
+
var i = 1, dots = '', mod_parts = [];
|
|
677
|
+
while (i < sig.length && sig[i].type === 'punc' && sig[i].value === '.') { dots += '.'; i++; }
|
|
678
|
+
while (i < sig.length && !(sig[i].type === 'keyword' && sig[i].value === 'import')) {
|
|
679
|
+
if (sig[i].type === 'name') mod_parts.push(sig[i].value);
|
|
680
|
+
i++;
|
|
681
|
+
}
|
|
682
|
+
var mod = (dots + mod_parts.join('.')).toLowerCase();
|
|
683
|
+
if (!is_from) return '0\0' + mod + '\0';
|
|
684
|
+
i++; // skip 'import' keyword
|
|
685
|
+
var names = [];
|
|
686
|
+
while (i < sig.length) { if (sig[i].type === 'name') names.push(sig[i].value.toLowerCase()); i++; }
|
|
687
|
+
return '1\0' + mod + '\0' + names.join(',');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function organize_import_elements(elements) {
|
|
691
|
+
// Identify the contiguous top-level import block: from the first level-0 import
|
|
692
|
+
// statement to the last, stopping at the first level-0 non-import statement.
|
|
693
|
+
var block_start = -1, block_end = -1;
|
|
694
|
+
for (var i = 0; i < elements.length; i++) {
|
|
695
|
+
var el = elements[i];
|
|
696
|
+
if (el.kind !== 'stmt' || el.level !== 0) continue;
|
|
697
|
+
if (is_import_stmt(el)) {
|
|
698
|
+
if (block_start < 0) block_start = i;
|
|
699
|
+
block_end = i;
|
|
700
|
+
} else {
|
|
701
|
+
if (block_start >= 0) break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (block_start < 0) return elements;
|
|
705
|
+
|
|
706
|
+
var block = elements.slice(block_start, block_end + 1);
|
|
707
|
+
var imports = [], leading_comments = [], seen_import = false;
|
|
708
|
+
for (var j = 0; j < block.length; j++) {
|
|
709
|
+
var bel = block[j];
|
|
710
|
+
if (bel.kind === 'stmt') { seen_import = true; imports.push(bel); }
|
|
711
|
+
else if (bel.kind === 'comment' && !seen_import) leading_comments.push(bel);
|
|
712
|
+
// Comments after the first import are dropped (import organizers conventionally do this).
|
|
713
|
+
}
|
|
714
|
+
if (!imports.length) return elements;
|
|
715
|
+
|
|
716
|
+
var python = [], stdlib = [], other = [];
|
|
717
|
+
for (var k = 0; k < imports.length; k++) {
|
|
718
|
+
if (is_python_builtin_import(imports[k])) python.push(imports[k]);
|
|
719
|
+
else if (STDLIB_MODULES.has(import_top_module(imports[k]))) stdlib.push(imports[k]);
|
|
720
|
+
else other.push(imports[k]);
|
|
721
|
+
}
|
|
722
|
+
function cmp(a, b) { var ka = import_sort_key(a), kb = import_sort_key(b); return ka < kb ? -1 : ka > kb ? 1 : 0; }
|
|
723
|
+
python.sort(cmp);
|
|
724
|
+
stdlib.sort(cmp);
|
|
725
|
+
other.sort(cmp);
|
|
726
|
+
|
|
727
|
+
var new_block = [], first_blank = block[0].blank_before || 0;
|
|
728
|
+
for (var lc = 0; lc < leading_comments.length; lc++) {
|
|
729
|
+
var cm = Object.assign({}, leading_comments[lc]);
|
|
730
|
+
if (lc === 0) cm.blank_before = first_blank;
|
|
731
|
+
new_block.push(cm);
|
|
732
|
+
first_blank = 0;
|
|
733
|
+
}
|
|
734
|
+
for (var p = 0; p < python.length; p++) {
|
|
735
|
+
var pi = Object.assign({}, python[p]);
|
|
736
|
+
pi.blank_before = (p === 0) ? first_blank : 0;
|
|
737
|
+
new_block.push(pi);
|
|
738
|
+
first_blank = 0;
|
|
739
|
+
}
|
|
740
|
+
var has_python = python.length > 0;
|
|
741
|
+
for (var s = 0; s < stdlib.length; s++) {
|
|
742
|
+
var si = Object.assign({}, stdlib[s]);
|
|
743
|
+
si.blank_before = (s === 0 && has_python) ? 1 : (s === 0 ? first_blank : 0);
|
|
744
|
+
new_block.push(si);
|
|
745
|
+
first_blank = 0;
|
|
746
|
+
}
|
|
747
|
+
var has_stdlib_or_python = stdlib.length > 0 || has_python;
|
|
748
|
+
for (var o = 0; o < other.length; o++) {
|
|
749
|
+
var oi = Object.assign({}, other[o]);
|
|
750
|
+
oi.blank_before = (o === 0 && has_stdlib_or_python) ? 1 : (o === 0 ? first_blank : 0);
|
|
751
|
+
new_block.push(oi);
|
|
752
|
+
}
|
|
753
|
+
return elements.slice(0, block_start).concat(new_block).concat(elements.slice(block_end + 1));
|
|
754
|
+
}
|
|
755
|
+
|
|
581
756
|
// ---------------------------------------------------------------------------
|
|
582
757
|
// Element rendering + assembly
|
|
583
758
|
// ---------------------------------------------------------------------------
|
|
@@ -589,9 +764,21 @@ function render_stmt(el, opts) {
|
|
|
589
764
|
}
|
|
590
765
|
var comment = null, code = el.tokens;
|
|
591
766
|
if (code.length && code[code.length - 1].type === 'comment') { comment = code[code.length - 1]; code = code.slice(0, -1); }
|
|
767
|
+
var comment_str = comment ? ' ' + format_comment(comment.value) : '';
|
|
768
|
+
if (!opts.join_lines) {
|
|
769
|
+
// Preserve source line breaks unless a resulting line exceeds the limit,
|
|
770
|
+
// in which case fall through to the collapse+wrap path below.
|
|
771
|
+
var preserved = base_indent + render(code, true, el.indent, base_indent, opts);
|
|
772
|
+
var preserved_full = preserved + comment_str;
|
|
773
|
+
var plines = preserved_full.split('\n');
|
|
774
|
+
var fits = true;
|
|
775
|
+
for (var pi = 0; pi < plines.length; pi++) {
|
|
776
|
+
if (plines[pi].length > opts.line_length) { fits = false; break; }
|
|
777
|
+
}
|
|
778
|
+
if (fits) return preserved_full;
|
|
779
|
+
}
|
|
592
780
|
var line = render(code, false, el.indent, base_indent, opts);
|
|
593
781
|
var full = base_indent + line;
|
|
594
|
-
var comment_str = comment ? ' ' + format_comment(comment.value) : '';
|
|
595
782
|
if ((full.length + comment_str.length) > opts.line_length && el.wrappable) {
|
|
596
783
|
var wrapped = wrap_line(code, el.level, opts);
|
|
597
784
|
if (wrapped !== null) return comment_str ? wrapped + comment_str : wrapped;
|
|
@@ -599,13 +786,7 @@ function render_stmt(el, opts) {
|
|
|
599
786
|
return full + comment_str;
|
|
600
787
|
}
|
|
601
788
|
|
|
602
|
-
|
|
603
|
-
var opts = normalize_opts(options);
|
|
604
|
-
var text = src.replace(/\r\n?/g, '\n');
|
|
605
|
-
var toks = lex(text);
|
|
606
|
-
var elements = group(toks);
|
|
607
|
-
compute_levels(elements);
|
|
608
|
-
|
|
789
|
+
function render_elements(elements, opts) {
|
|
609
790
|
var result = '';
|
|
610
791
|
for (var idx = 0; idx < elements.length; idx++) {
|
|
611
792
|
var el = elements[idx];
|
|
@@ -630,6 +811,26 @@ export function format_string(src, options) {
|
|
|
630
811
|
return result + '\n';
|
|
631
812
|
}
|
|
632
813
|
|
|
814
|
+
export function format_string(src, options) {
|
|
815
|
+
var opts = normalize_opts(options);
|
|
816
|
+
var text = src.replace(/\r\n?/g, '\n');
|
|
817
|
+
var toks = lex(text);
|
|
818
|
+
var elements = group(toks);
|
|
819
|
+
compute_levels(elements);
|
|
820
|
+
elements = organize_import_elements(elements);
|
|
821
|
+
return render_elements(elements, opts);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export function organize_imports(src, options) {
|
|
825
|
+
var opts = normalize_opts(options);
|
|
826
|
+
var text = src.replace(/\r\n?/g, '\n');
|
|
827
|
+
var toks = lex(text);
|
|
828
|
+
var elements = group(toks);
|
|
829
|
+
compute_levels(elements);
|
|
830
|
+
elements = organize_import_elements(elements);
|
|
831
|
+
return render_elements(elements, opts);
|
|
832
|
+
}
|
|
833
|
+
|
|
633
834
|
function normalize_opts(options) {
|
|
634
835
|
options = options || {};
|
|
635
836
|
var ll = options.line_length;
|
|
@@ -640,7 +841,7 @@ function normalize_opts(options) {
|
|
|
640
841
|
var pq = options.preferred_quote;
|
|
641
842
|
pref = (pq === 'double' || pq === '"') ? '"' : "'";
|
|
642
843
|
}
|
|
643
|
-
return { line_length: ll, preferred: pref };
|
|
844
|
+
return { line_length: ll, preferred: pref, join_lines: !!options.join_lines };
|
|
644
845
|
}
|
|
645
846
|
|
|
646
847
|
// ---------------------------------------------------------------------------
|
|
@@ -671,9 +872,18 @@ export async function collect_pyj_files(inputs) {
|
|
|
671
872
|
var f = list[i];
|
|
672
873
|
var st;
|
|
673
874
|
try { st = await fs.promises.lstat(f); }
|
|
674
|
-
catch (e) {
|
|
875
|
+
catch (e) {
|
|
876
|
+
if (from_dir && (e.code === 'EACCES' || e.code === 'EPERM')) continue;
|
|
877
|
+
throw new Error("can't access: " + f);
|
|
878
|
+
}
|
|
675
879
|
if (st.isDirectory()) {
|
|
676
|
-
var children
|
|
880
|
+
var children;
|
|
881
|
+
try {
|
|
882
|
+
children = (await fs.promises.readdir(f)).sort().map(function (x) { return path.join(f, x); });
|
|
883
|
+
} catch (e) {
|
|
884
|
+
if (from_dir && (e.code === 'EACCES' || e.code === 'EPERM')) continue;
|
|
885
|
+
throw new Error("can't read directory: " + f);
|
|
886
|
+
}
|
|
677
887
|
await walk(children, true);
|
|
678
888
|
} else if (st.isFile()) {
|
|
679
889
|
if (from_dir) { if (f.endsWith('.pyj')) files.push(f); }
|
|
@@ -696,8 +906,36 @@ async function read_stdin() {
|
|
|
696
906
|
return chunks.join('');
|
|
697
907
|
}
|
|
698
908
|
|
|
909
|
+
function argv_has_flag(flag_names) {
|
|
910
|
+
var args = process.argv.slice(3); // skip: node, script path, mode
|
|
911
|
+
for (var i = 0; i < args.length; i++) {
|
|
912
|
+
var a = args[i];
|
|
913
|
+
if (a === '--') break;
|
|
914
|
+
for (var j = 0; j < flag_names.length; j++) {
|
|
915
|
+
var f = flag_names[j];
|
|
916
|
+
if (a === f || a.startsWith(f + '=')) return true;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
|
|
699
922
|
export async function cli(argv, base_path, src_path, lib_path) {
|
|
700
|
-
var
|
|
923
|
+
var ll_from_cli = argv_has_flag(['--line-length', '--line_length', '-l']);
|
|
924
|
+
var q_from_cli = argv_has_flag(['--preferred-quote', '--preferred_quote', '-q']);
|
|
925
|
+
|
|
926
|
+
var effective_ll = ll_from_cli ? (parseInt(argv.line_length, 10) || 80) : null;
|
|
927
|
+
var effective_quote = q_from_cli ? argv.preferred_quote : null;
|
|
928
|
+
|
|
929
|
+
if (!ll_from_cli || !q_from_cli) {
|
|
930
|
+
var pyconf = await read_pyproject_config(process.cwd());
|
|
931
|
+
if (!ll_from_cli && pyconf.line_length) effective_ll = pyconf.line_length;
|
|
932
|
+
if (!q_from_cli && pyconf.preferred_quote) effective_quote = pyconf.preferred_quote;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (!effective_ll) effective_ll = 80;
|
|
936
|
+
if (!effective_quote) effective_quote = 'single';
|
|
937
|
+
|
|
938
|
+
var opts = normalize_opts({ line_length: effective_ll, preferred_quote: effective_quote, join_lines: argv.join_lines });
|
|
701
939
|
var inputs = (argv.files || []).slice();
|
|
702
940
|
|
|
703
941
|
if (inputs.length === 0) {
|
|
@@ -725,10 +963,19 @@ export async function cli(argv, base_path, src_path, lib_path) {
|
|
|
725
963
|
if (argv.check_only) {
|
|
726
964
|
var errs = check_report(f, code, formatted, opts);
|
|
727
965
|
if (errs.length) { had_errors = true; errs.forEach(function (m) { console.error(m); }); }
|
|
728
|
-
} else
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
966
|
+
} else {
|
|
967
|
+
if (formatted !== code.replace(/\r\n?/g, '\n')) {
|
|
968
|
+
try { await fs.promises.writeFile(f, formatted); }
|
|
969
|
+
catch (e) { console.error("ERROR: can't write file: " + f); process.exit(2); }
|
|
970
|
+
console.log('reformatted ' + f);
|
|
971
|
+
}
|
|
972
|
+
var flines = formatted.split('\n');
|
|
973
|
+
for (var li = 0; li < flines.length; li++) {
|
|
974
|
+
if (flines[li].length > opts.line_length) {
|
|
975
|
+
console.error(f + ':' + (li + 1) + ': line exceeds ' + opts.line_length + ' characters (' + flines[li].length + ')');
|
|
976
|
+
had_errors = true;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
732
979
|
}
|
|
733
980
|
}
|
|
734
981
|
process.exit(had_errors ? 1 : 0);
|
package/tools/ini.mjs
CHANGED
|
@@ -60,5 +60,116 @@ async function read_config(toplevel_dir) {
|
|
|
60
60
|
return parse_ini_data(data);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// pyproject.toml reading
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
async function find_pyproject_toml(start_dir) {
|
|
68
|
+
var current = start_dir;
|
|
69
|
+
while (true) {
|
|
70
|
+
try {
|
|
71
|
+
return await fs.promises.readFile(path.join(current, 'pyproject.toml'), 'utf-8');
|
|
72
|
+
} catch (e) {
|
|
73
|
+
if (e.code !== 'ENOENT' && e.code !== 'EACCES' && e.code !== 'EPERM') throw e;
|
|
74
|
+
}
|
|
75
|
+
var parent = path.dirname(current);
|
|
76
|
+
if (parent === current) return null;
|
|
77
|
+
current = parent;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parse_toml_sections(text) {
|
|
82
|
+
// Minimal TOML parser: returns a flat map of dotted-section-name -> {key: value}.
|
|
83
|
+
// Only handles simple string and integer scalar values needed for config settings.
|
|
84
|
+
var sections = {};
|
|
85
|
+
var cur = null;
|
|
86
|
+
var lines = text.split(/\r?\n/);
|
|
87
|
+
for (var i = 0; i < lines.length; i++) {
|
|
88
|
+
var line = lines[i].trim();
|
|
89
|
+
if (!line || line[0] === '#') continue;
|
|
90
|
+
// Section header: [tool.ruff] or [tool.ruff.format] — not [[array]] tables.
|
|
91
|
+
var sec_m = line.match(/^\[([^\[\]]+)\]$/);
|
|
92
|
+
if (sec_m) {
|
|
93
|
+
var sec_name = sec_m[1].trim();
|
|
94
|
+
if (!sections[sec_name]) sections[sec_name] = {};
|
|
95
|
+
cur = sections[sec_name];
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!cur) continue;
|
|
99
|
+
var eq = line.indexOf('=');
|
|
100
|
+
if (eq < 0) continue;
|
|
101
|
+
var key = line.slice(0, eq).trim();
|
|
102
|
+
var rest = line.slice(eq + 1).trim();
|
|
103
|
+
if (!key) continue;
|
|
104
|
+
var val;
|
|
105
|
+
if (rest[0] === '"' || rest[0] === "'") {
|
|
106
|
+
var q = rest[0];
|
|
107
|
+
if (rest.slice(0, 3) === q + q + q) continue; // triple-quoted: skip
|
|
108
|
+
var j = 1;
|
|
109
|
+
while (j < rest.length && rest[j] !== q) {
|
|
110
|
+
if (rest[j] === '\\') j++;
|
|
111
|
+
j++;
|
|
112
|
+
}
|
|
113
|
+
val = rest.slice(1, j);
|
|
114
|
+
} else {
|
|
115
|
+
var hash = rest.indexOf('#');
|
|
116
|
+
if (hash >= 0) rest = rest.slice(0, hash).trim();
|
|
117
|
+
val = rest;
|
|
118
|
+
}
|
|
119
|
+
cur[key] = val;
|
|
120
|
+
}
|
|
121
|
+
return sections;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function pyproject_line_length(sections) {
|
|
125
|
+
var candidates = [
|
|
126
|
+
['tool.ruff', 'line-length'],
|
|
127
|
+
['tool.black', 'line-length'],
|
|
128
|
+
['tool.isort', 'line_length'],
|
|
129
|
+
['tool.isort', 'line-length'],
|
|
130
|
+
];
|
|
131
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
132
|
+
var sec = sections[candidates[i][0]];
|
|
133
|
+
if (sec && sec[candidates[i][1]] !== undefined) {
|
|
134
|
+
var ll = parseInt(sec[candidates[i][1]], 10);
|
|
135
|
+
if (!isNaN(ll) && ll > 0) return ll;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function pyproject_quote(sections) {
|
|
142
|
+
// tool.ruff.format.quote-style (skip 'preserve')
|
|
143
|
+
var rf = sections['tool.ruff.format'];
|
|
144
|
+
if (rf && rf['quote-style']) {
|
|
145
|
+
if (rf['quote-style'] === 'double') return 'double';
|
|
146
|
+
if (rf['quote-style'] === 'single') return 'single';
|
|
147
|
+
}
|
|
148
|
+
// tool.ruff.lint.flake8-quotes.inline-quotes
|
|
149
|
+
var fq = sections['tool.ruff.lint.flake8-quotes'];
|
|
150
|
+
if (fq && fq['inline-quotes']) {
|
|
151
|
+
if (fq['inline-quotes'] === 'double') return 'double';
|
|
152
|
+
if (fq['inline-quotes'] === 'single') return 'single';
|
|
153
|
+
}
|
|
154
|
+
// tool.flake8.inline-quotes
|
|
155
|
+
var flk = sections['tool.flake8'];
|
|
156
|
+
if (flk && flk['inline-quotes']) {
|
|
157
|
+
if (flk['inline-quotes'] === 'double') return 'double';
|
|
158
|
+
if (flk['inline-quotes'] === 'single') return 'single';
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function read_pyproject_config(start_dir) {
|
|
164
|
+
var text = await find_pyproject_toml(start_dir);
|
|
165
|
+
if (!text) return {};
|
|
166
|
+
var sections = parse_toml_sections(text);
|
|
167
|
+
var result = {};
|
|
168
|
+
var ll = pyproject_line_length(sections);
|
|
169
|
+
if (ll !== null) result.line_length = ll;
|
|
170
|
+
var q = pyproject_quote(sections);
|
|
171
|
+
if (q !== null) result.preferred_quote = q;
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export { read_config, read_pyproject_config };
|
package/tools/lsp.mjs
CHANGED
|
@@ -22,9 +22,10 @@ import crypto from 'crypto';
|
|
|
22
22
|
import { pathToFileURL, fileURLToPath } from 'url';
|
|
23
23
|
import * as utils from './utils.mjs';
|
|
24
24
|
import { lint_parsed, BUILTINS, WARN, ERROR, MESSAGES } from './lint.mjs';
|
|
25
|
-
import { format_string } from './fmt.mjs';
|
|
25
|
+
import { format_string, organize_imports } from './fmt.mjs';
|
|
26
26
|
import * as sym from './lsp_symbols.mjs';
|
|
27
27
|
import { create_connection, DocumentStore, TextDocument, ResponseError, ErrorCodes } from './lsp_protocol.mjs';
|
|
28
|
+
import { read_pyproject_config } from './ini.mjs';
|
|
28
29
|
|
|
29
30
|
const RapydScript = globalThis.create_rapydscript_compiler();
|
|
30
31
|
|
|
@@ -37,7 +38,7 @@ var CompletionItemKind = {
|
|
|
37
38
|
Class: 7, Interface: 8, Module: 9, Property: 10, Keyword: 14, Constant: 21,
|
|
38
39
|
};
|
|
39
40
|
var SymbolKindLSP = { File: 1, Module: 2, Class: 5, Method: 6, Property: 7, Field: 8, Function: 12, Variable: 13, Constant: 14 };
|
|
40
|
-
var CodeActionKind = { QuickFix: 'quickfix', SourceFixAll: 'source.fixAll', Source: 'source' };
|
|
41
|
+
var CodeActionKind = { QuickFix: 'quickfix', SourceFixAll: 'source.fixAll', Source: 'source', OrganizeImports: 'source.organizeImports' };
|
|
41
42
|
|
|
42
43
|
function kind_to_completion(kind) {
|
|
43
44
|
switch (kind) {
|
|
@@ -72,6 +73,7 @@ export function create_server_context(opts) {
|
|
|
72
73
|
libdir: opts.libdir || null, // stdlib dir (src/lib)
|
|
73
74
|
line_length: opts.line_length || 80,
|
|
74
75
|
preferred_quote: opts.preferred_quote || 'single',
|
|
76
|
+
join_lines: opts.join_lines || false,
|
|
75
77
|
docs: new DocumentStore(),
|
|
76
78
|
analysis_cache: Object.create(null), // key -> analysis
|
|
77
79
|
workspace_roots: opts.workspace_roots ? opts.workspace_roots.slice() : [],
|
|
@@ -96,6 +98,10 @@ export function apply_configuration(ctx, settings) {
|
|
|
96
98
|
ctx.preferred_quote = settings.preferredQuote;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
if (settings.joinLines !== undefined && settings.joinLines !== null) {
|
|
102
|
+
ctx.join_lines = !!settings.joinLines;
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
if (settings.importPath !== undefined && settings.importPath !== null) {
|
|
100
106
|
var new_dirs;
|
|
101
107
|
if (Array.isArray(settings.importPath)) {
|
|
@@ -276,7 +282,7 @@ function lint_message_to_diagnostic(doc, m) {
|
|
|
276
282
|
// ---------------------------------------------------------------------------
|
|
277
283
|
export function format_document(ctx, raw_text) {
|
|
278
284
|
var text = normalize(raw_text);
|
|
279
|
-
var formatted = format_string(text, { line_length: ctx.line_length, preferred_quote: ctx.preferred_quote });
|
|
285
|
+
var formatted = format_string(text, { line_length: ctx.line_length, preferred_quote: ctx.preferred_quote, join_lines: ctx.join_lines });
|
|
280
286
|
if (formatted === text) return [];
|
|
281
287
|
// Single edit that replaces the whole document.
|
|
282
288
|
var doc = new TextDocument('inmem', 'rapydscript', 0, text);
|
|
@@ -284,6 +290,18 @@ export function format_document(ctx, raw_text) {
|
|
|
284
290
|
return [{ range: { start: { line: 0, character: 0 }, end: end }, newText: formatted }];
|
|
285
291
|
}
|
|
286
292
|
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Organize imports
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
export function organize_imports_document(ctx, raw_text) {
|
|
297
|
+
var text = normalize(raw_text);
|
|
298
|
+
var organized = organize_imports(text, { line_length: ctx.line_length, preferred_quote: ctx.preferred_quote, join_lines: ctx.join_lines });
|
|
299
|
+
if (organized === text) return [];
|
|
300
|
+
var doc = new TextDocument('inmem', 'rapydscript', 0, text);
|
|
301
|
+
var end = doc.position_at(text.length);
|
|
302
|
+
return [{ range: { start: { line: 0, character: 0 }, end: end }, newText: organized }];
|
|
303
|
+
}
|
|
304
|
+
|
|
287
305
|
// ---------------------------------------------------------------------------
|
|
288
306
|
// Completion
|
|
289
307
|
// ---------------------------------------------------------------------------
|
|
@@ -722,6 +740,9 @@ export async function code_actions(ctx, uri, raw_text, range, diagnostics) {
|
|
|
722
740
|
// Source action: format the document.
|
|
723
741
|
var fmt_edits = format_document(ctx, a.text);
|
|
724
742
|
if (fmt_edits.length) actions.push({ title: 'Format document', kind: CodeActionKind.Source, edit: { changes: single_change(uri, fmt_edits) } });
|
|
743
|
+
// Source action: organize imports.
|
|
744
|
+
var org_edits = organize_imports_document(ctx, a.text);
|
|
745
|
+
if (org_edits.length) actions.push({ title: 'Organize imports', kind: CodeActionKind.OrganizeImports, edit: { changes: single_change(uri, org_edits) } });
|
|
725
746
|
return actions;
|
|
726
747
|
}
|
|
727
748
|
|
|
@@ -754,12 +775,41 @@ export async function document_symbols(ctx, uri, raw_text) {
|
|
|
754
775
|
// ===========================================================================
|
|
755
776
|
// Server / CLI
|
|
756
777
|
// ===========================================================================
|
|
778
|
+
function argv_has_flag(flag_names) {
|
|
779
|
+
var args = process.argv.slice(3); // skip: node, script path, mode
|
|
780
|
+
for (var i = 0; i < args.length; i++) {
|
|
781
|
+
var a = args[i];
|
|
782
|
+
if (a === '--') break;
|
|
783
|
+
for (var j = 0; j < flag_names.length; j++) {
|
|
784
|
+
var f = flag_names[j];
|
|
785
|
+
if (a === f || a.startsWith(f + '=')) return true;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
|
|
757
791
|
export async function cli(argv, base_path, src_path, lib_path) {
|
|
792
|
+
var ll_from_cli = argv_has_flag(['--line-length', '--line_length', '-l']);
|
|
793
|
+
var q_from_cli = argv_has_flag(['--preferred-quote', '--preferred_quote', '-q']);
|
|
794
|
+
|
|
795
|
+
var effective_ll = ll_from_cli ? (parseInt(argv.line_length, 10) || 80) : null;
|
|
796
|
+
var effective_quote = q_from_cli ? argv.preferred_quote : null;
|
|
797
|
+
|
|
798
|
+
if (!ll_from_cli || !q_from_cli) {
|
|
799
|
+
var pyconf = await read_pyproject_config(process.cwd());
|
|
800
|
+
if (!ll_from_cli && pyconf.line_length) effective_ll = pyconf.line_length;
|
|
801
|
+
if (!q_from_cli && pyconf.preferred_quote) effective_quote = pyconf.preferred_quote;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (!effective_ll) effective_ll = 80;
|
|
805
|
+
if (!effective_quote) effective_quote = 'single';
|
|
806
|
+
|
|
758
807
|
var ctx = create_server_context({
|
|
759
808
|
import_dirs: utils.get_import_dirs(argv.import_path).map(function (p) { return path.resolve(p); }),
|
|
760
809
|
libdir: path.join(src_path, 'lib'),
|
|
761
|
-
line_length:
|
|
762
|
-
preferred_quote:
|
|
810
|
+
line_length: effective_ll,
|
|
811
|
+
preferred_quote: effective_quote,
|
|
812
|
+
join_lines: argv.join_lines || false,
|
|
763
813
|
});
|
|
764
814
|
|
|
765
815
|
// Diagnostics are pushed to the client, debounced per document.
|
|
@@ -797,7 +847,7 @@ export async function cli(argv, base_path, src_path, lib_path) {
|
|
|
797
847
|
renameProvider: true,
|
|
798
848
|
documentFormattingProvider: true,
|
|
799
849
|
documentSymbolProvider: true,
|
|
800
|
-
codeActionProvider: { codeActionKinds: [CodeActionKind.QuickFix, CodeActionKind.Source] },
|
|
850
|
+
codeActionProvider: { codeActionKinds: [CodeActionKind.QuickFix, CodeActionKind.Source, CodeActionKind.OrganizeImports] },
|
|
801
851
|
},
|
|
802
852
|
serverInfo: { name: 'rapydscript-lsp', version: '1.0.0' },
|
|
803
853
|
};
|
package/tools/repl.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import vm from 'vm';
|
|
|
12
12
|
import util from 'util';
|
|
13
13
|
import { createRequire } from 'module';
|
|
14
14
|
import * as utils from './utils.mjs';
|
|
15
|
+
import { EMBEDDED_STDLIB_PREFIX } from './compiler.mjs';
|
|
15
16
|
import completelib from './completer.mjs';
|
|
16
17
|
|
|
17
18
|
var colored = utils.safe_colored;
|
|
@@ -84,7 +85,9 @@ export default async function(options) {
|
|
|
84
85
|
|
|
85
86
|
// Read baselib synchronously: the repl must initialize synchronously so
|
|
86
87
|
// that the readline interface and prompt are ready before returning.
|
|
87
|
-
|
|
88
|
+
const _embedded = globalThis.__rapydscript_embedded__;
|
|
89
|
+
var baselib_plain = _embedded?.['baselib-plain-pretty.js'] ??
|
|
90
|
+
fs.readFileSync(path.join(options.lib_path, 'baselib-plain-pretty.js'), 'utf-8');
|
|
88
91
|
|
|
89
92
|
function print_ast(ast, keep_baselib) {
|
|
90
93
|
var output_options = {omit_baselib:!keep_baselib, write_name:false, private_scope:false, beautify:true, keep_docstrings:true};
|
|
@@ -160,7 +163,7 @@ export default async function(options) {
|
|
|
160
163
|
toplevel = await RapydScript.parse(source, {
|
|
161
164
|
'filename':'<repl>',
|
|
162
165
|
'basedir': process.cwd(),
|
|
163
|
-
'libdir': options.imp_path,
|
|
166
|
+
'libdir': globalThis.__rapydscript_embedded__ ? EMBEDDED_STDLIB_PREFIX : options.imp_path,
|
|
164
167
|
'import_dirs': import_dirs,
|
|
165
168
|
'classes': classes,
|
|
166
169
|
'scoped_flags': scoped_flags,
|