rapydscript-ns 0.8.0
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/.agignore +1 -0
- package/.gitattributes +4 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/web-repl-page-deploy.yml +42 -0
- package/=template.pyj +5 -0
- package/CHANGELOG.md +456 -0
- package/CONTRIBUTORS +13 -0
- package/HACKING.md +103 -0
- package/LICENSE +24 -0
- package/README.md +2512 -0
- package/TODO.md +327 -0
- package/add-toc-to-readme +2 -0
- package/bin/export +75 -0
- package/bin/rapydscript +70 -0
- package/bin/web-repl-export +102 -0
- package/build +3 -0
- package/package.json +46 -0
- package/publish.py +37 -0
- package/release/baselib-plain-pretty.js +4370 -0
- package/release/baselib-plain-ugly.js +3 -0
- package/release/compiler.js +18394 -0
- package/release/signatures.json +31 -0
- package/session.vim +4 -0
- package/setup.cfg +2 -0
- package/src/ast.pyj +1356 -0
- package/src/baselib-builtins.pyj +279 -0
- package/src/baselib-containers.pyj +723 -0
- package/src/baselib-errors.pyj +37 -0
- package/src/baselib-internal.pyj +421 -0
- package/src/baselib-itertools.pyj +97 -0
- package/src/baselib-str.pyj +798 -0
- package/src/compiler.pyj +36 -0
- package/src/errors.pyj +30 -0
- package/src/lib/aes.pyj +646 -0
- package/src/lib/collections.pyj +695 -0
- package/src/lib/elementmaker.pyj +83 -0
- package/src/lib/encodings.pyj +126 -0
- package/src/lib/functools.pyj +148 -0
- package/src/lib/gettext.pyj +569 -0
- package/src/lib/itertools.pyj +580 -0
- package/src/lib/math.pyj +193 -0
- package/src/lib/numpy.pyj +2101 -0
- package/src/lib/operator.pyj +11 -0
- package/src/lib/pythonize.pyj +20 -0
- package/src/lib/random.pyj +118 -0
- package/src/lib/re.pyj +470 -0
- package/src/lib/traceback.pyj +63 -0
- package/src/lib/uuid.pyj +77 -0
- package/src/monaco-language-service/analyzer.js +526 -0
- package/src/monaco-language-service/builtins.js +543 -0
- package/src/monaco-language-service/completions.js +498 -0
- package/src/monaco-language-service/diagnostics.js +643 -0
- package/src/monaco-language-service/dts.js +550 -0
- package/src/monaco-language-service/hover.js +121 -0
- package/src/monaco-language-service/index.js +386 -0
- package/src/monaco-language-service/scope.js +162 -0
- package/src/monaco-language-service/signature.js +144 -0
- package/src/output/__init__.pyj +0 -0
- package/src/output/classes.pyj +296 -0
- package/src/output/codegen.pyj +492 -0
- package/src/output/comments.pyj +45 -0
- package/src/output/exceptions.pyj +105 -0
- package/src/output/functions.pyj +491 -0
- package/src/output/literals.pyj +109 -0
- package/src/output/loops.pyj +444 -0
- package/src/output/modules.pyj +329 -0
- package/src/output/operators.pyj +429 -0
- package/src/output/statements.pyj +463 -0
- package/src/output/stream.pyj +309 -0
- package/src/output/treeshake.pyj +182 -0
- package/src/output/utils.pyj +72 -0
- package/src/parse.pyj +3106 -0
- package/src/string_interpolation.pyj +72 -0
- package/src/tokenizer.pyj +702 -0
- package/src/unicode_aliases.pyj +576 -0
- package/src/utils.pyj +192 -0
- package/test/_import_one.pyj +37 -0
- package/test/_import_two/__init__.pyj +11 -0
- package/test/_import_two/level2/__init__.pyj +0 -0
- package/test/_import_two/level2/deep.pyj +4 -0
- package/test/_import_two/other.pyj +6 -0
- package/test/_import_two/sub.pyj +13 -0
- package/test/aes_vectors.pyj +421 -0
- package/test/annotations.pyj +80 -0
- package/test/baselib.pyj +319 -0
- package/test/classes.pyj +452 -0
- package/test/collections.pyj +152 -0
- package/test/decorators.pyj +77 -0
- package/test/dict_spread.pyj +76 -0
- package/test/docstrings.pyj +39 -0
- package/test/elementmaker_test.pyj +45 -0
- package/test/ellipsis.pyj +49 -0
- package/test/functions.pyj +151 -0
- package/test/generators.pyj +41 -0
- package/test/generic.pyj +370 -0
- package/test/imports.pyj +72 -0
- package/test/internationalization.pyj +73 -0
- package/test/lint.pyj +164 -0
- package/test/loops.pyj +85 -0
- package/test/numpy.pyj +734 -0
- package/test/omit_function_metadata.pyj +20 -0
- package/test/regexp.pyj +55 -0
- package/test/repl.pyj +121 -0
- package/test/scoped_flags.pyj +76 -0
- package/test/starargs.pyj +506 -0
- package/test/starred_assign.pyj +104 -0
- package/test/str.pyj +198 -0
- package/test/subscript_tuple.pyj +53 -0
- package/test/unit/fixtures/fibonacci_expected.js +46 -0
- package/test/unit/index.js +2989 -0
- package/test/unit/language-service-builtins.js +815 -0
- package/test/unit/language-service-completions.js +1067 -0
- package/test/unit/language-service-dts.js +543 -0
- package/test/unit/language-service-hover.js +455 -0
- package/test/unit/language-service-scope.js +833 -0
- package/test/unit/language-service-signature.js +458 -0
- package/test/unit/language-service.js +705 -0
- package/test/unit/run-language-service.js +41 -0
- package/test/unit/web-repl.js +484 -0
- package/tools/build-language-service.js +190 -0
- package/tools/cli.js +547 -0
- package/tools/compile.js +219 -0
- package/tools/compiler.js +108 -0
- package/tools/completer.js +131 -0
- package/tools/embedded_compiler.js +251 -0
- package/tools/export.js +316 -0
- package/tools/gettext.js +185 -0
- package/tools/ini.js +65 -0
- package/tools/lint.js +705 -0
- package/tools/msgfmt.js +187 -0
- package/tools/repl.js +223 -0
- package/tools/self.js +162 -0
- package/tools/test.js +118 -0
- package/tools/utils.js +128 -0
- package/tools/web_repl.js +95 -0
- package/try +41 -0
- package/web-repl/env.js +74 -0
- package/web-repl/index.html +163 -0
- package/web-repl/language-service.js +4084 -0
- package/web-repl/main.js +254 -0
- package/web-repl/prism.css +139 -0
- package/web-repl/prism.js +113 -0
- package/web-repl/rapydscript.js +435 -0
- package/web-repl/sha1.js +25 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// index.js — RapydScript Monaco Language Service
|
|
2
|
+
// Public API: registerRapydScript(monaco, options) → service
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// import { registerRapydScript } from './monaco-language-service/index.js';
|
|
6
|
+
//
|
|
7
|
+
// const service = registerRapydScript(monaco, {
|
|
8
|
+
// compiler: window.RapydScript, // compiled rapydscript bundle
|
|
9
|
+
// virtualFiles: { 'mymod': 'def f(): pass' }, // optional
|
|
10
|
+
// dtsFiles: [{ name: 'dom', content: '...' }], // optional (Phase 6)
|
|
11
|
+
// parseDelay: 300, // ms debounce (default: 300)
|
|
12
|
+
// });
|
|
13
|
+
//
|
|
14
|
+
// service.setVirtualFiles({ 'mymod': newSource });
|
|
15
|
+
// service.addDts('name', dtsText); // Phase 6 stub for now
|
|
16
|
+
// service.dispose(); // remove all Monaco providers
|
|
17
|
+
|
|
18
|
+
import { Diagnostics, BASE_BUILTINS } from './diagnostics.js';
|
|
19
|
+
import { SourceAnalyzer } from './analyzer.js';
|
|
20
|
+
import { CompletionEngine } from './completions.js';
|
|
21
|
+
import { SignatureHelpEngine } from './signature.js';
|
|
22
|
+
import { HoverEngine } from './hover.js';
|
|
23
|
+
import { DtsRegistry } from './dts.js';
|
|
24
|
+
import { BuiltinsRegistry } from './builtins.js';
|
|
25
|
+
|
|
26
|
+
const LANGUAGE_ID = 'rapydscript';
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Per-model state
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
class ModelState {
|
|
33
|
+
constructor(model, service) {
|
|
34
|
+
this._model = model;
|
|
35
|
+
this._service = service;
|
|
36
|
+
this._timer = null;
|
|
37
|
+
this._scopeMap = null; // most recent ScopeMap for this model
|
|
38
|
+
|
|
39
|
+
// Run immediately on first attach
|
|
40
|
+
this._schedule(0);
|
|
41
|
+
|
|
42
|
+
// Re-run on every edit
|
|
43
|
+
this._subscription = model.onDidChangeContent(() => {
|
|
44
|
+
this._schedule(service._parseDelay);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_schedule(delay) {
|
|
49
|
+
clearTimeout(this._timer);
|
|
50
|
+
this._timer = setTimeout(() => this._run(), delay);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_run() {
|
|
54
|
+
const service = this._service;
|
|
55
|
+
const code = this._model.getValue();
|
|
56
|
+
const opts = { virtualFiles: service._virtualFiles };
|
|
57
|
+
|
|
58
|
+
// Diagnostics (syntax errors + lint markers)
|
|
59
|
+
const markers = service._diagnostics.check(code, {
|
|
60
|
+
...opts,
|
|
61
|
+
markerSeverity: service._monaco.MarkerSeverity,
|
|
62
|
+
});
|
|
63
|
+
service._monaco.editor.setModelMarkers(this._model, LANGUAGE_ID, markers);
|
|
64
|
+
|
|
65
|
+
// Scope analysis (feeds Phase 3 completions and Phase 5 hover)
|
|
66
|
+
this._scopeMap = service._analyzer.analyze(code, opts);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
dispose() {
|
|
70
|
+
clearTimeout(this._timer);
|
|
71
|
+
this._subscription.dispose();
|
|
72
|
+
// Clear markers when detaching
|
|
73
|
+
this._service._monaco.editor.setModelMarkers(this._model, LANGUAGE_ID, []);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// RapydScriptLanguageService
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
class RapydScriptLanguageService {
|
|
82
|
+
constructor(monaco, options) {
|
|
83
|
+
this._monaco = monaco;
|
|
84
|
+
this._parseDelay = options.parseDelay !== undefined ? options.parseDelay : 300;
|
|
85
|
+
this._virtualFiles = Object.assign({}, options.virtualFiles || {});
|
|
86
|
+
this._disposables = [];
|
|
87
|
+
this._modelStates = new Map(); // model → ModelState
|
|
88
|
+
|
|
89
|
+
// Build compiler reference — accept either the raw RapydScript global
|
|
90
|
+
// or a web_repl instance (we only need the parse-level API).
|
|
91
|
+
const compiler = options.compiler;
|
|
92
|
+
if (!compiler) throw new Error('registerRapydScript: options.compiler is required');
|
|
93
|
+
|
|
94
|
+
this._diagnostics = new Diagnostics(compiler, options.extraBuiltins, options.pythonFlags);
|
|
95
|
+
this._analyzer = new SourceAnalyzer(compiler, options.pythonFlags);
|
|
96
|
+
|
|
97
|
+
// Preserve extra builtin names so they survive addDts() rebuilds
|
|
98
|
+
this._extraBuiltinNames = options.extraBuiltins ? Object.keys(options.extraBuiltins) : [];
|
|
99
|
+
this._stdlibFiles = Object.assign({}, options.stdlibFiles || {});
|
|
100
|
+
|
|
101
|
+
// DTS registry — load any files supplied at construction time
|
|
102
|
+
this._dts = new DtsRegistry();
|
|
103
|
+
if (options.dtsFiles) {
|
|
104
|
+
options.dtsFiles.forEach(f => this._dts.addDts(f.name, f.content));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Built-in stubs registry (always present)
|
|
108
|
+
this._builtins = new BuiltinsRegistry();
|
|
109
|
+
this._builtins.enablePythonizeStrings();
|
|
110
|
+
|
|
111
|
+
// Merge BASE_BUILTINS + extra globals + DTS globals for completions
|
|
112
|
+
const builtin_names = BASE_BUILTINS
|
|
113
|
+
.concat(this._extraBuiltinNames)
|
|
114
|
+
.concat(this._dts.getGlobalNames());
|
|
115
|
+
|
|
116
|
+
this._completions = new CompletionEngine(this._analyzer, {
|
|
117
|
+
virtualFiles: this._virtualFiles,
|
|
118
|
+
stdlibFiles: this._stdlibFiles,
|
|
119
|
+
builtinNames: builtin_names,
|
|
120
|
+
dtsRegistry: this._dts,
|
|
121
|
+
builtinsRegistry: this._builtins,
|
|
122
|
+
});
|
|
123
|
+
this._signature_help = new SignatureHelpEngine(this._builtins);
|
|
124
|
+
this._hover = new HoverEngine(this._dts, this._builtins);
|
|
125
|
+
|
|
126
|
+
// Suppress diagnostics warnings for DTS globals
|
|
127
|
+
if (this._dts.getGlobalNames().length) {
|
|
128
|
+
this._diagnostics.addGlobals(this._dts.getGlobalNames());
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Store loadDts callback for lazy loading
|
|
132
|
+
this._loadDts = options.loadDts || null;
|
|
133
|
+
|
|
134
|
+
// Register language id if not already registered
|
|
135
|
+
const existing = monaco.languages.getLanguages().find(l => l.id === LANGUAGE_ID);
|
|
136
|
+
if (!existing) monaco.languages.register({ id: LANGUAGE_ID });
|
|
137
|
+
|
|
138
|
+
// Apply Python syntax highlighting — RapydScript is syntactically Python-like
|
|
139
|
+
// enough that Monaco's built-in Python tokenizer gives correct coloring.
|
|
140
|
+
// We then inject game-specific token rules so the same theme colors used for
|
|
141
|
+
// JS/TS files (ns, netscriptfunction, otherkeyvars, this) apply here too.
|
|
142
|
+
const extra_names = this._extraBuiltinNames;
|
|
143
|
+
const pythonLang = monaco.languages.getLanguages().find(l => l.id === 'python');
|
|
144
|
+
if (pythonLang && pythonLang.loader) {
|
|
145
|
+
pythonLang.loader().then(function(mod) {
|
|
146
|
+
const lang = mod.language;
|
|
147
|
+
|
|
148
|
+
// Python equivalents of the JS/TS otherkeyvars / this custom tokens.
|
|
149
|
+
// Unshift in reverse-priority order (last unshift = highest priority).
|
|
150
|
+
lang.tokenizer.root.unshift(
|
|
151
|
+
// self → 'this' color (Python's equivalent of JS `this`)
|
|
152
|
+
[/\bself\b/, { token: 'this' }],
|
|
153
|
+
// True / False / None / ... → 'otherkeyvars' (accent color, distinct from keywords)
|
|
154
|
+
[/\b(True|False|None)\b/, { token: 'otherkeyvars' }],
|
|
155
|
+
[/\.\.\./, { token: 'otherkeyvars' }],
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// ns → 'ns' token (the game API namespace object)
|
|
159
|
+
lang.tokenizer.root.unshift([/\bns\b/, { token: 'ns' }]);
|
|
160
|
+
|
|
161
|
+
// extraBuiltins → 'netscriptfunction' token (all NS API names)
|
|
162
|
+
if (extra_names.length) {
|
|
163
|
+
const alt = extra_names
|
|
164
|
+
.map(n => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
165
|
+
.join('|');
|
|
166
|
+
lang.tokenizer.root.unshift([
|
|
167
|
+
new RegExp('\\b(?:' + alt + ')\\b'),
|
|
168
|
+
{ token: 'netscriptfunction' },
|
|
169
|
+
]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
monaco.languages.setMonarchTokensProvider(LANGUAGE_ID, lang);
|
|
173
|
+
monaco.languages.setLanguageConfiguration(LANGUAGE_ID, mod.conf);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Attach to all currently open rapydscript models
|
|
178
|
+
monaco.editor.getModels().forEach(model => this._maybeAttach(model));
|
|
179
|
+
|
|
180
|
+
// Attach to future models
|
|
181
|
+
this._disposables.push(
|
|
182
|
+
monaco.editor.onDidCreateModel(model => this._maybeAttach(model))
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Detach when a model is disposed
|
|
186
|
+
this._disposables.push(
|
|
187
|
+
monaco.editor.onWillDisposeModel(model => this._detach(model))
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Register completion provider
|
|
191
|
+
const self = this;
|
|
192
|
+
this._disposables.push(
|
|
193
|
+
monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, {
|
|
194
|
+
triggerCharacters: ['.'],
|
|
195
|
+
provideCompletionItems(model, position) {
|
|
196
|
+
if (model.getLanguageId() !== LANGUAGE_ID) return { suggestions: [] };
|
|
197
|
+
const state = self._modelStates.get(model);
|
|
198
|
+
const scopeMap = state ? state._scopeMap : null;
|
|
199
|
+
const line_content = model.getLineContent(position.lineNumber);
|
|
200
|
+
const line_prefix = line_content.substring(0, position.column - 1);
|
|
201
|
+
return self._completions.getCompletions(
|
|
202
|
+
scopeMap,
|
|
203
|
+
position,
|
|
204
|
+
line_prefix,
|
|
205
|
+
monaco.languages.CompletionItemKind
|
|
206
|
+
);
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Register signature help provider
|
|
212
|
+
this._disposables.push(
|
|
213
|
+
monaco.languages.registerSignatureHelpProvider(LANGUAGE_ID, {
|
|
214
|
+
signatureHelpTriggerCharacters: ['(', ','],
|
|
215
|
+
signatureHelpRetriggerCharacters: [','],
|
|
216
|
+
provideSignatureHelp(model, position) {
|
|
217
|
+
if (model.getLanguageId() !== LANGUAGE_ID) return null;
|
|
218
|
+
const state = self._modelStates.get(model);
|
|
219
|
+
const scopeMap = state ? state._scopeMap : null;
|
|
220
|
+
const line_content = model.getLineContent(position.lineNumber);
|
|
221
|
+
const line_prefix = line_content.substring(0, position.column - 1);
|
|
222
|
+
return self._signature_help.getSignatureHelp(
|
|
223
|
+
scopeMap,
|
|
224
|
+
position,
|
|
225
|
+
line_prefix
|
|
226
|
+
);
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// Register hover provider
|
|
232
|
+
this._disposables.push(
|
|
233
|
+
monaco.languages.registerHoverProvider(LANGUAGE_ID, {
|
|
234
|
+
provideHover(model, position) {
|
|
235
|
+
if (model.getLanguageId() !== LANGUAGE_ID) return null;
|
|
236
|
+
const state = self._modelStates.get(model);
|
|
237
|
+
const scopeMap = state ? state._scopeMap : null;
|
|
238
|
+
const word_info = model.getWordAtPosition(position);
|
|
239
|
+
const word = word_info ? word_info.word : null;
|
|
240
|
+
const line_content = model.getLineContent(position.lineNumber);
|
|
241
|
+
// Text before the word's start column, used to detect dot-chain access
|
|
242
|
+
const line_before_word = word_info
|
|
243
|
+
? line_content.substring(0, word_info.startColumn - 1)
|
|
244
|
+
: '';
|
|
245
|
+
return self._hover.getHover(scopeMap, position, word, line_before_word);
|
|
246
|
+
},
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---- internal ----------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
_maybeAttach(model) {
|
|
254
|
+
if (model.getLanguageId() !== LANGUAGE_ID) return;
|
|
255
|
+
if (this._modelStates.has(model)) return;
|
|
256
|
+
this._modelStates.set(model, new ModelState(model, this));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_detach(model) {
|
|
260
|
+
const state = this._modelStates.get(model);
|
|
261
|
+
if (!state) return;
|
|
262
|
+
state.dispose();
|
|
263
|
+
this._modelStates.delete(model);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---- public API --------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Replace / merge virtual files available to the import resolver.
|
|
270
|
+
* Triggers an immediate re-check of all open models.
|
|
271
|
+
* @param {Object.<string,string>} files
|
|
272
|
+
*/
|
|
273
|
+
setVirtualFiles(files) {
|
|
274
|
+
Object.assign(this._virtualFiles, files);
|
|
275
|
+
this._completions.setVirtualFiles(this._virtualFiles);
|
|
276
|
+
this._modelStates.forEach(state => state._schedule(0));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Remove a virtual file by name.
|
|
281
|
+
* @param {string} name
|
|
282
|
+
*/
|
|
283
|
+
removeVirtualFile(name) {
|
|
284
|
+
delete this._virtualFiles[name];
|
|
285
|
+
this._completions.setVirtualFiles(this._virtualFiles);
|
|
286
|
+
this._modelStates.forEach(state => state._schedule(0));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Add additional global symbol names (e.g. from d.ts) so they are not
|
|
291
|
+
* flagged as undefined. Phase 6 will call this automatically.
|
|
292
|
+
* @param {string[]} names
|
|
293
|
+
*/
|
|
294
|
+
addGlobals(names) {
|
|
295
|
+
this._diagnostics.addGlobals(names);
|
|
296
|
+
this._modelStates.forEach(state => state._schedule(0));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Return the most recently built ScopeMap for a given Monaco model.
|
|
301
|
+
* Returns null if the model is not attached or has not been analysed yet.
|
|
302
|
+
* Used by Phase 3 (completions) and Phase 5 (hover).
|
|
303
|
+
* @param {object} model - Monaco ITextModel
|
|
304
|
+
* @returns {import('./scope.js').ScopeMap|null}
|
|
305
|
+
*/
|
|
306
|
+
getScopeMap(model) {
|
|
307
|
+
const state = this._modelStates.get(model);
|
|
308
|
+
return state ? state._scopeMap : null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Stub for Phase 6 (DtsRegistry). Calling it is safe but has no effect yet.
|
|
313
|
+
* @param {string} _name
|
|
314
|
+
* @param {string} _dtsText
|
|
315
|
+
*/
|
|
316
|
+
addDts(name, dtsText) {
|
|
317
|
+
this._dts.addDts(name, dtsText);
|
|
318
|
+
const new_names = this._dts.getGlobalNames();
|
|
319
|
+
// Suppress undef warnings for newly added globals
|
|
320
|
+
this._diagnostics.addGlobals(new_names);
|
|
321
|
+
// Rebuild builtin list so completions include new DTS names (preserve extraBuiltins)
|
|
322
|
+
const builtin_names = BASE_BUILTINS.concat(this._extraBuiltinNames).concat(new_names);
|
|
323
|
+
this._completions = new CompletionEngine(this._analyzer, {
|
|
324
|
+
virtualFiles: this._virtualFiles,
|
|
325
|
+
stdlibFiles: this._stdlibFiles,
|
|
326
|
+
builtinNames: builtin_names,
|
|
327
|
+
dtsRegistry: this._dts,
|
|
328
|
+
builtinsRegistry: this._builtins,
|
|
329
|
+
});
|
|
330
|
+
// Re-run diagnostics on all open models
|
|
331
|
+
this._modelStates.forEach(state => state._schedule(0));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Asynchronously fetch a .d.ts file using the `loadDts` callback supplied
|
|
336
|
+
* in options, then register it with addDts().
|
|
337
|
+
*
|
|
338
|
+
* Requires `options.loadDts` to have been provided at construction time.
|
|
339
|
+
* Returns a Promise that resolves when the file has been parsed and registered.
|
|
340
|
+
*
|
|
341
|
+
* @param {string} name identifier passed to the loadDts callback
|
|
342
|
+
* @returns {Promise<void>}
|
|
343
|
+
*/
|
|
344
|
+
loadDts(name) {
|
|
345
|
+
if (!this._loadDts) {
|
|
346
|
+
return Promise.reject(
|
|
347
|
+
new Error('registerRapydScript: options.loadDts was not provided')
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
const self = this;
|
|
351
|
+
return Promise.resolve(this._loadDts(name)).then(function (text) {
|
|
352
|
+
self.addDts(name, text);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Remove all Monaco providers and event listeners registered by this service.
|
|
358
|
+
*/
|
|
359
|
+
dispose() {
|
|
360
|
+
this._modelStates.forEach(state => state.dispose());
|
|
361
|
+
this._modelStates.clear();
|
|
362
|
+
this._disposables.forEach(d => d.dispose());
|
|
363
|
+
this._disposables = [];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Exported factory
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Register the RapydScript language service with a Monaco instance.
|
|
373
|
+
*
|
|
374
|
+
* @param {object} monaco - the Monaco global (window.monaco or require('monaco-editor'))
|
|
375
|
+
* @param {object} options
|
|
376
|
+
* @param {object} options.compiler - window.RapydScript compiled bundle (required)
|
|
377
|
+
* @param {object} [options.virtualFiles] - map of module-name → source
|
|
378
|
+
* @param {Array} [options.dtsFiles] - [{name, content}] d.ts files (Phase 6)
|
|
379
|
+
* @param {number} [options.parseDelay=300] - debounce ms
|
|
380
|
+
* @param {object} [options.extraBuiltins] - extra {name: true} globals to suppress undef warnings
|
|
381
|
+
* @param {string} [options.pythonFlags] - comma-separated python flags (e.g. "dict_literals,overload_getitem")
|
|
382
|
+
* @returns {RapydScriptLanguageService}
|
|
383
|
+
*/
|
|
384
|
+
export function registerRapydScript(monaco, options) {
|
|
385
|
+
return new RapydScriptLanguageService(monaco, options || {});
|
|
386
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// scope.js — ScopeMap data structures for the RapydScript language service.
|
|
2
|
+
//
|
|
3
|
+
// Position convention throughout this file:
|
|
4
|
+
// line — 1-indexed (matches Monaco's lineNumber)
|
|
5
|
+
// column — 1-indexed (matches Monaco's column)
|
|
6
|
+
// AST tokens use 1-indexed lines and 0-indexed cols; the analyzer converts
|
|
7
|
+
// before storing so callers always work with fully 1-indexed numbers.
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// SymbolInfo
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export class SymbolInfo {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} opts
|
|
16
|
+
* @param {string} opts.name
|
|
17
|
+
* @param {string} opts.kind 'function'|'method'|'class'|'variable'|'parameter'|'import'
|
|
18
|
+
* @param {{line:number,column:number}} opts.defined_at 1-indexed line and column
|
|
19
|
+
* @param {number} opts.scope_depth
|
|
20
|
+
* @param {string|null} [opts.doc]
|
|
21
|
+
* @param {Array|null} [opts.params] [{name, is_rest, is_kwargs}] for functions/methods
|
|
22
|
+
*/
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.name = opts.name;
|
|
25
|
+
this.kind = opts.kind;
|
|
26
|
+
this.defined_at = opts.defined_at; // {line, column} both 1-indexed
|
|
27
|
+
this.scope_depth = opts.scope_depth;
|
|
28
|
+
this.doc = opts.doc || null;
|
|
29
|
+
this.params = opts.params || null;
|
|
30
|
+
this.inferred_class = opts.inferred_class || null; // 'ClassName' when x = ClassName(...)
|
|
31
|
+
this.dts_call_path = opts.dts_call_path || null; // 'ns.getServer' when x = ns.getServer(...)
|
|
32
|
+
this.type = null; // Phase 6: TypeInfo
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// ScopeFrame
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export class ScopeFrame {
|
|
41
|
+
/**
|
|
42
|
+
* @param {object} opts
|
|
43
|
+
* @param {string} opts.kind 'module'|'function'|'class'|'comprehension'|'block'
|
|
44
|
+
* @param {string|null} opts.name
|
|
45
|
+
* @param {{start:{line,column}, end:{line,column}}} opts.range 1-indexed
|
|
46
|
+
* @param {ScopeFrame|null} opts.parent
|
|
47
|
+
* @param {number} opts.depth
|
|
48
|
+
*/
|
|
49
|
+
constructor(opts) {
|
|
50
|
+
this.kind = opts.kind;
|
|
51
|
+
this.name = opts.name || null;
|
|
52
|
+
this.range = opts.range;
|
|
53
|
+
this.parent = opts.parent || null;
|
|
54
|
+
this.depth = opts.depth;
|
|
55
|
+
this.symbols = new Map(); // name → SymbolInfo
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Add or overwrite a symbol in this frame. */
|
|
59
|
+
addSymbol(sym) {
|
|
60
|
+
this.symbols.set(sym.name, sym);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Return a symbol by exact name, or null. */
|
|
64
|
+
getSymbol(name) {
|
|
65
|
+
return this.symbols.get(name) || null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Range helpers (all positions 1-indexed)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
function range_contains(range, line, column) {
|
|
74
|
+
const { start, end } = range;
|
|
75
|
+
const after_start = (line > start.line) || (line === start.line && column >= start.column);
|
|
76
|
+
const before_end = (line < end.line) || (line === end.line && column <= end.column);
|
|
77
|
+
return after_start && before_end;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// ScopeMap
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
export class ScopeMap {
|
|
85
|
+
constructor() {
|
|
86
|
+
/** All frames in document order (outer to inner). @type {ScopeFrame[]} */
|
|
87
|
+
this.frames = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** @internal Called by ScopeBuilder as frames are created. */
|
|
91
|
+
addFrame(frame) {
|
|
92
|
+
this.frames.push(frame);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Return all ScopeFrames whose range contains (line, column), innermost first.
|
|
97
|
+
* Both line and column are 1-indexed (Monaco format).
|
|
98
|
+
* @param {number} line
|
|
99
|
+
* @param {number} column
|
|
100
|
+
* @returns {ScopeFrame[]}
|
|
101
|
+
*/
|
|
102
|
+
getScopesAtPosition(line, column) {
|
|
103
|
+
return this.frames
|
|
104
|
+
.filter(f => range_contains(f.range, line, column))
|
|
105
|
+
.sort((a, b) => b.depth - a.depth);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Return every symbol visible at (line, column).
|
|
110
|
+
* When two scopes define the same name, the innermost one wins.
|
|
111
|
+
* Both line and column are 1-indexed (Monaco format).
|
|
112
|
+
* @param {number} line
|
|
113
|
+
* @param {number} column
|
|
114
|
+
* @returns {SymbolInfo[]}
|
|
115
|
+
*/
|
|
116
|
+
getSymbolsAtPosition(line, column) {
|
|
117
|
+
const scopes = this.getScopesAtPosition(line, column);
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
const result = [];
|
|
120
|
+
for (const scope of scopes) {
|
|
121
|
+
for (const [name, sym] of scope.symbols) {
|
|
122
|
+
if (!seen.has(name)) {
|
|
123
|
+
seen.add(name);
|
|
124
|
+
result.push(sym);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Look up a single symbol by name that is visible at (line, column).
|
|
133
|
+
* Returns the innermost definition, or null if not found.
|
|
134
|
+
* Both line and column are 1-indexed (Monaco format).
|
|
135
|
+
* @param {string} name
|
|
136
|
+
* @param {number} line
|
|
137
|
+
* @param {number} column
|
|
138
|
+
* @returns {SymbolInfo|null}
|
|
139
|
+
*/
|
|
140
|
+
getSymbol(name, line, column) {
|
|
141
|
+
const scopes = this.getScopesAtPosition(line, column);
|
|
142
|
+
for (const scope of scopes) {
|
|
143
|
+
const sym = scope.getSymbol(name);
|
|
144
|
+
if (sym) return sym;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Return all symbols across every scope in the document.
|
|
151
|
+
* @returns {SymbolInfo[]}
|
|
152
|
+
*/
|
|
153
|
+
getAllSymbols() {
|
|
154
|
+
const result = [];
|
|
155
|
+
for (const frame of this.frames) {
|
|
156
|
+
for (const sym of frame.symbols.values()) {
|
|
157
|
+
result.push(sym);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// signature.js — Signature help provider for the RapydScript language service.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// import { SignatureHelpEngine, detect_call_context } from './signature.js';
|
|
5
|
+
// const engine = new SignatureHelpEngine();
|
|
6
|
+
// const help = engine.getSignatureHelp(scopeMap, position, linePrefix);
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Context detection
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Walk backwards through linePrefix to find the nearest unclosed `(`.
|
|
14
|
+
* Returns the callee name and which argument position the cursor is in,
|
|
15
|
+
* or null if the cursor is not inside a call.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} linePrefix text from column 1 up to (but not including) the cursor
|
|
18
|
+
* @returns {{ callee: string, activeParameter: number }|null}
|
|
19
|
+
*/
|
|
20
|
+
export function detect_call_context(linePrefix) {
|
|
21
|
+
let depth = 0;
|
|
22
|
+
let paren_pos = -1;
|
|
23
|
+
|
|
24
|
+
// Scan backwards to find the outermost unclosed '('
|
|
25
|
+
for (let i = linePrefix.length - 1; i >= 0; i--) {
|
|
26
|
+
const ch = linePrefix[i];
|
|
27
|
+
if (ch === ')') {
|
|
28
|
+
depth++;
|
|
29
|
+
} else if (ch === '(') {
|
|
30
|
+
if (depth === 0) {
|
|
31
|
+
paren_pos = i;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
depth--;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (paren_pos < 0) return null; // not inside a call
|
|
39
|
+
|
|
40
|
+
// Extract the callee: identifier immediately before the '('
|
|
41
|
+
const before_paren = linePrefix.slice(0, paren_pos).trimEnd();
|
|
42
|
+
const callee_match = before_paren.match(/(\w+)$/);
|
|
43
|
+
if (!callee_match) return null;
|
|
44
|
+
|
|
45
|
+
// Count commas at depth 0 between the '(' and the cursor
|
|
46
|
+
let active_param = 0;
|
|
47
|
+
let inner_depth = 0;
|
|
48
|
+
for (let j = paren_pos + 1; j < linePrefix.length; j++) {
|
|
49
|
+
const ch = linePrefix[j];
|
|
50
|
+
if (ch === '(' || ch === '[') {
|
|
51
|
+
inner_depth++;
|
|
52
|
+
} else if (ch === ')' || ch === ']') {
|
|
53
|
+
inner_depth--;
|
|
54
|
+
} else if (ch === ',' && inner_depth === 0) {
|
|
55
|
+
active_param++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { callee: callee_match[1], activeParameter: active_param };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// SignatureHelpEngine
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
export class SignatureHelpEngine {
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {import('./builtins.js').BuiltinsRegistry|null} [builtinsRegistry]
|
|
70
|
+
* Optional built-in stubs; used as fallback when the callee is not in ScopeMap.
|
|
71
|
+
*/
|
|
72
|
+
constructor(builtinsRegistry) {
|
|
73
|
+
this._builtins = builtinsRegistry || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Return a Monaco ISignatureHelpResult for the cursor position, or null
|
|
78
|
+
* if the cursor is not inside a known function call.
|
|
79
|
+
*
|
|
80
|
+
* Priority: ScopeMap (user-defined) → built-in stubs.
|
|
81
|
+
*
|
|
82
|
+
* @param {import('./scope.js').ScopeMap|null} scopeMap
|
|
83
|
+
* @param {{lineNumber:number,column:number}} position 1-indexed Monaco position
|
|
84
|
+
* @param {string} linePrefix text on the current line up to the cursor
|
|
85
|
+
* @returns {{ value: object, dispose: function }|null}
|
|
86
|
+
*/
|
|
87
|
+
getSignatureHelp(scopeMap, position, linePrefix) {
|
|
88
|
+
const ctx = detect_call_context(linePrefix);
|
|
89
|
+
if (!ctx) return null;
|
|
90
|
+
|
|
91
|
+
// 1. User-defined function from ScopeMap
|
|
92
|
+
if (scopeMap) {
|
|
93
|
+
const sym = scopeMap.getSymbol(ctx.callee, position.lineNumber, position.column);
|
|
94
|
+
if (sym && sym.params && sym.params.length > 0) {
|
|
95
|
+
const param_labels = sym.params.map(function (p) {
|
|
96
|
+
if (p.is_separator) return p.name; // '/' or '*'
|
|
97
|
+
if (p.is_kwargs) return '**' + p.name;
|
|
98
|
+
if (p.is_rest) return '*' + p.name;
|
|
99
|
+
return p.name;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let active = ctx.activeParameter;
|
|
103
|
+
if (active > param_labels.length - 1) active = param_labels.length - 1;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
value: {
|
|
107
|
+
signatures: [{
|
|
108
|
+
label: ctx.callee + '(' + param_labels.join(', ') + ')',
|
|
109
|
+
documentation: sym.doc || undefined,
|
|
110
|
+
parameters: param_labels.map(function (l) { return { label: l }; }),
|
|
111
|
+
}],
|
|
112
|
+
activeSignature: 0,
|
|
113
|
+
activeParameter: active,
|
|
114
|
+
},
|
|
115
|
+
dispose: function () {},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 2. Built-in stubs
|
|
121
|
+
if (this._builtins) {
|
|
122
|
+
const info = this._builtins.getSignatureInfo(ctx.callee);
|
|
123
|
+
if (info) {
|
|
124
|
+
let active = ctx.activeParameter;
|
|
125
|
+
if (active > info.params.length - 1) active = info.params.length - 1;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
value: {
|
|
129
|
+
signatures: [{
|
|
130
|
+
label: info.label,
|
|
131
|
+
documentation: info.doc || undefined,
|
|
132
|
+
parameters: info.params,
|
|
133
|
+
}],
|
|
134
|
+
activeSignature: 0,
|
|
135
|
+
activeParameter: active,
|
|
136
|
+
},
|
|
137
|
+
dispose: function () {},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
File without changes
|