jslike 1.0.0 → 1.2.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/package.json +7 -1
- package/src/editor/monaco/index.js +234 -0
- package/src/editor/wang-prism.js +136 -0
- package/src/index.js +48 -4
- package/src/interpreter/index.js +31 -4
- package/src/interpreter/interpreter.js +9 -2
- package/src/validator/index.js +103 -0
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jslike",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Production-ready JavaScript interpreter with full ES6+ support using Acorn parser",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./validator": "./src/validator/index.js",
|
|
10
|
+
"./editor/monaco": "./src/editor/monaco/index.js",
|
|
11
|
+
"./src/editor/wang-prism": "./src/editor/wang-prism.js"
|
|
12
|
+
},
|
|
7
13
|
"bin": {
|
|
8
14
|
"jslike": "./bin/jslike.js"
|
|
9
15
|
},
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Wang Language Definition for Monaco Editor
|
|
2
|
+
export const wangLanguage = {
|
|
3
|
+
defaultToken: 'invalid',
|
|
4
|
+
tokenPostfix: '.wang',
|
|
5
|
+
|
|
6
|
+
keywords: [
|
|
7
|
+
'let',
|
|
8
|
+
'const',
|
|
9
|
+
'var',
|
|
10
|
+
'if',
|
|
11
|
+
'else',
|
|
12
|
+
'for',
|
|
13
|
+
'while',
|
|
14
|
+
'do',
|
|
15
|
+
'break',
|
|
16
|
+
'continue',
|
|
17
|
+
'return',
|
|
18
|
+
'function',
|
|
19
|
+
'class',
|
|
20
|
+
'extends',
|
|
21
|
+
'constructor',
|
|
22
|
+
'async',
|
|
23
|
+
'await',
|
|
24
|
+
'import',
|
|
25
|
+
'export',
|
|
26
|
+
'from',
|
|
27
|
+
'as',
|
|
28
|
+
'try',
|
|
29
|
+
'catch',
|
|
30
|
+
'finally',
|
|
31
|
+
'throw',
|
|
32
|
+
'this',
|
|
33
|
+
'super',
|
|
34
|
+
'new',
|
|
35
|
+
'typeof',
|
|
36
|
+
'instanceof',
|
|
37
|
+
'in',
|
|
38
|
+
'of',
|
|
39
|
+
],
|
|
40
|
+
|
|
41
|
+
typeKeywords: [],
|
|
42
|
+
|
|
43
|
+
operators: [
|
|
44
|
+
'=>',
|
|
45
|
+
'+=',
|
|
46
|
+
'-=',
|
|
47
|
+
'*=',
|
|
48
|
+
'/=',
|
|
49
|
+
'===',
|
|
50
|
+
'!==',
|
|
51
|
+
'==',
|
|
52
|
+
'!=',
|
|
53
|
+
'<=',
|
|
54
|
+
'>=',
|
|
55
|
+
'<<',
|
|
56
|
+
'>>',
|
|
57
|
+
'>>>',
|
|
58
|
+
'&&',
|
|
59
|
+
'||',
|
|
60
|
+
'??',
|
|
61
|
+
'?.',
|
|
62
|
+
'...',
|
|
63
|
+
'++',
|
|
64
|
+
'--',
|
|
65
|
+
'**',
|
|
66
|
+
'+',
|
|
67
|
+
'-',
|
|
68
|
+
'*',
|
|
69
|
+
'/',
|
|
70
|
+
'%',
|
|
71
|
+
'&',
|
|
72
|
+
'|',
|
|
73
|
+
'^',
|
|
74
|
+
'~',
|
|
75
|
+
'!',
|
|
76
|
+
'?',
|
|
77
|
+
':',
|
|
78
|
+
'=',
|
|
79
|
+
'<',
|
|
80
|
+
'>',
|
|
81
|
+
],
|
|
82
|
+
|
|
83
|
+
symbols: /[=><!~?:&|+\-*\/\^%]+/,
|
|
84
|
+
|
|
85
|
+
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
|
|
86
|
+
|
|
87
|
+
tokenizer: {
|
|
88
|
+
root: [
|
|
89
|
+
// Identifiers and keywords
|
|
90
|
+
[
|
|
91
|
+
/[a-zA-Z_$][\w$]*/,
|
|
92
|
+
{
|
|
93
|
+
cases: {
|
|
94
|
+
'@keywords': 'keyword',
|
|
95
|
+
'true|false|null|undefined': 'constant',
|
|
96
|
+
'@default': 'identifier',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
|
|
101
|
+
// Whitespace
|
|
102
|
+
{ include: '@whitespace' },
|
|
103
|
+
|
|
104
|
+
// Delimiters and operators
|
|
105
|
+
[/[{}()\[\]]/, '@brackets'],
|
|
106
|
+
[/[<>](?!@symbols)/, '@brackets'],
|
|
107
|
+
[
|
|
108
|
+
/@symbols/,
|
|
109
|
+
{
|
|
110
|
+
cases: {
|
|
111
|
+
'@operators': 'operator',
|
|
112
|
+
'@default': '',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
|
|
117
|
+
// Numbers
|
|
118
|
+
[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
|
|
119
|
+
[/0[xX][0-9a-fA-F]+/, 'number.hex'],
|
|
120
|
+
[/\d+/, 'number'],
|
|
121
|
+
|
|
122
|
+
// Delimiter
|
|
123
|
+
[/[;,.]/, 'delimiter'],
|
|
124
|
+
|
|
125
|
+
// Strings
|
|
126
|
+
[/"([^"\\]|\\.)*$/, 'string.invalid'],
|
|
127
|
+
[/'([^'\\]|\\.)*$/, 'string.invalid'],
|
|
128
|
+
[/"/, 'string', '@string_double'],
|
|
129
|
+
[/'/, 'string', '@string_single'],
|
|
130
|
+
|
|
131
|
+
// Template literals
|
|
132
|
+
[/`/, 'string.template', '@template'],
|
|
133
|
+
|
|
134
|
+
// Regular expressions
|
|
135
|
+
[/\/(?=([^\/\\]|\\.)+\/)/, 'regexp', '@regexp'],
|
|
136
|
+
],
|
|
137
|
+
|
|
138
|
+
whitespace: [
|
|
139
|
+
[/[ \t\r\n]+/, ''],
|
|
140
|
+
[/\/\*/, 'comment', '@comment'],
|
|
141
|
+
[/\/\/.*$/, 'comment'],
|
|
142
|
+
],
|
|
143
|
+
|
|
144
|
+
comment: [
|
|
145
|
+
[/[^\/*]+/, 'comment'],
|
|
146
|
+
[/\*\//, 'comment', '@pop'],
|
|
147
|
+
[/[\/*]/, 'comment'],
|
|
148
|
+
],
|
|
149
|
+
|
|
150
|
+
string_double: [
|
|
151
|
+
[/[^\\"]+/, 'string'],
|
|
152
|
+
[/@escapes/, 'string.escape'],
|
|
153
|
+
[/\\./, 'string.escape.invalid'],
|
|
154
|
+
[/"/, 'string', '@pop'],
|
|
155
|
+
],
|
|
156
|
+
|
|
157
|
+
string_single: [
|
|
158
|
+
[/[^\\']+/, 'string'],
|
|
159
|
+
[/@escapes/, 'string.escape'],
|
|
160
|
+
[/\\./, 'string.escape.invalid'],
|
|
161
|
+
[/'/, 'string', '@pop'],
|
|
162
|
+
],
|
|
163
|
+
|
|
164
|
+
template: [
|
|
165
|
+
[/[^`\\$]+/, 'string.template'],
|
|
166
|
+
[/@escapes/, 'string.escape'],
|
|
167
|
+
[/\\./, 'string.escape.invalid'],
|
|
168
|
+
[/\${/, { token: 'delimiter.bracket', next: '@templateExpression' }],
|
|
169
|
+
[/`/, 'string.template', '@pop'],
|
|
170
|
+
],
|
|
171
|
+
|
|
172
|
+
templateExpression: [[/}/, { token: 'delimiter.bracket', next: '@pop' }], { include: 'root' }],
|
|
173
|
+
|
|
174
|
+
regexp: [
|
|
175
|
+
[
|
|
176
|
+
/(\{)(\d+(?:,\d*)?)(\})/,
|
|
177
|
+
['regexp.escape.control', 'regexp.escape.control', 'regexp.escape.control'],
|
|
178
|
+
],
|
|
179
|
+
[/(\[)([^\]\\]|\\.)*(\])/, 'regexp.escape.control'],
|
|
180
|
+
[/(\()(\?:|\?=|\?!)/, ['regexp.escape.control', 'regexp.escape.control']],
|
|
181
|
+
[/[()]/, 'regexp.escape.control'],
|
|
182
|
+
[/@escapes/, 'regexp.escape'],
|
|
183
|
+
[/\\./, 'regexp.escape'],
|
|
184
|
+
[/\/[gimsuy]*/, { token: 'regexp', bracket: '@close', next: '@pop' }],
|
|
185
|
+
[/./, 'regexp'],
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Register the language with Monaco
|
|
191
|
+
export function registerWangLanguage(monaco) {
|
|
192
|
+
monaco.languages.register({ id: 'wang' });
|
|
193
|
+
monaco.languages.setMonarchTokensProvider('wang', wangLanguage);
|
|
194
|
+
|
|
195
|
+
// Configuration for brackets and auto-closing
|
|
196
|
+
monaco.languages.setLanguageConfiguration('wang', {
|
|
197
|
+
comments: {
|
|
198
|
+
lineComment: '//',
|
|
199
|
+
blockComment: ['/*', '*/'],
|
|
200
|
+
},
|
|
201
|
+
brackets: [
|
|
202
|
+
['{', '}'],
|
|
203
|
+
['[', ']'],
|
|
204
|
+
['(', ')'],
|
|
205
|
+
],
|
|
206
|
+
autoClosingPairs: [
|
|
207
|
+
{ open: '{', close: '}' },
|
|
208
|
+
{ open: '[', close: ']' },
|
|
209
|
+
{ open: '(', close: ')' },
|
|
210
|
+
{ open: '"', close: '"', notIn: ['string'] },
|
|
211
|
+
{ open: "'", close: "'", notIn: ['string'] },
|
|
212
|
+
{ open: '`', close: '`', notIn: ['string'] },
|
|
213
|
+
{ open: '/*', close: '*/' },
|
|
214
|
+
],
|
|
215
|
+
surroundingPairs: [
|
|
216
|
+
{ open: '{', close: '}' },
|
|
217
|
+
{ open: '[', close: ']' },
|
|
218
|
+
{ open: '(', close: ')' },
|
|
219
|
+
{ open: '"', close: '"' },
|
|
220
|
+
{ open: "'", close: "'" },
|
|
221
|
+
{ open: '`', close: '`' },
|
|
222
|
+
],
|
|
223
|
+
folding: {
|
|
224
|
+
markers: {
|
|
225
|
+
start: new RegExp('^\\s*//\\s*#?region\\b'),
|
|
226
|
+
end: new RegExp('^\\s*//\\s*#?endregion\\b'),
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
indentationRules: {
|
|
230
|
+
increaseIndentPattern: new RegExp('^.*(\\{[^}]*|\\([^)]*|\\[[^\\]]*)$'),
|
|
231
|
+
decreaseIndentPattern: new RegExp('^\\s*(\\}|\\)|\\])'),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Wang Language Definition for Prism.js
|
|
2
|
+
/* global Prism */
|
|
3
|
+
(function (Prism) {
|
|
4
|
+
Prism.languages.wang = {
|
|
5
|
+
// Comments
|
|
6
|
+
comment: [
|
|
7
|
+
{
|
|
8
|
+
pattern: /\/\*[\s\S]*?\*\//,
|
|
9
|
+
greedy: true,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
pattern: /\/\/.*/,
|
|
13
|
+
greedy: true,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
// Template literals with interpolation
|
|
18
|
+
'template-string': {
|
|
19
|
+
pattern: /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,
|
|
20
|
+
greedy: true,
|
|
21
|
+
inside: {
|
|
22
|
+
'template-punctuation': {
|
|
23
|
+
pattern: /^`|`$/,
|
|
24
|
+
alias: 'string',
|
|
25
|
+
},
|
|
26
|
+
interpolation: {
|
|
27
|
+
pattern: /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,
|
|
28
|
+
lookbehind: true,
|
|
29
|
+
inside: {
|
|
30
|
+
'interpolation-punctuation': {
|
|
31
|
+
pattern: /^\$\{|\}$/,
|
|
32
|
+
alias: 'punctuation',
|
|
33
|
+
},
|
|
34
|
+
rest: Prism.languages.wang,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
string: /[\s\S]+/,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Strings
|
|
42
|
+
string: [
|
|
43
|
+
{
|
|
44
|
+
pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
|
|
45
|
+
greedy: true,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
// Regular expressions
|
|
50
|
+
regex: {
|
|
51
|
+
pattern: /\/(?:\\.|[^\/\\\r\n])+\/[gimsuy]*/,
|
|
52
|
+
greedy: true,
|
|
53
|
+
inside: {
|
|
54
|
+
'regex-delimiter': {
|
|
55
|
+
pattern: /^\/|\/[gimsuy]*$/,
|
|
56
|
+
alias: 'punctuation',
|
|
57
|
+
},
|
|
58
|
+
'regex-flags': {
|
|
59
|
+
pattern: /[gimsuy]+$/,
|
|
60
|
+
alias: 'keyword',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Keywords
|
|
66
|
+
keyword:
|
|
67
|
+
/\b(?:let|const|var|if|else|for|while|do|break|continue|return|function|class|extends|constructor|async|await|import|export|from|as|try|catch|finally|throw|new|this|super|typeof|instanceof|in|of)\b/,
|
|
68
|
+
|
|
69
|
+
// Boolean and null values
|
|
70
|
+
boolean: /\b(?:true|false)\b/,
|
|
71
|
+
null: /\b(?:null|undefined)\b/,
|
|
72
|
+
|
|
73
|
+
// Numbers
|
|
74
|
+
number: /\b(?:0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b/,
|
|
75
|
+
|
|
76
|
+
// Arrow function operator
|
|
77
|
+
'arrow-operator': {
|
|
78
|
+
pattern: /=>/,
|
|
79
|
+
alias: 'operator',
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Operators (order matters for matching)
|
|
83
|
+
operator:
|
|
84
|
+
/\+\+|--|===|!==|==|!=|<=|>=|<<|>>>|>>|&&|\|\||[?]{2}|\?\.|\.{3}|\*\*|\+=|-=|\*=|\/=|[+\-*/%<>&|^~!?:]=?/,
|
|
85
|
+
|
|
86
|
+
// Punctuation
|
|
87
|
+
punctuation: /[{}[\];(),.:]/,
|
|
88
|
+
|
|
89
|
+
// Functions (pattern for function calls)
|
|
90
|
+
function: /\b[a-zA-Z_$][\w$]*(?=\s*\()/,
|
|
91
|
+
|
|
92
|
+
// Class names (after class or extends)
|
|
93
|
+
'class-name': {
|
|
94
|
+
pattern: /(\b(?:class|extends)\s+)[a-zA-Z_$][\w$]*/,
|
|
95
|
+
lookbehind: true,
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Property names in object literals
|
|
99
|
+
property: {
|
|
100
|
+
pattern:
|
|
101
|
+
/((?:^|[,{])[ \t]*)(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*|"(?:\\.|[^\\"\r\n])*"|'(?:\\.|[^\\'\r\n])*')(?=\s*:)/m,
|
|
102
|
+
lookbehind: true,
|
|
103
|
+
greedy: true,
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Variables and identifiers (must come last)
|
|
107
|
+
variable: /\b[a-zA-Z_$][\w$]*/,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Set wang as an alias for javascript-like highlighting
|
|
111
|
+
Prism.languages.wang['class-name'].pattern = /(\b(?:class|extends|new)\s+)[a-zA-Z_$][\w$]*/;
|
|
112
|
+
|
|
113
|
+
// Hook to handle special Wang syntax
|
|
114
|
+
Prism.hooks.add('before-tokenize', function (env) {
|
|
115
|
+
if (env.language !== 'wang') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Hook to add special classes for Wang-specific features
|
|
121
|
+
Prism.hooks.add('wrap', function (env) {
|
|
122
|
+
if (env.language !== 'wang') {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add special class for Wang keywords
|
|
127
|
+
if (env.type === 'keyword' && ['import', 'export', 'from', 'as'].includes(env.content)) {
|
|
128
|
+
env.classes.push('wang-module');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
})(Prism);
|
|
132
|
+
|
|
133
|
+
// Export for module systems
|
|
134
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
135
|
+
module.exports = Prism.languages.wang;
|
|
136
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Use bundled Acorn parser for zero runtime dependencies
|
|
2
2
|
import { parse as acornParse } from './parser.js';
|
|
3
3
|
import { Interpreter } from './interpreter/interpreter.js';
|
|
4
|
-
import { Environment } from './runtime/environment.js';
|
|
4
|
+
import { Environment, ReturnValue } from './runtime/environment.js';
|
|
5
5
|
import { createGlobalEnvironment } from './runtime/builtins.js';
|
|
6
6
|
|
|
7
7
|
// Helper to detect if code contains module syntax or top-level await
|
|
@@ -231,7 +231,8 @@ export function parse(code, options = {}) {
|
|
|
231
231
|
return acornParse(processedCode, {
|
|
232
232
|
ecmaVersion: 2022, // Support ES2022 features (including top-level await)
|
|
233
233
|
sourceType: sourceType,
|
|
234
|
-
locations:
|
|
234
|
+
locations: true, // Track source locations for better error messages
|
|
235
|
+
allowReturnOutsideFunction: true // Allow top-level return statements
|
|
235
236
|
});
|
|
236
237
|
} catch (error) {
|
|
237
238
|
// Reformat error message for consistency
|
|
@@ -318,10 +319,10 @@ export async function execute(code, env = null, options = {}) {
|
|
|
318
319
|
|
|
319
320
|
if (needsAsync) {
|
|
320
321
|
const result = await interpreter.evaluateAsync(ast, userEnv);
|
|
321
|
-
return result;
|
|
322
|
+
return result instanceof ReturnValue ? result.value : result;
|
|
322
323
|
} else {
|
|
323
324
|
const result = interpreter.evaluate(ast, userEnv);
|
|
324
|
-
return result;
|
|
325
|
+
return result instanceof ReturnValue ? result.value : result;
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
|
|
@@ -335,3 +336,46 @@ export const isTopLevelAwait = containsModuleSyntax;
|
|
|
335
336
|
|
|
336
337
|
export { Interpreter } from './interpreter/interpreter.js';
|
|
337
338
|
export { Environment } from './runtime/environment.js';
|
|
339
|
+
export { WangInterpreter, InMemoryModuleResolver } from './interpreter/index.js';
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Abstract base class for module resolution
|
|
343
|
+
* Extend this class to implement custom module loading strategies
|
|
344
|
+
*/
|
|
345
|
+
export class ModuleResolver {
|
|
346
|
+
/**
|
|
347
|
+
* Resolve a module and return its code
|
|
348
|
+
* @param {string} modulePath - The module path to resolve
|
|
349
|
+
* @param {string} [fromPath] - The path of the importing module
|
|
350
|
+
* @returns {Promise<ModuleResolution>} The resolved module
|
|
351
|
+
*/
|
|
352
|
+
async resolve(modulePath, fromPath) {
|
|
353
|
+
throw new Error('ModuleResolver.resolve() must be implemented by subclass');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Check if a module exists
|
|
358
|
+
* @param {string} modulePath - The module path to check
|
|
359
|
+
* @param {string} [fromPath] - The path of the importing module
|
|
360
|
+
* @returns {Promise<boolean>} Whether the module exists
|
|
361
|
+
*/
|
|
362
|
+
async exists(modulePath, fromPath) {
|
|
363
|
+
throw new Error('ModuleResolver.exists() must be implemented by subclass');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* List available modules
|
|
368
|
+
* @param {string} [prefix] - Optional prefix to filter modules
|
|
369
|
+
* @returns {Promise<string[]>} List of module paths
|
|
370
|
+
*/
|
|
371
|
+
async list(prefix) {
|
|
372
|
+
throw new Error('ModuleResolver.list() must be implemented by subclass');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @typedef {Object} ModuleResolution
|
|
378
|
+
* @property {string} code - The module source code
|
|
379
|
+
* @property {string} path - The resolved module path
|
|
380
|
+
* @property {any} [metadata] - Optional metadata about the module
|
|
381
|
+
*/
|
package/src/interpreter/index.js
CHANGED
|
@@ -42,6 +42,13 @@ export class WangInterpreter {
|
|
|
42
42
|
return this.setVariable(name, func);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Expose global context for accessing variables after execution
|
|
46
|
+
get globalContext() {
|
|
47
|
+
return {
|
|
48
|
+
variables: this.persistentEnv?.vars || new Map()
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
createExecutionEnvironment() {
|
|
46
53
|
// Create fresh environment for each execution
|
|
47
54
|
const env = createEnvironment();
|
|
@@ -164,11 +171,31 @@ export class InMemoryModuleResolver {
|
|
|
164
171
|
this.modules = new Map();
|
|
165
172
|
}
|
|
166
173
|
|
|
167
|
-
addModule(name, code) {
|
|
168
|
-
this.modules.set(name, code);
|
|
174
|
+
addModule(name, code, metadata) {
|
|
175
|
+
this.modules.set(name, { code, metadata });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async resolve(modulePath, fromPath) {
|
|
179
|
+
const module = this.modules.get(modulePath);
|
|
180
|
+
if (!module) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
code: typeof module === 'string' ? module : module.code,
|
|
185
|
+
path: modulePath,
|
|
186
|
+
metadata: module.metadata
|
|
187
|
+
};
|
|
169
188
|
}
|
|
170
189
|
|
|
171
|
-
async
|
|
172
|
-
return this.modules.
|
|
190
|
+
async exists(modulePath, fromPath) {
|
|
191
|
+
return this.modules.has(modulePath);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async list(prefix) {
|
|
195
|
+
const paths = Array.from(this.modules.keys());
|
|
196
|
+
if (prefix) {
|
|
197
|
+
return paths.filter(p => p.startsWith(prefix));
|
|
198
|
+
}
|
|
199
|
+
return paths;
|
|
173
200
|
}
|
|
174
201
|
}
|
|
@@ -75,6 +75,10 @@ export class Interpreter {
|
|
|
75
75
|
let result = undefined;
|
|
76
76
|
for (const statement of node.body) {
|
|
77
77
|
result = await this.evaluateAsync(statement, env);
|
|
78
|
+
// Handle top-level return and throw
|
|
79
|
+
if (result instanceof ReturnValue || result instanceof ThrowSignal) {
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
78
82
|
}
|
|
79
83
|
return result;
|
|
80
84
|
}
|
|
@@ -1199,11 +1203,14 @@ export class Interpreter {
|
|
|
1199
1203
|
moduleExports = this.moduleCache.get(modulePath);
|
|
1200
1204
|
} else {
|
|
1201
1205
|
// Resolve and load module code
|
|
1202
|
-
const
|
|
1203
|
-
if (!
|
|
1206
|
+
const resolution = await this.moduleResolver.resolve(modulePath);
|
|
1207
|
+
if (!resolution) {
|
|
1204
1208
|
throw new Error(`Cannot find module '${modulePath}'`);
|
|
1205
1209
|
}
|
|
1206
1210
|
|
|
1211
|
+
// Handle both old (string) and new (ModuleResolution) formats
|
|
1212
|
+
const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
|
|
1213
|
+
|
|
1207
1214
|
// Parse and execute module in its own environment
|
|
1208
1215
|
const moduleAst = acornParse(moduleCode, {
|
|
1209
1216
|
ecmaVersion: 2020,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WangValidator - Syntax validation for Wang/JSLike code
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parse, preprocessCode } from '../index.js';
|
|
6
|
+
|
|
7
|
+
export class WangValidator {
|
|
8
|
+
/**
|
|
9
|
+
* Validate code and return validation result
|
|
10
|
+
* @param {string} code - The code to validate
|
|
11
|
+
* @param {object} options - Validation options
|
|
12
|
+
* @param {boolean} options.includeAST - Whether to include AST in result
|
|
13
|
+
* @returns {object} Validation result
|
|
14
|
+
*/
|
|
15
|
+
validate(code, options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
// Preprocess code (handles ASI, etc.)
|
|
18
|
+
const processed = preprocessCode(code);
|
|
19
|
+
|
|
20
|
+
// Parse the code
|
|
21
|
+
const ast = parse(processed);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
valid: true,
|
|
25
|
+
ast: options.includeAST ? ast : undefined
|
|
26
|
+
};
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// Extract line and column from error
|
|
29
|
+
const line = error.loc?.line || 1;
|
|
30
|
+
const column = error.loc?.column || 0;
|
|
31
|
+
|
|
32
|
+
// Generate suggestion for common errors
|
|
33
|
+
const suggestion = this.getSuggestion(error, code);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
valid: false,
|
|
37
|
+
error: {
|
|
38
|
+
message: error.message,
|
|
39
|
+
line,
|
|
40
|
+
column,
|
|
41
|
+
suggestion
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Generate suggestions for common syntax errors
|
|
49
|
+
* @param {Error} error - The parse error
|
|
50
|
+
* @param {string} code - The original code
|
|
51
|
+
* @returns {string|undefined} Suggestion text
|
|
52
|
+
*/
|
|
53
|
+
getSuggestion(error, code) {
|
|
54
|
+
const message = error.message.toLowerCase();
|
|
55
|
+
|
|
56
|
+
// Common error patterns and suggestions
|
|
57
|
+
if (message.includes('unexpected token')) {
|
|
58
|
+
if (message.includes('}')) {
|
|
59
|
+
return 'Check for missing opening brace or extra closing brace';
|
|
60
|
+
}
|
|
61
|
+
if (message.includes('{')) {
|
|
62
|
+
return 'Check for missing closing brace or misplaced opening brace';
|
|
63
|
+
}
|
|
64
|
+
if (message.includes(')')) {
|
|
65
|
+
return 'Check for missing opening parenthesis or extra closing parenthesis';
|
|
66
|
+
}
|
|
67
|
+
if (message.includes('(')) {
|
|
68
|
+
return 'Check for missing closing parenthesis';
|
|
69
|
+
}
|
|
70
|
+
return 'Check syntax near the error location';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (message.includes('unterminated string')) {
|
|
74
|
+
return 'Add closing quote to string literal';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (message.includes('unterminated template')) {
|
|
78
|
+
return 'Add closing backtick to template literal';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (message.includes('identifier expected')) {
|
|
82
|
+
return 'Provide a valid identifier name';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (message.includes('unexpected end of input')) {
|
|
86
|
+
return 'Check for unclosed brackets, braces, or parentheses';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (message.includes('invalid left-hand side')) {
|
|
90
|
+
return 'Check that assignment target is a valid variable or property';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (message.includes('duplicate parameter')) {
|
|
94
|
+
return 'Remove duplicate parameter name in function definition';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (message.includes('strict mode')) {
|
|
98
|
+
return 'This syntax is not allowed in strict mode';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|