rapydscript-ng 0.8.0 → 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.
Files changed (72) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +8 -0
  3. package/bin/build.ts +117 -0
  4. package/build_wheels.py +133 -0
  5. package/editor-plugins/README.md +80 -0
  6. package/editor-plugins/nvim.lua +321 -0
  7. package/package.json +1 -1
  8. package/publish.py +27 -17
  9. package/release/compiler.js +8125 -8093
  10. package/release/signatures.json +27 -27
  11. package/release/stdlib_modules.json +1 -0
  12. package/src/ast.pyj +379 -279
  13. package/src/baselib-builtins.pyj +47 -26
  14. package/src/baselib-containers.pyj +105 -92
  15. package/src/baselib-errors.pyj +8 -1
  16. package/src/baselib-internal.pyj +35 -20
  17. package/src/baselib-itertools.pyj +13 -7
  18. package/src/baselib-str.pyj +55 -80
  19. package/src/compiler.pyj +4 -4
  20. package/src/errors.pyj +3 -4
  21. package/src/lib/aes.pyj +46 -32
  22. package/src/lib/elementmaker.pyj +11 -9
  23. package/src/lib/encodings.pyj +15 -5
  24. package/src/lib/gettext.pyj +9 -4
  25. package/src/lib/math.pyj +106 -45
  26. package/src/lib/operator.pyj +10 -10
  27. package/src/lib/pythonize.pyj +2 -1
  28. package/src/lib/random.pyj +28 -21
  29. package/src/lib/re.pyj +490 -17
  30. package/src/lib/traceback.pyj +7 -2
  31. package/src/lib/uuid.pyj +2 -2
  32. package/src/output/classes.pyj +28 -27
  33. package/src/output/codegen.pyj +80 -83
  34. package/src/output/comments.pyj +8 -8
  35. package/src/output/exceptions.pyj +20 -21
  36. package/src/output/functions.pyj +37 -26
  37. package/src/output/literals.pyj +14 -10
  38. package/src/output/loops.pyj +63 -59
  39. package/src/output/modules.pyj +52 -23
  40. package/src/output/operators.pyj +40 -29
  41. package/src/output/statements.pyj +20 -14
  42. package/src/output/stream.pyj +33 -34
  43. package/src/output/utils.pyj +12 -8
  44. package/src/parse.pyj +234 -233
  45. package/src/string_interpolation.pyj +5 -3
  46. package/src/tokenizer.pyj +176 -148
  47. package/src/utils.pyj +31 -14
  48. package/test/fmt.pyj +94 -4
  49. package/test/generic.pyj +9 -0
  50. package/test/lsp.pyj +35 -0
  51. package/test/str.pyj +8 -0
  52. package/tools/cli.mjs +25 -4
  53. package/tools/compile.mjs +7 -3
  54. package/tools/compiler.mjs +34 -2
  55. package/tools/fmt.mjs +262 -22
  56. package/tools/ini.mjs +112 -1
  57. package/tools/lsp.mjs +56 -6
  58. package/tools/repl.mjs +5 -2
  59. package/tools/self.mjs +15 -0
  60. package/tools/test.mjs +100 -0
  61. package/tools/web_repl_export.mjs +4 -0
  62. package/tree-sitter/package.json +1 -1
  63. package/tree-sitter/tree-sitter.json +1 -1
  64. package/editor-plugins/nvim/rapydscript/README.md +0 -184
  65. package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
  66. package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +0 -10
  67. package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +0 -88
  68. package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +0 -279
  69. /package/tree-sitter/queries/{rapydscript/highlights.scm → highlights.scm} +0 -0
  70. /package/tree-sitter/queries/{rapydscript/indents.scm → indents.scm} +0 -0
  71. /package/tree-sitter/queries/{rapydscript/injections.scm → injections.scm} +0 -0
  72. /package/tree-sitter/queries/{rapydscript/locals.scm → locals.scm} +0 -0
package/src/utils.pyj CHANGED
@@ -4,27 +4,32 @@ from __python__ import hash_literals
4
4
 
5
5
  has_prop = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty)
6
6
 
7
+
7
8
  def array_to_hash(a):
8
9
  ret = Object.create(None)
9
10
  for i in range(len(a)):
10
11
  ret[a[i]] = True
11
12
  return ret
12
13
 
14
+
13
15
  def slice(a, start):
14
16
  return Array.prototype.slice.call(a, start or 0)
