sommark 5.0.3 → 5.0.5
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/core/evaluator.js +37 -10
- package/core/evaluator.stub.js +1 -0
- package/core/helpers/lib.js +1 -1
- package/core/modules.js +65 -4
- package/core/parser.js +5 -2
- package/core/transpiler.js +81 -82
- package/dist/sommark.browser.js +189 -97
- package/dist/sommark.browser.lite.js +151 -86
- package/dist/sommark.parser.js +5 -2
- package/index.shared.js +2 -1
- package/package.json +1 -1
|
@@ -1786,7 +1786,9 @@ function parseValue(tokens, i, placeholders = {}, variables = {}, allowLogic = t
|
|
|
1786
1786
|
}
|
|
1787
1787
|
variables.__consumed__.add(vKey);
|
|
1788
1788
|
} else {
|
|
1789
|
-
|
|
1789
|
+
// Encode fallback in the envelope key so resolveAstVariables can apply it
|
|
1790
|
+
// at instantiation time instead of baking it in now.
|
|
1791
|
+
val = getPrefixValue('v', vFallback !== undefined ? `${vKey}|${vFallback}` : vKey);
|
|
1790
1792
|
}
|
|
1791
1793
|
return [val, i, false];
|
|
1792
1794
|
} else if (current_token(tokens, i).type === TOKEN_TYPES.PREFIX_P) {
|
|
@@ -2180,7 +2182,8 @@ function parseText(tokens, i, placeholders = {}, variables = {}, depth = 0, opti
|
|
|
2180
2182
|
}
|
|
2181
2183
|
variables.__consumed__.add(tvKey);
|
|
2182
2184
|
} else {
|
|
2183
|
-
|
|
2185
|
+
// Encode fallback in envelope so resolveAstVariables can apply it later.
|
|
2186
|
+
textNode.text += getPrefixValue('v', tvFallback !== undefined ? `${tvKey}|${tvFallback}` : tvKey);
|
|
2184
2187
|
}
|
|
2185
2188
|
} else {
|
|
2186
2189
|
break;
|
|
@@ -2383,6 +2386,7 @@ function parser(tokens, filename = null, placeholders = {}, variables = {}) {
|
|
|
2383
2386
|
const LITE_ERROR =
|
|
2384
2387
|
"[SomMark lite] static ${}$ and runtime ${}$ blocks are not supported in lite mode. " +
|
|
2385
2388
|
"Use the full SomMark bundle to enable JS evaluation.";
|
|
2389
|
+
function withEvaluator(fn) { return fn(); }
|
|
2386
2390
|
|
|
2387
2391
|
class EvaluatorStub {
|
|
2388
2392
|
setDefaultFs(_fs) {}
|
|
@@ -8984,7 +8988,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
8984
8988
|
|
|
8985
8989
|
if (node.type === STATIC_LOGIC) {
|
|
8986
8990
|
try {
|
|
8987
|
-
const result = await Evaluator.execute(node.code);
|
|
8991
|
+
const result = await Evaluator.execute(node.code, node.baseDir || null);
|
|
8988
8992
|
if (generateRuntimeOutput) return "";
|
|
8989
8993
|
if (result && typeof result === "object" && result.__raw !== undefined) {
|
|
8990
8994
|
if (security?.allowRaw === false) {
|
|
@@ -9190,7 +9194,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9190
9194
|
}
|
|
9191
9195
|
} else if (child.type === STATIC_LOGIC) {
|
|
9192
9196
|
try {
|
|
9193
|
-
const val = await Evaluator.execute(child.code);
|
|
9197
|
+
const val = await Evaluator.execute(child.code, child.baseDir || null);
|
|
9194
9198
|
if (val !== undefined && typeof val !== "object") richText += String(val);
|
|
9195
9199
|
} catch (err) {
|
|
9196
9200
|
transpilerError([
|
|
@@ -9307,7 +9311,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
|
|
|
9307
9311
|
|
|
9308
9312
|
case STATIC_LOGIC:
|
|
9309
9313
|
try {
|
|
9310
|
-
const result = await Evaluator.execute(body_node.code);
|
|
9314
|
+
const result = await Evaluator.execute(body_node.code, body_node.baseDir || null);
|
|
9311
9315
|
if (result && typeof result === "object" && result.__raw !== undefined) {
|
|
9312
9316
|
if (security?.allowRaw === false) {
|
|
9313
9317
|
bodyOutput = mapper_file ? mapper_file.text(String(result.__raw)) : String(result.__raw);
|
|
@@ -9421,109 +9425,108 @@ async function transpiler(optionsOrAst, format, mapperFile) {
|
|
|
9421
9425
|
})();
|
|
9422
9426
|
|
|
9423
9427
|
const dualOutput = optionsOrAst?.dualOutput || false;
|
|
9424
|
-
|
|
9425
|
-
// Initialize Logic Sandbox
|
|
9426
|
-
await Evaluator.init(fileBaseDir, security, settings, targetMapper);
|
|
9427
|
-
// Inject global data
|
|
9428
9428
|
const placeholders = optionsOrAst?.placeholders || settings?.placeholders || {};
|
|
9429
9429
|
const variables = optionsOrAst?.variables || settings?.variables || {};
|
|
9430
9430
|
warnDroppedVariables(variables);
|
|
9431
|
-
Evaluator.inject(placeholders);
|
|
9432
|
-
Evaluator.inject(variables);
|
|
9433
9431
|
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9432
|
+
return withEvaluator(async () => {
|
|
9433
|
+
// Initialize Logic Sandbox inside isolated async context
|
|
9434
|
+
await Evaluator.init(fileBaseDir, security, settings, targetMapper);
|
|
9435
|
+
Evaluator.inject(placeholders);
|
|
9436
|
+
Evaluator.inject(variables);
|
|
9437
9437
|
|
|
9438
|
-
|
|
9439
|
-
|
|
9438
|
+
let output = "";
|
|
9439
|
+
let prev_body_node = null;
|
|
9440
|
+
let prev_was_silent = false;
|
|
9440
9441
|
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
let
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
prev_was_silent =
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9442
|
+
if (dualOutput) {
|
|
9443
|
+
const idState = { mode: 'record', ids: [], idx: 0 };
|
|
9444
|
+
|
|
9445
|
+
// HTML pass — generate HTML, record element IDs for runtime blocks
|
|
9446
|
+
let htmlOutput = "";
|
|
9447
|
+
try {
|
|
9448
|
+
for (let i = 0; i < body.length; i++) {
|
|
9449
|
+
const node = body[i];
|
|
9450
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
|
|
9451
|
+
let finalBlockOutput = blockOutput;
|
|
9452
|
+
if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
9453
|
+
if (finalBlockOutput) {
|
|
9454
|
+
htmlOutput += finalBlockOutput;
|
|
9455
|
+
prev_was_silent = false;
|
|
9456
|
+
} else {
|
|
9457
|
+
prev_was_silent = true;
|
|
9458
|
+
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
9459
|
+
const nextNode = body[i + 1];
|
|
9460
|
+
if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
|
|
9461
|
+
}
|
|
9457
9462
|
}
|
|
9458
9463
|
}
|
|
9464
|
+
} finally {
|
|
9465
|
+
Evaluator.destroy();
|
|
9459
9466
|
}
|
|
9460
|
-
} finally {
|
|
9461
|
-
Evaluator.destroy();
|
|
9462
|
-
}
|
|
9463
9467
|
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
+
// JS pass — replay the same IDs so querySelector targets match HTML
|
|
9469
|
+
idState.mode = 'replay';
|
|
9470
|
+
idState.idx = 0;
|
|
9471
|
+
prev_was_silent = false;
|
|
9468
9472
|
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9473
|
+
await Evaluator.init(fileBaseDir, security, settings, targetMapper);
|
|
9474
|
+
Evaluator.inject(placeholders);
|
|
9475
|
+
Evaluator.inject(variables);
|
|
9476
|
+
|
|
9477
|
+
let jsOutput = "";
|
|
9478
|
+
try {
|
|
9479
|
+
for (let i = 0; i < body.length; i++) {
|
|
9480
|
+
const node = body[i];
|
|
9481
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
|
|
9482
|
+
let finalBlockOutput = blockOutput;
|
|
9483
|
+
if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
9484
|
+
if (finalBlockOutput) {
|
|
9485
|
+
jsOutput += finalBlockOutput;
|
|
9486
|
+
prev_was_silent = false;
|
|
9487
|
+
} else {
|
|
9488
|
+
prev_was_silent = true;
|
|
9489
|
+
}
|
|
9490
|
+
}
|
|
9491
|
+
} finally {
|
|
9492
|
+
Evaluator.destroy();
|
|
9493
|
+
}
|
|
9494
|
+
|
|
9495
|
+
return [htmlOutput.trim(), jsOutput.trim()];
|
|
9496
|
+
}
|
|
9472
9497
|
|
|
9473
|
-
let jsOutput = "";
|
|
9474
9498
|
try {
|
|
9475
9499
|
for (let i = 0; i < body.length; i++) {
|
|
9476
9500
|
const node = body[i];
|
|
9477
|
-
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null,
|
|
9501
|
+
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, false, instance);
|
|
9502
|
+
|
|
9478
9503
|
let finalBlockOutput = blockOutput;
|
|
9479
|
-
if (prev_was_silent && node.type === TEXT$1)
|
|
9504
|
+
if (prev_was_silent && node.type === TEXT$1) {
|
|
9505
|
+
finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
9506
|
+
}
|
|
9507
|
+
|
|
9480
9508
|
if (finalBlockOutput) {
|
|
9481
|
-
|
|
9509
|
+
output += finalBlockOutput;
|
|
9482
9510
|
prev_was_silent = false;
|
|
9511
|
+
if (node.type !== TEXT$1 || node.text.trim().length > 0) {
|
|
9512
|
+
prev_body_node = node;
|
|
9513
|
+
}
|
|
9483
9514
|
} else {
|
|
9484
9515
|
prev_was_silent = true;
|
|
9516
|
+
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
9517
|
+
const nextNode = body[i + 1];
|
|
9518
|
+
if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) {
|
|
9519
|
+
i++;
|
|
9520
|
+
}
|
|
9521
|
+
}
|
|
9485
9522
|
}
|
|
9486
9523
|
}
|
|
9487
9524
|
} finally {
|
|
9488
9525
|
Evaluator.destroy();
|
|
9489
9526
|
}
|
|
9490
9527
|
|
|
9491
|
-
return
|
|
9492
|
-
}
|
|
9493
|
-
|
|
9494
|
-
try {
|
|
9495
|
-
for (let i = 0; i < body.length; i++) {
|
|
9496
|
-
const node = body[i];
|
|
9497
|
-
const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, false, instance);
|
|
9498
|
-
|
|
9499
|
-
let finalBlockOutput = blockOutput;
|
|
9500
|
-
if (prev_was_silent && node.type === TEXT$1) {
|
|
9501
|
-
finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
|
|
9502
|
-
}
|
|
9503
|
-
|
|
9504
|
-
if (finalBlockOutput) {
|
|
9505
|
-
output += finalBlockOutput;
|
|
9506
|
-
prev_was_silent = false;
|
|
9507
|
-
if (node.type !== TEXT$1 || node.text.trim().length > 0) {
|
|
9508
|
-
prev_body_node = node;
|
|
9509
|
-
}
|
|
9510
|
-
} else {
|
|
9511
|
-
prev_was_silent = true;
|
|
9512
|
-
if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
|
|
9513
|
-
// If a comment is removed, check the next node.
|
|
9514
|
-
// If it's just a blank line, skip it so we don't have extra gaps in the output.
|
|
9515
|
-
const nextNode = body[i + 1];
|
|
9516
|
-
if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) {
|
|
9517
|
-
i++; // Skip the next newline node
|
|
9518
|
-
}
|
|
9519
|
-
}
|
|
9520
|
-
}
|
|
9521
|
-
}
|
|
9522
|
-
} finally {
|
|
9523
|
-
Evaluator.destroy();
|
|
9524
|
-
}
|
|
9525
|
-
|
|
9526
|
-
return output.trim();
|
|
9528
|
+
return output.trim();
|
|
9529
|
+
});
|
|
9527
9530
|
}
|
|
9528
9531
|
|
|
9529
9532
|
/**
|
|
@@ -9546,7 +9549,7 @@ async function transpileArgs(props) {
|
|
|
9546
9549
|
result[key] = "";
|
|
9547
9550
|
} else if (value.type === STATIC_LOGIC) {
|
|
9548
9551
|
try {
|
|
9549
|
-
result[key] = await Evaluator.execute(value.code);
|
|
9552
|
+
result[key] = await Evaluator.execute(value.code, value.baseDir || null);
|
|
9550
9553
|
} catch (err) {
|
|
9551
9554
|
transpilerError([
|
|
9552
9555
|
`<$red:Logic Error (Argument):$> ${err.message}{line}`,
|
|
@@ -12184,7 +12187,10 @@ const resolveAstVariables = (nodes, variables) => {
|
|
|
12184
12187
|
for (const node of nodes) {
|
|
12185
12188
|
if (node.type === TEXT$1) {
|
|
12186
12189
|
if (node.text.includes(VAR_PREFIX)) {
|
|
12187
|
-
node.text = node.text.replace(VAR_PATTERN, (match,
|
|
12190
|
+
node.text = node.text.replace(VAR_PATTERN, (match, keyAndFallback) => {
|
|
12191
|
+
const pipeIdx = keyAndFallback.indexOf('|');
|
|
12192
|
+
const key = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
|
|
12193
|
+
const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
|
|
12188
12194
|
if (variables[key] !== undefined) {
|
|
12189
12195
|
if (!variables.__consumed__) {
|
|
12190
12196
|
Object.defineProperty(variables, "__consumed__", {
|
|
@@ -12197,14 +12203,21 @@ const resolveAstVariables = (nodes, variables) => {
|
|
|
12197
12203
|
variables.__consumed__.add(key);
|
|
12198
12204
|
return String(variables[key]);
|
|
12199
12205
|
}
|
|
12206
|
+
if (fallback !== undefined) return fallback;
|
|
12200
12207
|
return match;
|
|
12201
12208
|
});
|
|
12202
12209
|
}
|
|
12203
12210
|
} else if (node.type === BLOCK) {
|
|
12204
12211
|
// Resolve any unresolved variables in block arguments
|
|
12205
12212
|
for (const [argKey, argVal] of Object.entries(node.props)) {
|
|
12206
|
-
if (typeof argVal
|
|
12207
|
-
|
|
12213
|
+
if (typeof argVal !== "string" || !argVal.includes(VAR_PREFIX)) continue;
|
|
12214
|
+
|
|
12215
|
+
if (argVal.startsWith(VAR_PREFIX) && argVal.endsWith(VAR_SUFFIX)) {
|
|
12216
|
+
// Entire value is an envelope — resolve to scalar or fallback
|
|
12217
|
+
const keyAndFallback = argVal.slice(VAR_PREFIX.length, -VAR_SUFFIX.length);
|
|
12218
|
+
const pipeIdx = keyAndFallback.indexOf('|');
|
|
12219
|
+
const varKey = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
|
|
12220
|
+
const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
|
|
12208
12221
|
if (variables[varKey] !== undefined) {
|
|
12209
12222
|
node.props[argKey] = variables[varKey];
|
|
12210
12223
|
if (!variables.__consumed__) {
|
|
@@ -12216,7 +12229,31 @@ const resolveAstVariables = (nodes, variables) => {
|
|
|
12216
12229
|
});
|
|
12217
12230
|
}
|
|
12218
12231
|
variables.__consumed__.add(varKey);
|
|
12232
|
+
} else if (fallback !== undefined) {
|
|
12233
|
+
node.props[argKey] = fallback;
|
|
12219
12234
|
}
|
|
12235
|
+
} else {
|
|
12236
|
+
// Envelope embedded inside a larger string — replace in-place.
|
|
12237
|
+
// Unresolved envelopes become "" so they don't pollute class names etc.
|
|
12238
|
+
node.props[argKey] = argVal.replace(VAR_PATTERN, (match, keyAndFallback) => {
|
|
12239
|
+
const pipeIdx = keyAndFallback.indexOf('|');
|
|
12240
|
+
const key = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
|
|
12241
|
+
const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
|
|
12242
|
+
if (variables[key] !== undefined) {
|
|
12243
|
+
if (!variables.__consumed__) {
|
|
12244
|
+
Object.defineProperty(variables, "__consumed__", {
|
|
12245
|
+
value: new Set(),
|
|
12246
|
+
writable: true,
|
|
12247
|
+
enumerable: false,
|
|
12248
|
+
configurable: true
|
|
12249
|
+
});
|
|
12250
|
+
}
|
|
12251
|
+
variables.__consumed__.add(key);
|
|
12252
|
+
return String(variables[key]);
|
|
12253
|
+
}
|
|
12254
|
+
if (fallback !== undefined) return fallback;
|
|
12255
|
+
return "";
|
|
12256
|
+
});
|
|
12220
12257
|
}
|
|
12221
12258
|
}
|
|
12222
12259
|
if (node.body) {
|
|
@@ -12246,6 +12283,7 @@ const cloneAst = (nodes) => {
|
|
|
12246
12283
|
if (node.id !== undefined) nodeCopy.id = node.id;
|
|
12247
12284
|
if (node.code !== undefined) nodeCopy.code = node.code;
|
|
12248
12285
|
if (node.isSelfClosing !== undefined) nodeCopy.isSelfClosing = node.isSelfClosing;
|
|
12286
|
+
if (node.baseDir !== undefined) nodeCopy.baseDir = node.baseDir;
|
|
12249
12287
|
if (node.props !== undefined) {
|
|
12250
12288
|
nodeCopy.props = { ...node.props };
|
|
12251
12289
|
}
|
|
@@ -12257,6 +12295,20 @@ const cloneAst = (nodes) => {
|
|
|
12257
12295
|
return copy;
|
|
12258
12296
|
};
|
|
12259
12297
|
|
|
12298
|
+
/**
|
|
12299
|
+
* Tags all STATIC_LOGIC and RUNTIME_LOGIC nodes in a subtree with their
|
|
12300
|
+
* source module's baseDir so the evaluator can resolve imports correctly.
|
|
12301
|
+
*/
|
|
12302
|
+
const tagLogicNodes = (nodes, baseDir) => {
|
|
12303
|
+
if (!nodes) return;
|
|
12304
|
+
for (const node of nodes) {
|
|
12305
|
+
if ((node.type === STATIC_LOGIC || node.type === RUNTIME_LOGIC) && !node.baseDir) {
|
|
12306
|
+
node.baseDir = baseDir;
|
|
12307
|
+
}
|
|
12308
|
+
if (node.body) tagLogicNodes(node.body, baseDir);
|
|
12309
|
+
}
|
|
12310
|
+
};
|
|
12311
|
+
|
|
12260
12312
|
/**
|
|
12261
12313
|
* Handles all [import] and [$use-module] blocks in your code.
|
|
12262
12314
|
* It loads the requested files, checks for errors, and puts the content into the main document.
|
|
@@ -12430,6 +12482,7 @@ async function resolveModules(ast, context) {
|
|
|
12430
12482
|
format: context.format,
|
|
12431
12483
|
filename: mod.path,
|
|
12432
12484
|
baseDir: posix.dirname(mod.localPath),
|
|
12485
|
+
fs: context.instance.fs,
|
|
12433
12486
|
mapperFile: context.instance.mapperFile,
|
|
12434
12487
|
placeholders: context.instance.placeholders,
|
|
12435
12488
|
variables: {},
|
|
@@ -12445,6 +12498,7 @@ async function resolveModules(ast, context) {
|
|
|
12445
12498
|
});
|
|
12446
12499
|
|
|
12447
12500
|
const subAst = await subSmark.parse();
|
|
12501
|
+
tagLogicNodes(subAst, posix.dirname(mod.localPath));
|
|
12448
12502
|
context.instance.moduleCache.set(mod.localPath, subAst);
|
|
12449
12503
|
expandedNodes = trimAst(subAst);
|
|
12450
12504
|
}
|
|
@@ -12488,6 +12542,7 @@ async function resolveModules(ast, context) {
|
|
|
12488
12542
|
format: context.format,
|
|
12489
12543
|
filename: mod.path,
|
|
12490
12544
|
baseDir: posix.dirname(mod.localPath),
|
|
12545
|
+
fs: context.instance.fs,
|
|
12491
12546
|
mapperFile: context.instance.mapperFile,
|
|
12492
12547
|
placeholders: context.instance.placeholders,
|
|
12493
12548
|
variables: {}, // Parse without variables to keep the cached AST pure
|
|
@@ -12503,6 +12558,7 @@ async function resolveModules(ast, context) {
|
|
|
12503
12558
|
});
|
|
12504
12559
|
|
|
12505
12560
|
subAst = await subSmark.parse();
|
|
12561
|
+
tagLogicNodes(subAst, posix.dirname(mod.localPath));
|
|
12506
12562
|
context.instance.moduleCache.set(mod.localPath, subAst);
|
|
12507
12563
|
subAst = cloneAst(subAst);
|
|
12508
12564
|
}
|
|
@@ -12520,6 +12576,7 @@ async function resolveModules(ast, context) {
|
|
|
12520
12576
|
Object.entries(node.props).filter(([key]) => {
|
|
12521
12577
|
if (key === "__consumed__") return false;
|
|
12522
12578
|
if (consumed.has(key)) return false; // THE FIX: Filter if hit by v{}
|
|
12579
|
+
if (key === "smark-raw") return false; // directive — must not leak onto root element
|
|
12523
12580
|
return true;
|
|
12524
12581
|
})
|
|
12525
12582
|
);
|
|
@@ -12572,6 +12629,13 @@ async function resolveModules(ast, context) {
|
|
|
12572
12629
|
return ast;
|
|
12573
12630
|
}
|
|
12574
12631
|
|
|
12632
|
+
/**
|
|
12633
|
+
* After full transpilation of the top-level file, apply any v{} fallbacks that
|
|
12634
|
+
* remain unresolved. Envelopes with no fallback are kept as-is (debugging signal).
|
|
12635
|
+
* Must NOT be called on sub-module ASTs — only on the final top-level AST.
|
|
12636
|
+
*/
|
|
12637
|
+
const applyVariableFallbacks = (ast) => resolveAstVariables(ast, {});
|
|
12638
|
+
|
|
12575
12639
|
/**
|
|
12576
12640
|
* SomMark Rules Validator
|
|
12577
12641
|
*
|
|
@@ -13066,6 +13130,7 @@ class SomMark {
|
|
|
13066
13130
|
if (this.showSpinner) startSpinner();
|
|
13067
13131
|
try {
|
|
13068
13132
|
const ast = this.ast || await this.parse(src);
|
|
13133
|
+
applyVariableFallbacks(ast);
|
|
13069
13134
|
let result = await transpiler({
|
|
13070
13135
|
ast,
|
|
13071
13136
|
format: this.targetFormat,
|
package/dist/sommark.parser.js
CHANGED
|
@@ -1341,7 +1341,9 @@ function parseValue(tokens, i, placeholders = {}, variables = {}, allowLogic = t
|
|
|
1341
1341
|
}
|
|
1342
1342
|
variables.__consumed__.add(vKey);
|
|
1343
1343
|
} else {
|
|
1344
|
-
|
|
1344
|
+
// Encode fallback in the envelope key so resolveAstVariables can apply it
|
|
1345
|
+
// at instantiation time instead of baking it in now.
|
|
1346
|
+
val = getPrefixValue('v', vFallback !== undefined ? `${vKey}|${vFallback}` : vKey);
|
|
1345
1347
|
}
|
|
1346
1348
|
return [val, i, false];
|
|
1347
1349
|
} else if (current_token(tokens, i).type === TOKEN_TYPES.PREFIX_P) {
|
|
@@ -1735,7 +1737,8 @@ function parseText(tokens, i, placeholders = {}, variables = {}, depth = 0, opti
|
|
|
1735
1737
|
}
|
|
1736
1738
|
variables.__consumed__.add(tvKey);
|
|
1737
1739
|
} else {
|
|
1738
|
-
|
|
1740
|
+
// Encode fallback in envelope so resolveAstVariables can apply it later.
|
|
1741
|
+
textNode.text += getPrefixValue('v', tvFallback !== undefined ? `${tvKey}|${tvFallback}` : tvKey);
|
|
1739
1742
|
}
|
|
1740
1743
|
} else {
|
|
1741
1744
|
break;
|
package/index.shared.js
CHANGED
|
@@ -19,7 +19,7 @@ import { runtimeError } from "./core/errors.js";
|
|
|
19
19
|
import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat, jsoncFormat, xmlFormat, csvFormat, tomlFormat, yamlFormat } from "./core/formats.js";
|
|
20
20
|
import TOKEN_TYPES from "./core/tokenTypes.js";
|
|
21
21
|
import * as labels from "./core/labels.js";
|
|
22
|
-
import { resolveModules } from "./core/modules.js";
|
|
22
|
+
import { resolveModules, applyVariableFallbacks } from "./core/modules.js";
|
|
23
23
|
import { validateAST } from "./core/validator.js";
|
|
24
24
|
import { enableColor } from "./helpers/colorize.js";
|
|
25
25
|
import { safeArg } from "./helpers/utils.js";
|
|
@@ -292,6 +292,7 @@ class SomMark {
|
|
|
292
292
|
if (this.showSpinner) startSpinner();
|
|
293
293
|
try {
|
|
294
294
|
const ast = this.ast || await this.parse(src);
|
|
295
|
+
applyVariableFallbacks(ast);
|
|
295
296
|
let result = await transpiler({
|
|
296
297
|
ast,
|
|
297
298
|
format: this.targetFormat,
|
package/package.json
CHANGED