thyscript 1.0.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/bin/thy-node.js +332 -0
- package/bin/thy.js +828 -0
- package/bin/thys.js +25 -0
- package/examples/calculator.thy +274 -0
- package/examples/calculator.thy.js +628 -0
- package/examples/hello.thy +34 -0
- package/examples/login.thy +382 -0
- package/examples/login.thy.js +710 -0
- package/package.json +54 -0
- package/scripts/_class.js +18 -0
- package/scripts/_class2.js +11 -0
- package/scripts/_class3.js +21 -0
- package/scripts/_dstruct.js +20 -0
- package/scripts/_dstruct2.js +24 -0
- package/scripts/_scaffold.js +16 -0
- package/scripts/_scaffold2.js +16 -0
- package/scripts/_scaffold3.js +26 -0
- package/scripts/_tpl_compile.js +13 -0
- package/scripts/_tpl_test.js +9 -0
- package/scripts/_typeof.js +26 -0
- package/scripts/_typeof2.js +25 -0
- package/scripts/patch_genmethod.js +44 -0
- package/scripts/smoke_test.js +167 -0
- package/scripts/smoke_test2.js +151 -0
- package/scripts/test_control_panel.js +58 -0
- package/src/compiler/CodeGenerator.js +1449 -0
- package/src/compiler/JITCompiler.js +325 -0
- package/src/database/DatabaseEngine.js +931 -0
- package/src/errors/ErrorDetector.js +305 -0
- package/src/errors/ErrorMessages.js +497 -0
- package/src/index.js +169 -0
- package/src/lexer/Lexer.js +342 -0
- package/src/lexer/TokenTypes.js +1234 -0
- package/src/memory/MemoryManager.js +283 -0
- package/src/parser/AST.js +606 -0
- package/src/parser/Parser.js +1942 -0
- package/src/runtime/ThyRuntime.js +265 -0
- package/src/runtime/stdlib/ArrayLib.js +207 -0
- package/src/runtime/stdlib/FileLib.js +168 -0
- package/src/runtime/stdlib/MathLib.js +411 -0
- package/src/runtime/stdlib/StringLib.js +387 -0
- package/src/server/ControlPanel.js +1188 -0
- package/src/server/HTMLCSSEngine.js +866 -0
- package/src/server/SecurityEngine.js +714 -0
- package/src/server/ServerEngine.js +738 -0
- package/tests/test1_math.thy +82 -0
- package/tests/test2_oop.thy +133 -0
- package/tests/test3_server.thy +143 -0
- package/tests/test4_functions.thy +159 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* ThyScript — Programming Language
|
|
4
|
+
* A blazing-fast programming language built on Node.js
|
|
5
|
+
* for machine learning, physics simulation, web, game engines,
|
|
6
|
+
* full-stack applications, and complex algorithms.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { performance } = require('perf_hooks');
|
|
12
|
+
|
|
13
|
+
const Lexer = require('./lexer/Lexer');
|
|
14
|
+
const TokenType = require('./lexer/TokenTypes');
|
|
15
|
+
const Parser = require('./parser/Parser');
|
|
16
|
+
const AST = require('./parser/AST');
|
|
17
|
+
const JITCompiler = require('./compiler/JITCompiler');
|
|
18
|
+
const CodeGenerator = require('./compiler/CodeGenerator');
|
|
19
|
+
const ThyRuntime = require('./runtime/ThyRuntime');
|
|
20
|
+
const ErrorDetector = require('./errors/ErrorDetector');
|
|
21
|
+
const ErrorMessages = require('./errors/ErrorMessages');
|
|
22
|
+
const MemoryManager = require('./memory/MemoryManager');
|
|
23
|
+
const ServerEngine = require('./server/ServerEngine');
|
|
24
|
+
const SecurityEngine = require('./server/SecurityEngine');
|
|
25
|
+
const HTMLCSSEngine = require('./server/HTMLCSSEngine');
|
|
26
|
+
const DatabaseEngine = require('./database/DatabaseEngine');
|
|
27
|
+
|
|
28
|
+
// Standard library modules
|
|
29
|
+
const MathLib = require('./runtime/stdlib/MathLib');
|
|
30
|
+
const StringLib = require('./runtime/stdlib/StringLib');
|
|
31
|
+
const ArrayLib = require('./runtime/stdlib/ArrayLib');
|
|
32
|
+
const FileLib = require('./runtime/stdlib/FileLib');
|
|
33
|
+
|
|
34
|
+
const VERSION = '1.0.0';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compile ThyScript source code to optimized Node.js JavaScript
|
|
38
|
+
* @param {string} source - ThyScript source code
|
|
39
|
+
* @param {string} [filename='<anonymous>'] - Source file name
|
|
40
|
+
* @param {Object} [options={}] - Compiler options
|
|
41
|
+
* @returns {string} Compiled JavaScript code
|
|
42
|
+
*/
|
|
43
|
+
function compile(source, filename = '<anonymous>', options = {}) {
|
|
44
|
+
// 1. Lexical analysis
|
|
45
|
+
const lexer = new Lexer(source, filename);
|
|
46
|
+
const tokens = lexer.tokenize();
|
|
47
|
+
|
|
48
|
+
// 2. Syntax analysis (Parsing)
|
|
49
|
+
const parser = new Parser(tokens, source, filename);
|
|
50
|
+
const parseResult = parser.parse();
|
|
51
|
+
const ast = parseResult.ast || parseResult;
|
|
52
|
+
|
|
53
|
+
// 3. Optional Error & Bug Detection
|
|
54
|
+
if (options.checkErrors !== false) {
|
|
55
|
+
const detector = new ErrorDetector(options);
|
|
56
|
+
const scanResult = detector.scan(tokens, ast, source, filename);
|
|
57
|
+
if (!scanResult.isValid && options.strict) {
|
|
58
|
+
const firstErr = scanResult.errors[0];
|
|
59
|
+
throw new Error(`[ThyScript Compiler Error] ${detector.formatError(firstErr, source)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 4. JIT Code Generation
|
|
64
|
+
const compiler = new JITCompiler(ast, source, filename, options);
|
|
65
|
+
return compiler.compile();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Run a ThyScript file directly (.thy)
|
|
70
|
+
* @param {string} filePath - Path to .thy file
|
|
71
|
+
* @param {Object} [options={}] - Execution options
|
|
72
|
+
* @returns {Promise<any>} Execution result
|
|
73
|
+
*/
|
|
74
|
+
async function run(filePath, options = {}) {
|
|
75
|
+
const resolvedPath = path.resolve(filePath);
|
|
76
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
77
|
+
throw new Error(`[ThyScript] File not found: ${resolvedPath}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const source = fs.readFileSync(resolvedPath, 'utf8');
|
|
81
|
+
|
|
82
|
+
// Initialize runtime
|
|
83
|
+
const runtime = new ThyRuntime(options);
|
|
84
|
+
await runtime.init();
|
|
85
|
+
|
|
86
|
+
// Lex & Parse
|
|
87
|
+
const t0 = performance.now();
|
|
88
|
+
const lexer = new Lexer(source, resolvedPath);
|
|
89
|
+
const tokens = lexer.tokenize();
|
|
90
|
+
|
|
91
|
+
const parser = new Parser(tokens, source, resolvedPath);
|
|
92
|
+
const parseResult = parser.parse();
|
|
93
|
+
const ast = parseResult.ast || parseResult;
|
|
94
|
+
|
|
95
|
+
// Compile
|
|
96
|
+
const compiler = new JITCompiler(ast, source, resolvedPath, options);
|
|
97
|
+
const compiledJs = compiler.compile();
|
|
98
|
+
|
|
99
|
+
// Execute in sandbox with runtime context
|
|
100
|
+
const context = runtime.buildContext(options.context || {});
|
|
101
|
+
const result = await compiler.execute(compiledJs, context);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
result,
|
|
105
|
+
stats: compiler.getStats(),
|
|
106
|
+
compiledCode: compiledJs,
|
|
107
|
+
runtime,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Execute ThyScript source string directly
|
|
113
|
+
* @param {string} source - ThyScript code
|
|
114
|
+
* @param {Object} [options={}] - Execution options
|
|
115
|
+
* @returns {Promise<any>} Execution result
|
|
116
|
+
*/
|
|
117
|
+
async function runSource(source, options = {}) {
|
|
118
|
+
const runtime = new ThyRuntime(options);
|
|
119
|
+
await runtime.init();
|
|
120
|
+
|
|
121
|
+
const lexer = new Lexer(source, options.filename || '<anonymous>');
|
|
122
|
+
const tokens = lexer.tokenize();
|
|
123
|
+
|
|
124
|
+
const parser = new Parser(tokens, source, options.filename || '<anonymous>');
|
|
125
|
+
const parseResult = parser.parse();
|
|
126
|
+
const ast = parseResult.ast || parseResult;
|
|
127
|
+
|
|
128
|
+
const compiler = new JITCompiler(ast, source, options.filename || '<anonymous>', options);
|
|
129
|
+
const compiledJs = compiler.compile();
|
|
130
|
+
|
|
131
|
+
const context = runtime.buildContext(options.context || {});
|
|
132
|
+
const result = await compiler.execute(compiledJs, context);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
result,
|
|
136
|
+
stats: compiler.getStats(),
|
|
137
|
+
compiledCode: compiledJs,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
VERSION,
|
|
143
|
+
// Core Compiler Pipeline
|
|
144
|
+
Lexer,
|
|
145
|
+
TokenType,
|
|
146
|
+
Parser,
|
|
147
|
+
AST,
|
|
148
|
+
JITCompiler,
|
|
149
|
+
CodeGenerator,
|
|
150
|
+
// Runtime & Engines
|
|
151
|
+
ThyRuntime,
|
|
152
|
+
MemoryManager,
|
|
153
|
+
ServerEngine,
|
|
154
|
+
SecurityEngine,
|
|
155
|
+
HTMLCSSEngine,
|
|
156
|
+
DatabaseEngine,
|
|
157
|
+
// Error System
|
|
158
|
+
ErrorDetector,
|
|
159
|
+
ErrorMessages,
|
|
160
|
+
// Standard Libraries
|
|
161
|
+
MathLib,
|
|
162
|
+
StringLib,
|
|
163
|
+
ArrayLib,
|
|
164
|
+
FileLib,
|
|
165
|
+
// High-level API
|
|
166
|
+
compile,
|
|
167
|
+
run,
|
|
168
|
+
runSource,
|
|
169
|
+
};
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* ThyScript Lexer
|
|
4
|
+
* Tokenizes ThyScript source code into a stream of tokens
|
|
5
|
+
* Supports all ThyScript syntax including HTML blocks, math ops, and keywords
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TokenType = require('./TokenTypes');
|
|
9
|
+
|
|
10
|
+
class Lexer {
|
|
11
|
+
constructor(source, filename = '<anonymous>') {
|
|
12
|
+
this.source = source;
|
|
13
|
+
this.filename = filename;
|
|
14
|
+
this.pos = 0;
|
|
15
|
+
this.line = 1;
|
|
16
|
+
this.col = 1;
|
|
17
|
+
this.tokens = [];
|
|
18
|
+
this.errors = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
error(msg) {
|
|
22
|
+
const err = { message: msg, line: this.line, col: this.col, filename: this.filename };
|
|
23
|
+
this.errors.push(err);
|
|
24
|
+
throw new Error(`[ThyScript Lexer] ${this.filename}:${this.line}:${this.col} - ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
peek(offset = 0) { return this.source[this.pos + offset]; }
|
|
28
|
+
advance() {
|
|
29
|
+
const ch = this.source[this.pos++];
|
|
30
|
+
if (ch === '\n') { this.line++; this.col = 1; } else { this.col++; }
|
|
31
|
+
return ch;
|
|
32
|
+
}
|
|
33
|
+
isEOF() { return this.pos >= this.source.length; }
|
|
34
|
+
|
|
35
|
+
skipWhitespaceAndComments() {
|
|
36
|
+
while (!this.isEOF()) {
|
|
37
|
+
const ch = this.peek();
|
|
38
|
+
// Skip whitespace (not newlines - they may be significant)
|
|
39
|
+
if (ch === ' ' || ch === '\r' || ch === '\t') { this.advance(); continue; }
|
|
40
|
+
// Single line comment //
|
|
41
|
+
if (ch === '/' && this.peek(1) === '/') {
|
|
42
|
+
while (!this.isEOF() && this.peek() !== '\n') this.advance();
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Block comment /* */
|
|
46
|
+
if (ch === '/' && this.peek(1) === '*') {
|
|
47
|
+
this.advance(); this.advance();
|
|
48
|
+
while (!this.isEOF()) {
|
|
49
|
+
if (this.peek() === '*' && this.peek(1) === '/') { this.advance(); this.advance(); break; }
|
|
50
|
+
this.advance();
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
readString(quote) {
|
|
59
|
+
this.advance(); // consume opening quote
|
|
60
|
+
let str = '';
|
|
61
|
+
while (!this.isEOF() && this.peek() !== quote) {
|
|
62
|
+
if (this.peek() === '\\') {
|
|
63
|
+
this.advance();
|
|
64
|
+
const esc = this.advance();
|
|
65
|
+
switch (esc) {
|
|
66
|
+
case 'n': str += '\n'; break;
|
|
67
|
+
case 't': str += '\t'; break;
|
|
68
|
+
case 'r': str += '\r'; break;
|
|
69
|
+
case '\\': str += '\\'; break;
|
|
70
|
+
case '"': str += '"'; break;
|
|
71
|
+
case "'": str += "'"; break;
|
|
72
|
+
case '`': str += '`'; break;
|
|
73
|
+
default: str += '\\' + esc;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
str += this.advance();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (this.isEOF()) this.error('Unterminated string literal');
|
|
80
|
+
this.advance(); // consume closing quote
|
|
81
|
+
return str;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
readTemplateLiteral() {
|
|
85
|
+
this.advance(); // consume `
|
|
86
|
+
let parts = [];
|
|
87
|
+
let current = '';
|
|
88
|
+
while (!this.isEOF() && this.peek() !== '`') {
|
|
89
|
+
if (this.peek() === '$' && this.peek(1) === '{') {
|
|
90
|
+
if (current) parts.push({ type: 'str', value: current });
|
|
91
|
+
current = '';
|
|
92
|
+
this.advance(); this.advance(); // consume ${
|
|
93
|
+
let depth = 1;
|
|
94
|
+
let expr = '';
|
|
95
|
+
while (!this.isEOF() && depth > 0) {
|
|
96
|
+
if (this.peek() === '{') depth++;
|
|
97
|
+
if (this.peek() === '}') { depth--; if (depth === 0) break; }
|
|
98
|
+
expr += this.advance();
|
|
99
|
+
}
|
|
100
|
+
this.advance(); // consume }
|
|
101
|
+
parts.push({ type: 'expr', value: expr });
|
|
102
|
+
} else {
|
|
103
|
+
current += this.advance();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (this.isEOF()) this.error('Unterminated template literal');
|
|
107
|
+
this.advance(); // consume `
|
|
108
|
+
if (current) parts.push({ type: 'str', value: current });
|
|
109
|
+
return parts;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
readNumber() {
|
|
113
|
+
let num = '';
|
|
114
|
+
let isFloat = false;
|
|
115
|
+
let isBinary = false;
|
|
116
|
+
let isHex = false;
|
|
117
|
+
let isOctal = false;
|
|
118
|
+
|
|
119
|
+
if (this.peek() === '0') {
|
|
120
|
+
if (this.peek(1) === 'x' || this.peek(1) === 'X') {
|
|
121
|
+
num += this.advance() + this.advance();
|
|
122
|
+
isHex = true;
|
|
123
|
+
while (!this.isEOF() && /[0-9a-fA-F_]/.test(this.peek())) num += this.advance();
|
|
124
|
+
} else if (this.peek(1) === 'b' || this.peek(1) === 'B') {
|
|
125
|
+
num += this.advance() + this.advance();
|
|
126
|
+
isBinary = true;
|
|
127
|
+
while (!this.isEOF() && /[01_]/.test(this.peek())) num += this.advance();
|
|
128
|
+
} else if (this.peek(1) === 'o' || this.peek(1) === 'O') {
|
|
129
|
+
num += this.advance() + this.advance();
|
|
130
|
+
isOctal = true;
|
|
131
|
+
while (!this.isEOF() && /[0-7_]/.test(this.peek())) num += this.advance();
|
|
132
|
+
} else {
|
|
133
|
+
num += this.advance();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!isHex && !isBinary && !isOctal) {
|
|
138
|
+
while (!this.isEOF() && /[0-9_]/.test(this.peek())) num += this.advance();
|
|
139
|
+
if (!this.isEOF() && this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
|
|
140
|
+
isFloat = true;
|
|
141
|
+
num += this.advance();
|
|
142
|
+
while (!this.isEOF() && /[0-9_]/.test(this.peek())) num += this.advance();
|
|
143
|
+
}
|
|
144
|
+
if (!this.isEOF() && (this.peek() === 'e' || this.peek() === 'E')) {
|
|
145
|
+
isFloat = true;
|
|
146
|
+
num += this.advance();
|
|
147
|
+
if (!this.isEOF() && (this.peek() === '+' || this.peek() === '-')) num += this.advance();
|
|
148
|
+
while (!this.isEOF() && /[0-9]/.test(this.peek())) num += this.advance();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const cleanNum = num.replace(/_/g, '');
|
|
153
|
+
return { raw: num, value: isFloat ? parseFloat(cleanNum) : parseInt(cleanNum), isFloat };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
readIdentifierOrKeyword() {
|
|
157
|
+
let id = '';
|
|
158
|
+
while (!this.isEOF() && /[a-zA-Z0-9_$]/.test(this.peek())) id += this.advance();
|
|
159
|
+
const kw = TokenType.KEYWORDS[id];
|
|
160
|
+
return kw ? { type: kw, value: id } : { type: TokenType.IDENTIFIER, value: id };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
readHtmlBlock() {
|
|
164
|
+
// Reads everything after classHtml(name): until end()
|
|
165
|
+
let html = '';
|
|
166
|
+
// Skip until next line
|
|
167
|
+
while (!this.isEOF() && this.peek() !== '\n') this.advance();
|
|
168
|
+
if (!this.isEOF() && this.peek() === '\n') {
|
|
169
|
+
this.advance(); // consume newline
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
while (!this.isEOF()) {
|
|
173
|
+
// Check if current position/line starts with end() (ignoring indentation)
|
|
174
|
+
let p = this.pos;
|
|
175
|
+
while (p < this.source.length && (this.source[p] === ' ' || this.source[p] === '\t')) {
|
|
176
|
+
p++;
|
|
177
|
+
}
|
|
178
|
+
if (this.source.slice(p, p + 5) === 'end()') {
|
|
179
|
+
// Stop before indentation so end() is tokenized naturally
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
html += this.advance();
|
|
183
|
+
}
|
|
184
|
+
return html.trim();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
tokenize() {
|
|
188
|
+
while (!this.isEOF()) {
|
|
189
|
+
this.skipWhitespaceAndComments();
|
|
190
|
+
if (this.isEOF()) break;
|
|
191
|
+
|
|
192
|
+
const startLine = this.line;
|
|
193
|
+
const startCol = this.col;
|
|
194
|
+
const ch = this.peek();
|
|
195
|
+
|
|
196
|
+
// Newlines
|
|
197
|
+
if (ch === '\n') {
|
|
198
|
+
this.advance();
|
|
199
|
+
this.tokens.push({ type: TokenType.NEWLINE, value: '\n', line: startLine, col: startCol });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Numbers
|
|
204
|
+
if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(this.peek(1)))) {
|
|
205
|
+
const num = this.readNumber();
|
|
206
|
+
this.tokens.push({ type: num.isFloat ? TokenType.FLOAT : TokenType.NUMBER, value: num.value, raw: num.raw, line: startLine, col: startCol });
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Strings
|
|
211
|
+
if (ch === '"' || ch === "'") {
|
|
212
|
+
const str = this.readString(ch);
|
|
213
|
+
this.tokens.push({ type: TokenType.STRING, value: str, line: startLine, col: startCol });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Template literals
|
|
218
|
+
if (ch === '`') {
|
|
219
|
+
const parts = this.readTemplateLiteral();
|
|
220
|
+
this.tokens.push({ type: TokenType.TEMPLATE_LITERAL, value: parts, line: startLine, col: startCol });
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Identifiers and keywords
|
|
225
|
+
if (/[a-zA-Z_$]/.test(ch)) {
|
|
226
|
+
const tok = this.readIdentifierOrKeyword();
|
|
227
|
+
this.tokens.push({ ...tok, line: startLine, col: startCol });
|
|
228
|
+
|
|
229
|
+
if (tok.type === TokenType.KW_CLASS_HTML) {
|
|
230
|
+
this.skipWhitespaceAndComments();
|
|
231
|
+
if (this.peek() === '(') {
|
|
232
|
+
this.advance(); // consume (
|
|
233
|
+
this.tokens.push({ type: TokenType.LPAREN, value: '(', line: this.line, col: this.col });
|
|
234
|
+
this.skipWhitespaceAndComments();
|
|
235
|
+
let name = '';
|
|
236
|
+
while (!this.isEOF() && this.peek() !== ')') {
|
|
237
|
+
name += this.advance();
|
|
238
|
+
}
|
|
239
|
+
name = name.trim();
|
|
240
|
+
this.tokens.push({ type: TokenType.IDENTIFIER, value: name, line: this.line, col: this.col });
|
|
241
|
+
if (this.peek() === ')') {
|
|
242
|
+
this.advance(); // consume )
|
|
243
|
+
this.tokens.push({ type: TokenType.RPAREN, value: ')', line: this.line, col: this.col });
|
|
244
|
+
}
|
|
245
|
+
this.skipWhitespaceAndComments();
|
|
246
|
+
if (this.peek() === ':') {
|
|
247
|
+
this.advance(); // consume :
|
|
248
|
+
this.tokens.push({ type: TokenType.COLON, value: ':', line: this.line, col: this.col });
|
|
249
|
+
const html = this.readHtmlBlock();
|
|
250
|
+
this.tokens.push({ type: TokenType.STRING, value: html, line: this.line, col: this.col });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Operators and punctuation
|
|
258
|
+
const op = this.readOperator();
|
|
259
|
+
if (op) {
|
|
260
|
+
this.tokens.push({ ...op, line: startLine, col: startCol });
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.error(`Unknown character '${ch}' (code ${ch.charCodeAt(0)})`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this.tokens.push({ type: TokenType.EOF, value: null, line: this.line, col: this.col });
|
|
268
|
+
return this.tokens;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
readOperator() {
|
|
272
|
+
const ch = this.advance();
|
|
273
|
+
const next = this.peek();
|
|
274
|
+
|
|
275
|
+
switch (ch) {
|
|
276
|
+
case '+': if (next === '+') { this.advance(); return { type: TokenType.PLUS_PLUS, value: '++' }; }
|
|
277
|
+
if (next === '=') { this.advance(); return { type: TokenType.PLUS_ASSIGN, value: '+=' }; }
|
|
278
|
+
return { type: TokenType.PLUS, value: '+' };
|
|
279
|
+
case '-': if (next === '-') { this.advance(); return { type: TokenType.MINUS_MINUS, value: '--' }; }
|
|
280
|
+
if (next === '=') { this.advance(); return { type: TokenType.MINUS_ASSIGN, value: '-=' }; }
|
|
281
|
+
if (next === '>') { this.advance(); return { type: TokenType.ARROW, value: '->' }; }
|
|
282
|
+
return { type: TokenType.MINUS, value: '-' };
|
|
283
|
+
case '*': if (next === '*') { this.advance(); return { type: TokenType.POWER, value: '**' }; }
|
|
284
|
+
if (next === '=') { this.advance(); return { type: TokenType.MUL_ASSIGN, value: '*=' }; }
|
|
285
|
+
return { type: TokenType.STAR, value: '*' };
|
|
286
|
+
case '/': if (next === '=') { this.advance(); return { type: TokenType.DIV_ASSIGN, value: '/=' }; }
|
|
287
|
+
return { type: TokenType.SLASH, value: '/' };
|
|
288
|
+
case '%': if (next === '=') { this.advance(); return { type: TokenType.MOD_ASSIGN, value: '%=' }; }
|
|
289
|
+
return { type: TokenType.PERCENT, value: '%' };
|
|
290
|
+
case '^': return { type: TokenType.CARET, value: '^' };
|
|
291
|
+
case '&': if (next === '&') { this.advance(); return { type: TokenType.AND_AND, value: '&&' }; }
|
|
292
|
+
return { type: TokenType.AMPERSAND, value: '&' };
|
|
293
|
+
case '|': if (next === '|') { this.advance(); return { type: TokenType.OR_OR, value: '||' }; }
|
|
294
|
+
return { type: TokenType.PIPE, value: '|' };
|
|
295
|
+
case '!': if (next === '=') {
|
|
296
|
+
this.advance();
|
|
297
|
+
if (this.peek() === '=') { this.advance(); return { type: TokenType.NOT_EQ_STRICT, value: '!==' }; }
|
|
298
|
+
return { type: TokenType.NOT_EQ, value: '!=' };
|
|
299
|
+
}
|
|
300
|
+
return { type: TokenType.NOT, value: '!' };
|
|
301
|
+
case '=': if (next === '=') {
|
|
302
|
+
this.advance();
|
|
303
|
+
if (this.peek() === '=') { this.advance(); return { type: TokenType.EQ_STRICT, value: '===' }; }
|
|
304
|
+
return { type: TokenType.EQ_EQ, value: '==' };
|
|
305
|
+
}
|
|
306
|
+
if (next === '>') { this.advance(); return { type: TokenType.FAT_ARROW, value: '=>' }; }
|
|
307
|
+
return { type: TokenType.ASSIGN, value: '=' };
|
|
308
|
+
case '<': if (next === '=') { this.advance(); return { type: TokenType.LT_EQ, value: '<=' }; }
|
|
309
|
+
if (next === '<') { this.advance(); return { type: TokenType.SHIFT_LEFT, value: '<<' }; }
|
|
310
|
+
return { type: TokenType.LT, value: '<' };
|
|
311
|
+
case '>': if (next === '=') { this.advance(); return { type: TokenType.GT_EQ, value: '>=' }; }
|
|
312
|
+
if (next === '>') { this.advance(); return { type: TokenType.SHIFT_RIGHT, value: '>>' }; }
|
|
313
|
+
return { type: TokenType.GT, value: '>' };
|
|
314
|
+
case '(': return { type: TokenType.LPAREN, value: '(' };
|
|
315
|
+
case ')': return { type: TokenType.RPAREN, value: ')' };
|
|
316
|
+
case '{': return { type: TokenType.LBRACE, value: '{' };
|
|
317
|
+
case '}': return { type: TokenType.RBRACE, value: '}' };
|
|
318
|
+
case '[': return { type: TokenType.LBRACKET, value: '[' };
|
|
319
|
+
case ']': return { type: TokenType.RBRACKET, value: ']' };
|
|
320
|
+
case '.': if (next === '.' && this.peek(1) === '.') { this.advance(); this.advance(); return { type: TokenType.SPREAD, value: '...' }; }
|
|
321
|
+
return { type: TokenType.DOT, value: '.' };
|
|
322
|
+
case ',': return { type: TokenType.COMMA, value: ',' };
|
|
323
|
+
case ':': return { type: TokenType.COLON, value: ':' };
|
|
324
|
+
case ';': return { type: TokenType.SEMICOLON, value: ';' };
|
|
325
|
+
case '#': {
|
|
326
|
+
let comment = '';
|
|
327
|
+
while (!this.isEOF() && this.peek() !== '\n') comment += this.advance();
|
|
328
|
+
return { type: TokenType.COMMENT, value: comment };
|
|
329
|
+
}
|
|
330
|
+
case '@': return { type: TokenType.AT, value: '@' };
|
|
331
|
+
case '$': return { type: TokenType.DOLLAR, value: '$' };
|
|
332
|
+
case '?': if (next === '?') { this.advance(); return { type: TokenType.NULL_COALESCE, value: '??' }; }
|
|
333
|
+
if (next === '.') { this.advance(); return { type: TokenType.OPTIONAL_CHAIN, value: '?.' }; }
|
|
334
|
+
return { type: TokenType.QUESTION, value: '?' };
|
|
335
|
+
case '\\': return { type: TokenType.BACKSLASH, value: '\\' };
|
|
336
|
+
case '~': return { type: TokenType.TILDE, value: '~' };
|
|
337
|
+
default: return null;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
module.exports = Lexer;
|