15
17
 
18
+
16
19
  def characters(str_):
17
- return str_.split("")
20
+ return str_.split('')
21
+
18
22
 
19
23
  def member(name, array):
20
- for i in range(array.length-1, -1, -1):
24
+ for i in range(array.length - 1, -1, -1):
21
25
  if array[i] is name:
22
26
  return True
23
27
  return False
24
28
 
29
+
25
30
  def repeat_string(str_, i):
26
31
  if i <= 0:
27
- return ""
32
+ return ''
28
33
  if i is 1:
29
34
  return str_
30
35
  d = repeat_string(str_, i >> 1)
@@ -33,11 +38,12 @@ def repeat_string(str_, i):
33
38
  d += str_
34
39
  return d
35
40
 
36
- class DefaultsError(ValueError):
37
41
 
42
+ class DefaultsError(ValueError):
38
43
  def __init__(self, name, defs):
39
44
  ValueError.__init__(self, name + ' is not a supported option. Supported options are: ' + str(Object.keys(defs)))
40
45
 
46
+
41
47
  def defaults(args, defs, croak):
42
48
  if args is True:
43
49
  args = {}
@@ -51,15 +57,17 @@ def defaults(args, defs, croak):
51
57
  ret[i] = args[i] if args and has_prop(args, i) else defs[i]
52
58
  return ret
53
59
 
60
+
54
61
  def merge(obj, ext):
55
62
  for i in ext:
56
63
  obj[i] = ext[i]
57
64
  return obj
58
65
 
66
+
59
67
  def noop():
60
68
  pass
61
69
 
62
- MAP = def():
70
+ MAP = def ():
63
71
  def MAP(a, f, backwards):
64
72
  ret = []
65
73
  top = []
@@ -84,7 +92,7 @@ MAP = def():
84
92
 
85
93
  if Array.isArray(a):
86
94
  if backwards:
87
- for i in range(a.length-1, -1, -1):
95
+ for i in range(a.length - 1, -1, -1):
88
96
  if doit():
89
97
  break
90
98
  ret.reverse()
@@ -99,14 +107,15 @@ MAP = def():
99
107
  break
100
108
  return top.concat(ret)
101
109
 
102
- MAP.at_top = def(val):
110
+ MAP.at_top = def (val):
103
111
  return new AtTop(val)
104
- MAP.splice = def(val):
112
+ MAP.splice = def (val):
105
113
  return new Splice(val)
106
- MAP.last = def(val):
114
+ MAP.last = def (val):
107
115
  return new Last(val)
108
116
 
109
117
  skip = MAP.skip = {}
118
+
110
119
  def AtTop(val):
111
120
  this.v = val
112
121
 
@@ -119,20 +128,24 @@ MAP = def():
119
128
  return MAP
120
129
  .call(this)
121
130
 
131
+
122
132
  def push_uniq(array, el):
123
133
  if array.indexOf(el) < 0:
124
134
  array.push(el)
125
135
 
136
+
126
137
  def string_template(text, props):
