sommark 4.0.3 → 4.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/README.md +304 -73
- package/cli/cli.mjs +1 -1
- package/cli/commands/build.js +3 -1
- package/cli/commands/help.js +2 -0
- package/cli/commands/init.js +25 -6
- package/cli/constants.js +2 -1
- package/cli/helpers/transpile.js +5 -2
- package/constants/html_props.js +1 -0
- package/core/evaluator.js +1061 -0
- package/core/formats.js +15 -7
- package/core/helpers/config-loader.js +16 -8
- package/core/helpers/lib.js +72 -0
- package/core/helpers/preprocessor.js +202 -0
- package/core/helpers/runtimeOutput.js +28 -0
- package/core/helpers/url.js +12 -0
- package/core/labels.js +9 -2
- package/core/lexer.js +228 -61
- package/core/modules.js +338 -60
- package/core/parser.js +275 -55
- package/core/tokenTypes.js +11 -0
- package/core/transpiler.js +352 -66
- package/core/validator.js +70 -7
- package/formatter/tag.js +31 -7
- package/grammar.ebnf +21 -10
- package/helpers/fetch-fs.js +37 -0
- package/helpers/safeDataParser.js +3 -3
- package/helpers/spinner.js +97 -0
- package/helpers/utils.js +46 -0
- package/helpers/virtual-fs.js +29 -0
- package/index.browser.js +87 -0
- package/index.js +23 -332
- package/index.shared.js +443 -0
- package/mappers/languages/html.js +50 -9
- package/mappers/languages/json.js +81 -38
- package/mappers/languages/jsonc.js +82 -0
- package/mappers/languages/markdown.js +88 -48
- package/mappers/languages/mdx.js +50 -15
- package/mappers/languages/text.js +67 -0
- package/mappers/languages/xml.js +6 -6
- package/mappers/mapper.js +36 -4
- package/mappers/shared/index.js +12 -13
- package/package.json +11 -2
- package/core/formatter.js +0 -215
package/core/lexer.js
CHANGED
|
@@ -27,9 +27,9 @@ function lexer(src, filename = "anonymous") {
|
|
|
27
27
|
let isInAtBlockBody = false;
|
|
28
28
|
let isInQuote = false;
|
|
29
29
|
let isInHeader = false; // Tracks if we are in a structural header context
|
|
30
|
+
let isInAtBlockHeader = false; // Specific for At-Block headers (@_ ... _@)
|
|
30
31
|
let isInInlineHead = false; // Specific for (key:val) after ->
|
|
31
32
|
let parenDepth = 0; // To track balanced parentheses in inlines
|
|
32
|
-
let delimiterStack = []; // To track block nesting for body mode
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Adds a token to the stream and updates the scanner's position tracking.
|
|
@@ -54,8 +54,7 @@ function lexer(src, filename = "anonymous") {
|
|
|
54
54
|
type,
|
|
55
55
|
value,
|
|
56
56
|
source: filename,
|
|
57
|
-
range: { start, end }
|
|
58
|
-
depth: delimiterStack.length
|
|
57
|
+
range: { start, end }
|
|
59
58
|
});
|
|
60
59
|
|
|
61
60
|
prev_type = type;
|
|
@@ -140,17 +139,18 @@ function lexer(src, filename = "anonymous") {
|
|
|
140
139
|
i += 2;
|
|
141
140
|
continue;
|
|
142
141
|
}
|
|
143
|
-
|
|
142
|
+
|
|
144
143
|
// Support Prefix Layers inside quotes!
|
|
145
|
-
if ((src[i] === "j" && src[i+1] === "s" && src[i+2] === "{") || (src[i] === "p" && src[i+1] === "{")) {
|
|
144
|
+
if ((src[i] === "j" && src[i + 1] === "s" && src[i + 2] === "{") || (src[i] === "p" && src[i + 1] === "{") || (src[i] === "v" && src[i + 1] === "{")) {
|
|
146
145
|
const isJS = (src[i] === "j");
|
|
146
|
+
const isV = (src[i] === "v");
|
|
147
147
|
if (quoteValue.length > 0) {
|
|
148
148
|
addToken(TOKEN_TYPES.VALUE, quoteValue);
|
|
149
149
|
quoteValue = "";
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
let braceDepth = 1;
|
|
153
|
-
let prefixValue = isJS ? "js{" : "p{";
|
|
153
|
+
let prefixValue = isJS ? "js{" : (isV ? "v{" : "p{");
|
|
154
154
|
i += isJS ? 3 : 2;
|
|
155
155
|
|
|
156
156
|
let internalString = null;
|
|
@@ -172,7 +172,8 @@ function lexer(src, filename = "anonymous") {
|
|
|
172
172
|
prefixValue += c;
|
|
173
173
|
i++;
|
|
174
174
|
}
|
|
175
|
-
|
|
175
|
+
let tokenType = isJS ? TOKEN_TYPES.PREFIX_JS : (isV ? TOKEN_TYPES.PREFIX_V : TOKEN_TYPES.PREFIX_P);
|
|
176
|
+
addToken(tokenType, prefixValue);
|
|
176
177
|
continue;
|
|
177
178
|
}
|
|
178
179
|
|
|
@@ -197,7 +198,7 @@ function lexer(src, filename = "anonymous") {
|
|
|
197
198
|
|
|
198
199
|
// --- PHASE 3: STRUCTURAL PARSING ---
|
|
199
200
|
// Handles markers, whitespace, and structural symbols.
|
|
200
|
-
|
|
201
|
+
|
|
201
202
|
// WHITESPACE
|
|
202
203
|
if (char === "\n") {
|
|
203
204
|
addToken(TOKEN_TYPES.WHITESPACE, char);
|
|
@@ -218,11 +219,31 @@ function lexer(src, filename = "anonymous") {
|
|
|
218
219
|
// COMMENTS
|
|
219
220
|
if (char === "#") {
|
|
220
221
|
let comm = "";
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
i
|
|
222
|
+
// Check for Multiline Comment ### (must have no spaces)
|
|
223
|
+
if (src[i + 1] === "#" && src[i + 2] === "#") {
|
|
224
|
+
const startPos = i;
|
|
225
|
+
comm = "###";
|
|
226
|
+
i += 3;
|
|
227
|
+
let closed = false;
|
|
228
|
+
while (i < src.length) {
|
|
229
|
+
if (src[i] === "#" && src[i + 1] === "#" && src[i + 2] === "#") {
|
|
230
|
+
comm += "###";
|
|
231
|
+
i += 3;
|
|
232
|
+
closed = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
comm += src[i];
|
|
236
|
+
i++;
|
|
237
|
+
}
|
|
238
|
+
addToken(TOKEN_TYPES.COMMENT_BLOCK, comm);
|
|
239
|
+
} else {
|
|
240
|
+
// Single line comment
|
|
241
|
+
while (i < src.length && src[i] !== "\n") {
|
|
242
|
+
comm += src[i];
|
|
243
|
+
i++;
|
|
244
|
+
}
|
|
245
|
+
addToken(TOKEN_TYPES.COMMENT, comm);
|
|
224
246
|
}
|
|
225
|
-
addToken(TOKEN_TYPES.COMMENT, comm);
|
|
226
247
|
continue;
|
|
227
248
|
}
|
|
228
249
|
|
|
@@ -234,23 +255,24 @@ function lexer(src, filename = "anonymous") {
|
|
|
234
255
|
continue;
|
|
235
256
|
}
|
|
236
257
|
|
|
237
|
-
// PREFIX LAYERS (js{...} or p{...})
|
|
238
|
-
if ((char === "j" && next === "s" && src[i+2] === "{") || (char === "p" && next === "{")) {
|
|
258
|
+
// PREFIX LAYERS (js{...} or p{...} or v{...})
|
|
259
|
+
if ((char === "j" && next === "s" && src[i + 2] === "{") || (char === "p" && next === "{") || (char === "v" && next === "{")) {
|
|
239
260
|
const isJS = (char === "j");
|
|
240
261
|
const isP = (char === "p");
|
|
241
|
-
|
|
262
|
+
const isV = (char === "v");
|
|
263
|
+
|
|
242
264
|
// Context Check
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
265
|
+
const isBlockHeader = isInHeader && !isInAtBlockHeader;
|
|
266
|
+
const isNormalText = !isInHeader && !isInInlineHead && !isInAtBlockBody && parenDepth === 0;
|
|
267
|
+
|
|
247
268
|
let allowed = false;
|
|
248
|
-
if (isJS &&
|
|
249
|
-
if (isP && (
|
|
269
|
+
if (isJS && isBlockHeader) allowed = true;
|
|
270
|
+
if (isP && (isBlockHeader || isNormalText)) allowed = true;
|
|
271
|
+
if (isV && (isBlockHeader || isNormalText)) allowed = true;
|
|
250
272
|
|
|
251
273
|
if (allowed) {
|
|
252
274
|
let braceDepth = 1;
|
|
253
|
-
let prefixValue = isJS ? "js{" : "p{";
|
|
275
|
+
let prefixValue = isJS ? "js{" : (isV ? "v{" : "p{");
|
|
254
276
|
i += isJS ? 3 : 2;
|
|
255
277
|
|
|
256
278
|
let inString = null; // Track if we are inside " " or ' '
|
|
@@ -273,7 +295,8 @@ function lexer(src, filename = "anonymous") {
|
|
|
273
295
|
prefixValue += c;
|
|
274
296
|
i++;
|
|
275
297
|
}
|
|
276
|
-
|
|
298
|
+
let tokenType = isJS ? TOKEN_TYPES.PREFIX_JS : (isV ? TOKEN_TYPES.PREFIX_V : TOKEN_TYPES.PREFIX_P);
|
|
299
|
+
addToken(tokenType, prefixValue);
|
|
277
300
|
continue;
|
|
278
301
|
}
|
|
279
302
|
// If not allowed, it will fall through to normal word scanning
|
|
@@ -283,8 +306,8 @@ function lexer(src, filename = "anonymous") {
|
|
|
283
306
|
if (char === "@" && next === "_") {
|
|
284
307
|
addToken(TOKEN_TYPES.OPEN_AT, "@_");
|
|
285
308
|
i += 2;
|
|
286
|
-
if (!isInAtBlockBody) delimiterStack.push("@");
|
|
287
309
|
isInHeader = true; // At-Blocks start with a header part
|
|
310
|
+
isInAtBlockHeader = true;
|
|
288
311
|
continue;
|
|
289
312
|
}
|
|
290
313
|
if (char === "-" && next === ">") {
|
|
@@ -299,13 +322,128 @@ function lexer(src, filename = "anonymous") {
|
|
|
299
322
|
continue;
|
|
300
323
|
}
|
|
301
324
|
|
|
325
|
+
// STATIC KEYWORD
|
|
326
|
+
if (char === "s" && src.slice(i, i + 6) === "static") {
|
|
327
|
+
const afterStatic = src.slice(i + 6);
|
|
328
|
+
const hasSpace = afterStatic.startsWith(" ");
|
|
329
|
+
const hasLogic = hasSpace ? afterStatic.slice(1).startsWith("${") : afterStatic.startsWith("${");
|
|
330
|
+
|
|
331
|
+
const isMainIdentifier = (
|
|
332
|
+
last_non_junk_type === TOKEN_TYPES.OPEN_BRACKET ||
|
|
333
|
+
last_non_junk_type === TOKEN_TYPES.OPEN_AT ||
|
|
334
|
+
(last_non_junk_type === TOKEN_TYPES.OPEN_PAREN && isInInlineHead)
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if ((hasLogic || isInHeader) && !isMainIdentifier) {
|
|
338
|
+
addToken(TOKEN_TYPES.STATIC_KEYWORD, hasSpace ? "static " : "static");
|
|
339
|
+
i += hasSpace ? 7 : 6;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// RUNTIME KEYWORD
|
|
345
|
+
if (char === "r" && src.slice(i, i + 7) === "runtime") {
|
|
346
|
+
const afterRuntime = src.slice(i + 7);
|
|
347
|
+
const hasSpace = afterRuntime.startsWith(" ");
|
|
348
|
+
const hasLogic = hasSpace ? afterRuntime.slice(1).startsWith("${") : afterRuntime.startsWith("${");
|
|
349
|
+
|
|
350
|
+
const isMainIdentifier = (
|
|
351
|
+
last_non_junk_type === TOKEN_TYPES.OPEN_BRACKET ||
|
|
352
|
+
last_non_junk_type === TOKEN_TYPES.OPEN_AT ||
|
|
353
|
+
(last_non_junk_type === TOKEN_TYPES.OPEN_PAREN && isInInlineHead)
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if ((hasLogic || isInHeader) && !isMainIdentifier) {
|
|
357
|
+
addToken(TOKEN_TYPES.RUNTIME_KEYWORD, hasSpace ? "runtime " : "runtime");
|
|
358
|
+
i += hasSpace ? 8 : 7;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// LOGIC BLOCKS (${ ... }$)
|
|
364
|
+
if (char === "$" && next === "{" && (last_non_junk_type === TOKEN_TYPES.STATIC_KEYWORD || last_non_junk_type === TOKEN_TYPES.RUNTIME_KEYWORD)) {
|
|
365
|
+
const startLine = line;
|
|
366
|
+
const startCharacter = character;
|
|
367
|
+
i += 2;
|
|
368
|
+
let logicCode = "";
|
|
369
|
+
let braceDepth = 1;
|
|
370
|
+
let internalString = null;
|
|
371
|
+
let foundClosing = false;
|
|
372
|
+
|
|
373
|
+
while (i < src.length) {
|
|
374
|
+
const c = src[i];
|
|
375
|
+
const n = src[i + 1];
|
|
376
|
+
|
|
377
|
+
// Stop condition: }$ (only if not inside a JS string and at top-level brace depth)
|
|
378
|
+
if (c === "}" && n === "$" && !internalString && braceDepth === 1) {
|
|
379
|
+
i += 2;
|
|
380
|
+
braceDepth = 0;
|
|
381
|
+
foundClosing = true;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (internalString) {
|
|
386
|
+
if (c === "\\" && (n === internalString || n === "\\")) {
|
|
387
|
+
logicCode += c + n;
|
|
388
|
+
i += 2;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (c === internalString) internalString = null;
|
|
392
|
+
} else {
|
|
393
|
+
if (c === "/" && n === "/") {
|
|
394
|
+
logicCode += c + n;
|
|
395
|
+
i += 2;
|
|
396
|
+
while (i < src.length && src[i] !== "\n" && src[i] !== "\r") {
|
|
397
|
+
logicCode += src[i];
|
|
398
|
+
i++;
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (c === "/" && n === "*") {
|
|
403
|
+
logicCode += c + n;
|
|
404
|
+
i += 2;
|
|
405
|
+
while (i < src.length) {
|
|
406
|
+
if (src[i] === "*" && src[i + 1] === "/") {
|
|
407
|
+
logicCode += "*/";
|
|
408
|
+
i += 2;
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
logicCode += src[i];
|
|
412
|
+
i++;
|
|
413
|
+
}
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (c === "\"" || c === "'" || c === "`") internalString = c;
|
|
418
|
+
else if (c === "{") braceDepth++;
|
|
419
|
+
else if (c === "}") braceDepth--;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
logicCode += c;
|
|
423
|
+
i++;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!foundClosing) {
|
|
427
|
+
lexerError("Unclosed logic block. Expected '}$' to close the block starting with '${'.", {
|
|
428
|
+
src,
|
|
429
|
+
filename,
|
|
430
|
+
range: {
|
|
431
|
+
start: { line: startLine, character: startCharacter },
|
|
432
|
+
end: { line: startLine, character: startCharacter + 2 }
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
addToken(TOKEN_TYPES.LOGIC, logicCode);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
302
441
|
// SINGLE-CHAR MARKERS
|
|
303
442
|
if (char === "[") {
|
|
304
443
|
if (isInAtBlockBody || (parenDepth > 0 && !isInInlineHead)) {
|
|
305
444
|
addToken(TOKEN_TYPES.TEXT, "[");
|
|
306
445
|
} else {
|
|
307
446
|
addToken(TOKEN_TYPES.OPEN_BRACKET, "[");
|
|
308
|
-
delimiterStack.push("[");
|
|
309
447
|
isInHeader = true;
|
|
310
448
|
}
|
|
311
449
|
i++;
|
|
@@ -317,13 +455,11 @@ function lexer(src, filename = "anonymous") {
|
|
|
317
455
|
} else {
|
|
318
456
|
const lastRealType = last_non_junk_type;
|
|
319
457
|
addToken(TOKEN_TYPES.CLOSE_AT, "_@");
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
isInHeader = false;
|
|
326
|
-
}
|
|
458
|
+
// Removed delimiter stack check
|
|
459
|
+
if (lastRealType === TOKEN_TYPES.END_KEYWORD) {
|
|
460
|
+
isInAtBlockBody = false;
|
|
461
|
+
isInHeader = false;
|
|
462
|
+
isInAtBlockHeader = false;
|
|
327
463
|
}
|
|
328
464
|
}
|
|
329
465
|
i += 2;
|
|
@@ -359,7 +495,10 @@ function lexer(src, filename = "anonymous") {
|
|
|
359
495
|
parenDepth--;
|
|
360
496
|
if (parenDepth === 0) {
|
|
361
497
|
addToken(TOKEN_TYPES.CLOSE_PAREN, ")");
|
|
362
|
-
if (isInInlineHead)
|
|
498
|
+
if (isInInlineHead) {
|
|
499
|
+
isInInlineHead = false;
|
|
500
|
+
isInHeader = false;
|
|
501
|
+
}
|
|
363
502
|
} else {
|
|
364
503
|
addToken(TOKEN_TYPES.TEXT, ")");
|
|
365
504
|
}
|
|
@@ -373,7 +512,7 @@ function lexer(src, filename = "anonymous") {
|
|
|
373
512
|
if (isInAtBlockBody || (parenDepth > 0 && !isInInlineHead)) {
|
|
374
513
|
addToken(TOKEN_TYPES.TEXT, ":");
|
|
375
514
|
} else {
|
|
376
|
-
const allowed = [TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.KEY, TOKEN_TYPES.CLOSE_AT, TOKEN_TYPES.VALUE, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.QUOTE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT];
|
|
515
|
+
const allowed = [TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.KEY, TOKEN_TYPES.CLOSE_AT, TOKEN_TYPES.VALUE, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.QUOTE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_V, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT, TOKEN_TYPES.LOGIC, TOKEN_TYPES.STATIC_KEYWORD, TOKEN_TYPES.RUNTIME_KEYWORD, TOKEN_TYPES.FOR_EACH];
|
|
377
516
|
if (allowed.includes(last_non_junk_type)) {
|
|
378
517
|
addToken(TOKEN_TYPES.COLON, ":");
|
|
379
518
|
isInHeader = true;
|
|
@@ -388,7 +527,7 @@ function lexer(src, filename = "anonymous") {
|
|
|
388
527
|
if (isInAtBlockBody || (parenDepth > 0 && !isInInlineHead)) {
|
|
389
528
|
addToken(TOKEN_TYPES.TEXT, "=");
|
|
390
529
|
} else {
|
|
391
|
-
const allowed = [TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.KEY, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.QUOTE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT];
|
|
530
|
+
const allowed = [TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.KEY, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.QUOTE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_V, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT, TOKEN_TYPES.LOGIC, TOKEN_TYPES.STATIC_KEYWORD, TOKEN_TYPES.RUNTIME_KEYWORD, TOKEN_TYPES.FOR_EACH];
|
|
392
531
|
if (allowed.includes(last_non_junk_type)) {
|
|
393
532
|
addToken(TOKEN_TYPES.EQUAL, "=");
|
|
394
533
|
} else {
|
|
@@ -402,7 +541,7 @@ function lexer(src, filename = "anonymous") {
|
|
|
402
541
|
if (isInAtBlockBody || (parenDepth > 0 && !isInInlineHead)) {
|
|
403
542
|
addToken(TOKEN_TYPES.TEXT, ",");
|
|
404
543
|
} else {
|
|
405
|
-
const allowed = [TOKEN_TYPES.VALUE, TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.QUOTE, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT];
|
|
544
|
+
const allowed = [TOKEN_TYPES.VALUE, TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.QUOTE, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_V, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT, TOKEN_TYPES.LOGIC, TOKEN_TYPES.STATIC_KEYWORD, TOKEN_TYPES.RUNTIME_KEYWORD, TOKEN_TYPES.FOR_EACH];
|
|
406
545
|
if (allowed.includes(last_non_junk_type)) {
|
|
407
546
|
addToken(TOKEN_TYPES.COMMA, ",");
|
|
408
547
|
} else {
|
|
@@ -416,16 +555,14 @@ function lexer(src, filename = "anonymous") {
|
|
|
416
555
|
if (isInAtBlockBody || (parenDepth > 0 && !isInInlineHead)) {
|
|
417
556
|
addToken(TOKEN_TYPES.TEXT, ";");
|
|
418
557
|
} else {
|
|
419
|
-
const allowed = [TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.VALUE, TOKEN_TYPES.CLOSE_AT, TOKEN_TYPES.CLOSE_PAREN, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.QUOTE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT];
|
|
558
|
+
const allowed = [TOKEN_TYPES.IDENTIFIER, TOKEN_TYPES.VALUE, TOKEN_TYPES.CLOSE_AT, TOKEN_TYPES.CLOSE_PAREN, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.QUOTE, TOKEN_TYPES.PREFIX_JS, TOKEN_TYPES.PREFIX_V, TOKEN_TYPES.PREFIX_P, TOKEN_TYPES.IMPORT, TOKEN_TYPES.USE_MODULE, TOKEN_TYPES.END_KEYWORD, TOKEN_TYPES.TEXT, TOKEN_TYPES.LOGIC, TOKEN_TYPES.STATIC_KEYWORD, TOKEN_TYPES.RUNTIME_KEYWORD, TOKEN_TYPES.FOR_EACH];
|
|
420
559
|
if (allowed.includes(last_non_junk_type)) {
|
|
421
560
|
addToken(TOKEN_TYPES.SEMICOLON, ";");
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
isInAtBlockBody = true;
|
|
428
|
-
}
|
|
561
|
+
// ONLY trigger body mode if we were actually in an At-Block header
|
|
562
|
+
if (isInAtBlockHeader) {
|
|
563
|
+
isInHeader = false;
|
|
564
|
+
isInAtBlockHeader = false;
|
|
565
|
+
isInAtBlockBody = true;
|
|
429
566
|
}
|
|
430
567
|
} else {
|
|
431
568
|
addToken(TOKEN_TYPES.TEXT, ";");
|
|
@@ -434,6 +571,13 @@ function lexer(src, filename = "anonymous") {
|
|
|
434
571
|
i++;
|
|
435
572
|
continue;
|
|
436
573
|
}
|
|
574
|
+
if (char === "!") {
|
|
575
|
+
if (isInHeader) {
|
|
576
|
+
addToken(TOKEN_TYPES.EXCLAMATION_MARK, "!");
|
|
577
|
+
i++;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
437
581
|
if (char === "\"" || char === "'") {
|
|
438
582
|
const valTriggers = [TOKEN_TYPES.COLON, TOKEN_TYPES.EQUAL, TOKEN_TYPES.COMMA, TOKEN_TYPES.ESCAPE, TOKEN_TYPES.OPEN_BRACKET, TOKEN_TYPES.OPEN_AT];
|
|
439
583
|
const wasValueTrigger = valTriggers.includes(last_non_junk_type);
|
|
@@ -455,28 +599,40 @@ function lexer(src, filename = "anonymous") {
|
|
|
455
599
|
// At-Blocks (@_) and Inlines (->( )) do NOT allow ':' in the ID.
|
|
456
600
|
const isStartOfBlockId = (last_non_junk_type === TOKEN_TYPES.OPEN_BRACKET);
|
|
457
601
|
|
|
458
|
-
let stopChars = "[](){}
|
|
602
|
+
let stopChars = "[](){}:=;,@>\"'#\\ \t\n\r!";
|
|
459
603
|
if (isStartOfBlockId || (parenDepth > 0 && !isInInlineHead)) {
|
|
460
604
|
stopChars = stopChars.replace(":", "");
|
|
461
605
|
}
|
|
462
|
-
|
|
463
|
-
|
|
606
|
+
const isInNormalText = !isInHeader && !isInInlineHead && !isInAtBlockBody;
|
|
607
|
+
if (isInNormalText) {
|
|
608
|
+
stopChars = "[]@()>_()\\#\n\r"; // In normal text, stop at markers, comments and newlines
|
|
464
609
|
}
|
|
465
610
|
|
|
466
|
-
while (i < src.length && !stopChars.includes(src[i])) {
|
|
611
|
+
while (i < src.length && !stopChars.includes(src[i])) {
|
|
612
|
+
// Stop ONLY if $ is followed by { (Logic block start)
|
|
613
|
+
if (src[i] === "$" && src[i + 1] === "{") break;
|
|
614
|
+
|
|
615
|
+
// Lookahead for At-Block markers (_@ or @_)
|
|
616
|
+
if (src[i] === "_" && src[i + 1] === "@") break;
|
|
617
|
+
if (src[i] === "@" && src[i + 1] === "_") break;
|
|
618
|
+
|
|
619
|
+
// Lookahead for 'static ${' or 'runtime ${' (only if we're not at the very start of the word scanning)
|
|
620
|
+
if (word.length > 0) {
|
|
621
|
+
if (src[i] === "s" && src.slice(i, i + 7) === "static " && src[i + 7] === "$" && src[i + 8] === "{") break;
|
|
622
|
+
if (src[i] === "s" && src.slice(i, i + 6) === "static" && src[i + 6] === "$" && src[i + 7] === "{") break;
|
|
623
|
+
if (src[i] === "r" && src.slice(i, i + 8) === "runtime " && src[i + 8] === "$" && src[i + 9] === "{") break;
|
|
624
|
+
if (src[i] === "r" && src.slice(i, i + 7) === "runtime" && src[i + 7] === "$" && src[i + 8] === "{") break;
|
|
625
|
+
}
|
|
626
|
+
|
|
467
627
|
// Lookahead for -> marker in normal text
|
|
468
|
-
if (!isInHeader && src[i] === "-" && src[i+1] === ">") break;
|
|
628
|
+
if (!isInHeader && src[i] === "-" && src[i + 1] === ">") break;
|
|
469
629
|
|
|
470
630
|
// Stop if we hit an ALLOWED prefix trigger
|
|
471
|
-
if ((src[i] === "p" && src[i+1] === "{")) {
|
|
472
|
-
|
|
473
|
-
const isInBlockHeader = isInHeader && top === "[";
|
|
474
|
-
const isInNormalText = !isInHeader && !isInInlineHead && !isInAtBlockBody && parenDepth === 0;
|
|
475
|
-
if (isInBlockHeader || isInNormalText) break;
|
|
631
|
+
if ((src[i] === "p" && src[i + 1] === "{") || (src[i] === "v" && src[i + 1] === "{")) {
|
|
632
|
+
if (isInHeader || isInNormalText) break;
|
|
476
633
|
}
|
|
477
|
-
if (src[i] === "j" && src[i+1] === "s" && src[i+2] === "{") {
|
|
478
|
-
|
|
479
|
-
if (isInHeader && top === "[") break;
|
|
634
|
+
if (src[i] === "j" && src[i + 1] === "s" && src[i + 2] === "{") {
|
|
635
|
+
if (isInHeader) break;
|
|
480
636
|
}
|
|
481
637
|
word += src[i];
|
|
482
638
|
i++;
|
|
@@ -490,7 +646,7 @@ function lexer(src, filename = "anonymous") {
|
|
|
490
646
|
} else if (isInHeader || isInInlineHead) {
|
|
491
647
|
// Inside a structural header context
|
|
492
648
|
const isMainIdentifier = (
|
|
493
|
-
last_non_junk_type === TOKEN_TYPES.OPEN_BRACKET ||
|
|
649
|
+
last_non_junk_type === TOKEN_TYPES.OPEN_BRACKET ||
|
|
494
650
|
last_non_junk_type === TOKEN_TYPES.OPEN_AT ||
|
|
495
651
|
(last_non_junk_type === TOKEN_TYPES.OPEN_PAREN && isInInlineHead)
|
|
496
652
|
);
|
|
@@ -498,23 +654,34 @@ function lexer(src, filename = "anonymous") {
|
|
|
498
654
|
if (isMainIdentifier) {
|
|
499
655
|
if (word === end_keyword) {
|
|
500
656
|
addToken(TOKEN_TYPES.END_KEYWORD, word);
|
|
501
|
-
if (delimiterStack[delimiterStack.length - 1] === "[") delimiterStack.pop();
|
|
502
657
|
}
|
|
503
658
|
else if (word === "import") addToken(TOKEN_TYPES.IMPORT, word);
|
|
504
659
|
else if (word === "$use-module") addToken(TOKEN_TYPES.USE_MODULE, word);
|
|
660
|
+
else if (word === "slot") addToken(TOKEN_TYPES.SLOT_KEYWORD, word);
|
|
661
|
+
else if (word === "for-each") addToken(TOKEN_TYPES.FOR_EACH, word);
|
|
505
662
|
else addToken(TOKEN_TYPES.IDENTIFIER, word);
|
|
506
663
|
} else {
|
|
507
664
|
// Use lookahead to distinguish KEY from VALUE
|
|
508
665
|
const p = peekStructural(i);
|
|
509
666
|
if (p === ":") {
|
|
510
667
|
addToken(TOKEN_TYPES.KEY, word);
|
|
668
|
+
} else if (word === "static") {
|
|
669
|
+
addToken(TOKEN_TYPES.STATIC_KEYWORD, word);
|
|
670
|
+
} else if (word === "runtime") {
|
|
671
|
+
addToken(TOKEN_TYPES.RUNTIME_KEYWORD, word);
|
|
511
672
|
} else {
|
|
512
673
|
addToken(TOKEN_TYPES.VALUE, word);
|
|
513
674
|
}
|
|
514
675
|
}
|
|
515
676
|
} else {
|
|
516
677
|
// Normal text
|
|
517
|
-
|
|
678
|
+
if (word.trim() === "static") {
|
|
679
|
+
addToken(TOKEN_TYPES.STATIC_KEYWORD, word);
|
|
680
|
+
} else if (word.trim() === "runtime") {
|
|
681
|
+
addToken(TOKEN_TYPES.RUNTIME_KEYWORD, word);
|
|
682
|
+
} else {
|
|
683
|
+
addToken(TOKEN_TYPES.TEXT, word);
|
|
684
|
+
}
|
|
518
685
|
}
|
|
519
686
|
} else {
|
|
520
687
|
// Fallback for any unhandled characters
|