jslike 1.0.0 → 1.1.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 CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "jslike",
3
- "version": "1.0.0",
3
+ "version": "1.1.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
@@ -335,3 +335,46 @@ export const isTopLevelAwait = containsModuleSyntax;
335
335
 
336
336
  export { Interpreter } from './interpreter/interpreter.js';
337
337
  export { Environment } from './runtime/environment.js';
338
+ export { WangInterpreter, InMemoryModuleResolver } from './interpreter/index.js';
339
+
340
+ /**
341
+ * Abstract base class for module resolution
342
+ * Extend this class to implement custom module loading strategies
343
+ */
344
+ export class ModuleResolver {
345
+ /**
346
+ * Resolve a module and return its code
347
+ * @param {string} modulePath - The module path to resolve
348
+ * @param {string} [fromPath] - The path of the importing module
349
+ * @returns {Promise<ModuleResolution>} The resolved module
350
+ */
351
+ async resolve(modulePath, fromPath) {
352
+ throw new Error('ModuleResolver.resolve() must be implemented by subclass');
353
+ }
354
+
355
+ /**
356
+ * Check if a module exists
357
+ * @param {string} modulePath - The module path to check
358
+ * @param {string} [fromPath] - The path of the importing module
359
+ * @returns {Promise<boolean>} Whether the module exists
360
+ */
361
+ async exists(modulePath, fromPath) {
362
+ throw new Error('ModuleResolver.exists() must be implemented by subclass');
363
+ }
364
+
365
+ /**
366
+ * List available modules
367
+ * @param {string} [prefix] - Optional prefix to filter modules
368
+ * @returns {Promise<string[]>} List of module paths
369
+ */
370
+ async list(prefix) {
371
+ throw new Error('ModuleResolver.list() must be implemented by subclass');
372
+ }
373
+ }
374
+
375
+ /**
376
+ * @typedef {Object} ModuleResolution
377
+ * @property {string} code - The module source code
378
+ * @property {string} path - The resolved module path
379
+ * @property {any} [metadata] - Optional metadata about the module
380
+ */
@@ -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 resolve(name) {
172
- return this.modules.get(name);
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
  }
@@ -1199,11 +1199,14 @@ export class Interpreter {
1199
1199
  moduleExports = this.moduleCache.get(modulePath);
1200
1200
  } else {
1201
1201
  // Resolve and load module code
1202
- const moduleCode = await this.moduleResolver.resolve(modulePath);
1203
- if (!moduleCode) {
1202
+ const resolution = await this.moduleResolver.resolve(modulePath);
1203
+ if (!resolution) {
1204
1204
  throw new Error(`Cannot find module '${modulePath}'`);
1205
1205
  }
1206
1206
 
1207
+ // Handle both old (string) and new (ModuleResolution) formats
1208
+ const moduleCode = typeof resolution === 'string' ? resolution : resolution.code;
1209
+
1207
1210
  // Parse and execute module in its own environment
1208
1211
  const moduleAst = acornParse(moduleCode, {
1209
1212
  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
+ }