127
- return text.replace(/\{(.+?)\}/g, def(str_, p):
138
+ return text.replace(/\{(.+?)\}/g, def (str_, p):
128
139
  return props[p]
129
140
  )
130
141
 
142
+
131
143
  def remove(array, el):
132
- for i in range(array.length-1, -1, -1):
144
+ for i in range(array.length - 1, -1, -1):
133
145
  if array[i] is el:
134
146
  array.splice(i, 1)
135
147
 
148
+
136
149
  def mergeSort(array, cmp):
137
150
  if array.length < 2:
138
151
  return array.slice()
@@ -167,24 +180,28 @@ def mergeSort(array, cmp):
167
180
  return merge(left, right)
168
181
  return _ms(array)
169
182
 
183
+
170
184
  def set_difference(a, b):
171
- return a.filter(def(el):
185
+ return a.filter(def (el):
172
186
  return b.indexOf(el) < 0
173
187
  )
174
188
 
189
+
175
190
  def set_intersection(a, b):
176
- return a.filter(def(el):
191
+ return a.filter(def (el):
177
192
  return b.indexOf(el) >= 0
178
193
  )
179
194
 
195
+
180
196
  def make_predicate(words):
181
197
  if jstype(words) is 'string':
182
- words = words.split(" ")
198
+ words = words.split(' ')
183
199
  a = Object.create(None)
184
200
  for k in words:
185
201
  a[k] = True
186
202
  return a
187
203
 
204
+
188
205
  def cache_file_name(src, cache_dir):
189
206
  if cache_dir:
190
207
  src = str.replace(src, '\\', '/')
package/test/fmt.pyj CHANGED
@@ -129,14 +129,36 @@ async def run_tests():
129
129
  {'line_length': 40})
130
130
 
131
131
  # --- reflow of simple multi-line statements ----------------------------
132
+ # By default, source line breaks are preserved (no joining).
133
+ await check('no-join-default',
134
+ 'x = fn(\n a,\n b,\n c\n)\n',
135
+ 'x = fn(\n a,\n b,\n c\n)\n')
136
+ # With join_lines=True the statement is collapsed onto one line.
132
137
  await check('reflow-simple-call',
133
138
  'x = fn(\n a,\n b,\n c\n)\n',
134
- 'x = fn(a, b, c)\n')
139
+ 'x = fn(a, b, c)\n',
140
+ {'join_lines': True})
141
+ # A multi-line statement that is too long even in source form gets wrapped
142
+ # regardless of join_lines.
143
+ await check('no-join-wrap-long',
144
+ 'x = very_long_function_name(\n argument_one,\n argument_two,\n argument_three\n)\n',
145
+ 'x = very_long_function_name(\n argument_one,\n argument_two,\n argument_three\n)\n',
146
+ {'line_length': 40})
135
147
 
136
148
  # --- RapydScript specific constructs are preserved ----------------------
137
149
  await check('preserve-anon-function',
138
- "params = {\n 'onclick': def(event):\n alert('hi')\n ,\n 'x': 5\n}\n",
139
- "params = {\n 'onclick': def(event):\n alert('hi')\n ,\n 'x': 5\n}\n")
150
+ "params = {\n 'onclick': def (event):\n alert('hi')\n ,\n 'x': 5\n}\n",
151
+ "params = {\n 'onclick': def (event):\n alert('hi')\n ,\n 'x': 5\n}\n")
152
+ # Assignments inside an anonymous def passed as an argument must keep spaces
153
+ # around '='; they are not kwargs even though they are inside the outer call.
154
+ await check('anon-def-arg-assignment',
155
+ "window.addEventListener('beforeunload', def (event):\n self.disable=True\n self.ws.close()\n)\n",
156
+ "window.addEventListener('beforeunload', def (event):\n self.disable = True\n self.ws.close()\n)\n")
157
+ # Inline anonymous def inside a call: kwargs that follow the def must not
158
+ # gain spaces (they are not assignments in the def body).
159
+ await check('inline-anon-def-arg-kwargs',
160
+ "xhr = ajax('url', def ():pass;, method='POST', flag=False)\n",
161
+ "xhr = ajax('url', def (): pass;, method='POST', flag=False)\n")
140
162
  await check('preserve-chain',
141
163
  "$(element)\n.css('background-color', 'red')\n.show()\n",
142
164
  "$(element)\n.css('background-color', 'red')\n.show()\n")
@@ -160,7 +182,14 @@ async def run_tests():
160
182
 
161
183
  # --- inline anonymous function on a single line -------------------------
162
184
  await check('inline-anon', 'add = def(a,b): return a+b\n',
163
- 'add = def(a, b): return a + b\n')
185
+ 'add = def (a, b): return a + b\n')
186
+ # --- anonymous function assignment: space always inserted between def and ( --
187
+ await check('anon-def-assignment',
188
+ "func = def(event):\n pass\n",
189
+ "func = def (event):\n pass\n")
190
+ await check('anon-def-assignment-already-spaced',
191
+ "func = def (event):\n pass\n",
192
+ "func = def (event):\n pass\n")
164
193
 
165
194
  # --- shebang preserved --------------------------------------------------
166
195
  await check('shebang', '#!/usr/bin/env rapydscript\nx=1\n',
@@ -183,6 +212,67 @@ async def run_tests():
183
212
  assrt.ok(errs3.length >= 1, 'check_report must flag over-length lines')
184
213
  assrt.ok(errs3.join('\n').indexOf('exceeds') >= 0, 'check_report over-length message content')
185
214
 
215
+ # --- import organization ------------------------------------------------
216
+ # __python__ group is first; stdlib group is second; other group is third.
217
+ # Blank line separates each non-empty group from the next.
218
+ await check('import-organize-three-groups',
219
+ 'import my_module\nimport math\nfrom __python__ import os\n',
220
+ 'from __python__ import os\n\nimport math\n\nimport my_module\n')
221
+ # stdlib (math, re, ...) in group 2; other imports in group 3, blank line between.
222
+ await check('import-organize-two-groups',
223
+ 'import my_module\nimport math\n',
224
+ 'import math\n\nimport my_module\n')
225
+ # from-imports in stdlib group follow bare imports (isort convention).
226
+ await check('import-organize-from-after-bare',
227
+ 'from re import match\nimport math\nimport my_module\n',
228
+ 'import math\nfrom re import match\n\nimport my_module\n')
229
+ # Sorting within a group: alphabetical by module then by names.
230
+ await check('import-organize-sort-stdlib',
231
+ 'import re\nimport math\n',
232
+ 'import math\nimport re\n')
233
+ await check('import-organize-sort-other',
234
+ 'import zebra\nimport apple\n',
235
+ 'import apple\nimport zebra\n')
236
+ # Only stdlib: no blank-line separator.
237
+ await check('import-organize-stdlib-only',
238
+ 'import re\nimport math\n',
239
+ 'import math\nimport re\n')
240
+ # Only other: no blank-line separator.
241
+ await check('import-organize-other-only',
242
+ 'import zebra\nimport apple\n',
243
+ 'import apple\nimport zebra\n')
244
+ # Only __python__: no blank-line separator.
245
+ await check('import-organize-python-only',
246
+ 'from __python__ import sys\nfrom __python__ import os\n',
247
+ 'from __python__ import os\nfrom __python__ import sys\n')
248
+ # __python__ with stdlib only: blank line between them.
249
+ await check('import-organize-python-and-stdlib',
250
+ 'import math\nfrom __python__ import os\n',
251
+ 'from __python__ import os\n\nimport math\n')
252
+ # __python__ with other only: blank line between them.
253
+ await check('import-organize-python-and-other',
254
+ 'import my_module\nfrom __python__ import os\n',
255
+ 'from __python__ import os\n\nimport my_module\n')
256
+ # Already organized: idempotent (checked automatically by check()).
257
+ await check('import-organize-already-organized',
258
+ 'import math\n\nimport my_module\n',
259
+ 'import math\n\nimport my_module\n')
260
+ await check('import-organize-python-already-organized',
261
+ 'from __python__ import os\n\nimport math\n\nimport my_module\n',
262
+ 'from __python__ import os\n\nimport math\n\nimport my_module\n')
263
+ # Code after imports is untouched; blank line between imports and code is preserved.
264
+ await check('import-organize-with-code',
265
+ 'import my_module\nimport math\n\nx = 1\n',
266
+ 'import math\n\nimport my_module\n\nx = 1\n')
267
+ # from-imports within other group are sorted by module then names.
268
+ await check('import-organize-from-other',
269
+ 'from zebra import z\nfrom apple import a\n',
270
+ 'from apple import a\nfrom zebra import z\n')
271
+ # Mixed bare and from-imports across groups.
272
+ await check('import-organize-mixed',
273
+ 'from my_mod import func\nimport re\nimport math\n',
274
+ 'import math\nimport re\n\nfrom my_mod import func\n')
275
+
186
276
  # --- collect_pyj_files (directory recursion) ----------------------------
187
277
  tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rs-fmt-'))
188
278
  fs.mkdirSync(path.join(tmp, 'sub'))
package/test/generic.pyj CHANGED
@@ -39,6 +39,15 @@ assrt.equal(-1 < 0 == 1 < 0, False)
39
39
  # Empty tuple
40
40
  assrt.deepEqual((), [])
41
41
 
42
+ # Tuples with newlines inside parentheses
43
+ assrt.deepEqual((1,
44
+ 2), [1, 2])
45
+ assrt.deepEqual((1,
46
+ 2,
47
+ 3), [1, 2, 3])
48
+ assrt.deepEqual((1,
49
+ 2), [1, 2])
50
+
42
51
  # Conditional operators
43
52
  assrt.equal(1 if True else 2, 1)
44
53
  assrt.equal(1
package/test/lsp.pyj CHANGED
@@ -206,6 +206,40 @@ async def test_configuration_change():
206
206
  lsp.apply_configuration(ctx, {'importPath': ''})
207
207
  assrt.equal(ctx.import_dirs.length, 0, 'empty importPath string should clear import_dirs')
208
208
 
209
+ async def test_organize_imports():
210
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rs-lsp-org-'))
211
+ ctx = lsp.create_server_context({
212
+ 'import_dirs': [tmp], 'libdir': libdir, 'workspace_roots': [tmp],
213
+ 'line_length': 80, 'preferred_quote': 'single',
214
+ })
215
+ # Unorganized: stdlib after other -> edits are produced.
216
+ unorganized = 'import my_module\nimport math\n\nx = 1\n'
217
+ edits = lsp.organize_imports_document(ctx, unorganized)
218
+ assrt.ok(Array.isArray(edits) and edits.length > 0,
219
+ 'organize_imports_document must return edits for unorganized imports')
220
+ assrt.ok(edits[0].newText.indexOf('import math') < edits[0].newText.indexOf('import my_module'),
221
+ 'stdlib import (math) must come before other import in organized output')
222
+ assrt.ok(edits[0].newText.indexOf('\n\n') >= 0,
223
+ 'organized output must have a blank line between stdlib and other groups')
224
+ # from __python__ import ... goes first, before stdlib.
225
+ with_python = 'import math\nfrom __python__ import os\n\nx = 1\n'
226
+ edits_py = lsp.organize_imports_document(ctx, with_python)
227
+ assrt.ok(Array.isArray(edits_py) and edits_py.length > 0,
228
+ 'organize_imports_document must return edits when __python__ import is out of place')
229
+ assrt.ok(edits_py[0].newText.indexOf('from __python__') < edits_py[0].newText.indexOf('import math'),
230
+ '__python__ group must come before stdlib group in organized output')
231
+ # Already organized: no edits.
232
+ organized = 'import math\n\nimport my_module\n\nx = 1\n'
233
+ edits2 = lsp.organize_imports_document(ctx, organized)
234
+ assrt.equal(edits2.length, 0,
235
+ 'organize_imports_document must return no edits for already-organized imports')
236
+ # No imports: no edits.
237
+ no_imports = 'x = 1\ny = 2\n'
238
+ edits3 = lsp.organize_imports_document(ctx, no_imports)
239
+ assrt.equal(edits3.length, 0,
240
+ 'organize_imports_document must return no edits when there are no imports')
241
+ fs.rmSync(tmp, {'recursive': True})
242
+
209
243
  async def run_tests():
210
244
  await test_diagnostics()
211
245
  await test_hover_and_definition()
@@ -218,5 +252,6 @@ async def run_tests():
218
252
  await test_error_recovery_still_analyzes()
219
253
  await test_parser_recovery_flag()
220
254
  await test_configuration_change()
255
+ await test_organize_imports()
221
256
 
222
257
  __test_async_done__ = run_tests()
package/test/str.pyj CHANGED
@@ -57,6 +57,14 @@ def test_interpolation():
57
57
  ae(f'{a}'f'{a}', '11')
58
58
  ae(f'a''b', 'ab')
59
59
  ae(f'{a}''b', '1b')
60
+ # Test plain string followed by f-string (regression: was parsed as a call expression)
61
+ ae('a' f'b', 'ab')
62
+ ae('a' f'{a}', 'a1')
63
+ ae('prefix ' f'{a}', 'prefix 1')
64
+ ae('a' f'{a}', 'a1')
65
+ ae('a' f'{a}' 'b', 'a1b')
66
+ ae('a' f'{a}' f'{a}', 'a11')
67
+ ae(rf'raw{a}' f' and {a}', 'raw1 and 1')
60
68
 
61
69
  somevar = 33
62
70
  test('somevar=33', '{somevar=}', somevar=somevar)
package/tools/cli.mjs CHANGED
@@ -449,18 +449,29 @@ If no files or directories are specified, the source code is read from
449
449
  STDIN and the formatted result is written to STDOUT.`);
450
450
 
451
451
  opt("line_length", 'l', 'string', '80', `The maximum allowed line length. Lines longer than this
452
- are wrapped where it is safe to do so. Defaults to 80.`);
452
+ are wrapped where it is safe to do so. Defaults to 80.
453
+ If not specified, the value is read from pyproject.toml
454
+ (searched upward from the current directory) under
455
+ [tool.ruff], [tool.black], or [tool.isort].`);
453
456
 
454
457
  opt("preferred_quote", 'q', 'string', 'single', `The preferred quote character for string literals. Either
455
458
  "single" or "double". A string is only re-quoted when doing
456
459
  so does not increase the number of backslash escapes.
457
- Defaults to single.`, ['single', 'double']);
460
+ Defaults to single. If not specified, the value is read from
461
+ pyproject.toml (searched upward from the current directory)
462
+ under [tool.ruff.format], [tool.ruff.lint.flake8-quotes],
463
+ or [tool.flake8].`, ['single', 'double']);
458
464
 
459
465
  opt("check_only", 'c', 'bool', false, `Do not modify files. Instead, print the names of files that
460
466
  would be reformatted (and any lines that exceed the maximum
461
467
  length) to STDERR. Exit with a status of 1 if any issues are
462
468
  found, otherwise 0.`);
463
469
 
470
+ opt("join_lines", 'j', 'bool', false, `Join multi-line statements that fit within the maximum line
471
+ length onto a single line. Disabled by default: source line
472
+ breaks inside statements are preserved as long as no line
473
+ exceeds the maximum length.`);
474
+
464
475
  create_group('lsp', "", `Run a Language Server Protocol (LSP) server for RapydScript.
465
476
  The server communicates over stdin/stdout using the standard
466
477
  LSP JSON-RPC framing and provides code completion, diagnostics,
@@ -484,10 +495,20 @@ RAPYDSCRIPT_IMPORT_PATH for this, with identical syntax.`);
484
495
 
485
496
  opt("line_length", 'l', 'string', '80', `The maximum allowed line length used by the document formatter.
486
497
  Lines longer than this are wrapped where it is safe to do so.
487
- Defaults to 80.`);
498
+ Defaults to 80. If not specified, the value is read from
499
+ pyproject.toml (searched upward from the working directory)
500
+ under [tool.ruff], [tool.black], or [tool.isort].`);
488
501
 
489
502
  opt("preferred_quote", 'q', 'string', 'single', `The preferred quote character used by the document formatter.
490
- Either "single" or "double". Defaults to single.`, ['single', 'double']);
503
+ Either "single" or "double". Defaults to single. If not
504
+ specified, the value is read from pyproject.toml (searched
505
+ upward from the working directory) under [tool.ruff.format],
506
+ [tool.ruff.lint.flake8-quotes], or [tool.flake8].`, ['single', 'double']);
507
+
508
+ opt("join_lines", 'j', 'bool', false, `Join multi-line statements that fit within the maximum line
509
+ length onto a single line. Disabled by default: source line
510
+ breaks inside statements are preserved as long as no line
511
+ exceeds the maximum length.`);
491
512
 
492
513
 
493
514
  export var argv = parse_args();
package/tools/compile.mjs CHANGED
@@ -9,7 +9,7 @@ import fs from 'fs';
9
9
  import path from 'path';
10
10
  import vm from 'vm';
11
11
  import { createRequire } from 'module';
12
- import { create_compiler } from './compiler.mjs';
12
+ import { create_compiler, EMBEDDED_STDLIB_PREFIX } from './compiler.mjs';
13
13
  import * as utils from './utils.mjs';
14
14
  import { generate_source_map } from './sourcemap.mjs';
15
15
  import tree_shake from './treeshake.mjs';
@@ -83,7 +83,10 @@ export default async function(start_time, argv, base_path, src_path, lib_path) {
83
83
 
84
84
  if (!argv.omit_baselib) {
85
85
  var which = (OUTPUT_OPTIONS.beautify) ? 'pretty' : 'ugly';
86
- OUTPUT_OPTIONS.baselib_plain = await fs.promises.readFile(path.join(lib_path, 'baselib-plain-' + which + '.js'), 'utf-8');
86
+ const baselib_key = 'baselib-plain-' + which + '.js';
87
+ const embedded = globalThis.__rapydscript_embedded__;
88
+ OUTPUT_OPTIONS.baselib_plain = embedded?.[baselib_key] ??
89
+ await fs.promises.readFile(path.join(lib_path, baselib_key), 'utf-8');
87
90
  }
88
91
 
89
92
  var files = argv.files.slice();
@@ -96,11 +99,12 @@ export default async function(start_time, argv, base_path, src_path, lib_path) {
96
99
  }
97
100
 
98
101
  async function parse_file(code, file, toplevel) {
102
+ const embedded = globalThis.__rapydscript_embedded__;
99
103
  return await RapydScript.parse(code, {
100
104
  filename: file,
101
105
  toplevel: toplevel,
102
106
  basedir: (file !== '<stdin>') ? path.dirname(file) : undefined,
103
- libdir: path.join(src_path, 'lib'),
107
+ libdir: embedded ? EMBEDDED_STDLIB_PREFIX : path.join(src_path, 'lib'),
104
108
  import_dirs: utils.get_import_dirs(argv.import_path),
105
109
  discard_asserts: argv.discard_asserts,
106
110
  module_cache_dir: cache_dir,
@@ -53,9 +53,15 @@ async function find_compiler_dir() {
53
53
  return { base, compiler_dir };
54
54
  }
55
55
 
56
+ // Sentinel libdir value used when stdlib files come from the embedded asset VFS
57
+ // rather than the real filesystem. The compiler's readfile/stat_file callbacks
58
+ // intercept paths that start with this prefix.
59
+ const EMBEDDED_STDLIB_PREFIX = '__stdlib__';
60
+
56
61
  async function create_compiler(opts) {
57
62
  opts = opts || {};
58
63
  var vfs = opts.virtual_file_system;
64
+ const embedded = globalThis.__rapydscript_embedded__;
59
65
 
60
66
  const { base, compiler_dir } = await find_compiler_dir();
61
67
 
@@ -83,6 +89,32 @@ async function create_compiler(opts) {
83
89
  const content = await readfile(p, 'utf-8');
84
90
  return { mtimeMs: null, content };
85
91
  };
92
+ } else if (embedded) {
93
+ // Compiled standalone binary: stdlib lives in the embedded asset VFS.
94
+ // Paths prefixed with EMBEDDED_STDLIB_PREFIX are served from memory;
95
+ // all other paths (user source files, cache) still hit the real filesystem.
96
+ readfile = async (p, enc) => {
97
+ if (p.startsWith('__stdlib__/')) {
98
+ const name = p.slice('__stdlib__/'.length);
99
+ if (embedded.stdlib && embedded.stdlib[name] !== undefined) return embedded.stdlib[name];
100
+ }
101
+ return fs.promises.readFile(p, enc);
102
+ };
103
+ // Silently drop writes that target embedded virtual paths (e.g. stdlib cache files).
104
+ writefile = async (p, data) => {
105
+ if (p.startsWith('__stdlib__/')) return;
106
+ return fs.promises.writeFile(p, data);
107
+ };
108
+ stat_file = async (p) => {
109
+ if (p.startsWith('__stdlib__/')) {
110
+ const name = p.slice('__stdlib__/'.length);
111
+ if (embedded.stdlib && embedded.stdlib[name] !== undefined) return { mtimeMs: null };
112
+ const err = Object.assign(new Error(`stdlib not found: ${name}`), { code: 'ENOENT' });
113
+ throw err;
114
+ }
115
+ const st = await fs.promises.stat(p);
116
+ return { mtimeMs: st.mtimeMs };
117
+ };
86
118
  } else {
87
119
  readfile = async (p, enc) => fs.promises.readFile(p, enc);
88
120
  writefile = async (p, data) => fs.promises.writeFile(p, data);
@@ -103,7 +135,7 @@ async function create_compiler(opts) {
103
135
  exports : compiler_exports,
104
136
  });
105
137
  var compiler_file = path.join(compiler_dir, 'compiler.js');
106
- var compilerjs = await fs.promises.readFile(compiler_file, 'utf-8');
138
+ var compilerjs = embedded?.['compiler.js'] ?? await fs.promises.readFile(compiler_file, 'utf-8');
107
139
  vm.runInContext(compilerjs, compiler_context, path.relative(base, compiler_file));
108
140
  const { ast_to_json, ast_from_json, make_lazy_ast_module, encode_cache, decode_cache } = make_ast_serializer(compiler_exports);
109
141
  compiler_exports.ast_to_json = ast_to_json;
@@ -124,4 +156,4 @@ async function create_embedded_compiler(compiler, baselib, runjs, name) {
124
156
  return await embedded_compiler_factory(compiler || await create_compiler(), baselib, runjs, name, tree_shake, generate_source_map);
125
157
  }
126
158
 
127
- export { create_compiler, create_embedded_compiler, generate_source_map };
159
+ export { create_compiler, create_embedded_compiler, generate_source_map, EMBEDDED_STDLIB_PREFIX };