sommark 5.0.2 → 5.0.4

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.
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
1
3
  let _lazyMatch = () => { var __lib__=(()=>{var m=Object.defineProperty,V=Object.getOwnPropertyDescriptor,G=Object.getOwnPropertyNames,T=Object.prototype.hasOwnProperty,q=(r,e)=>{for(var n in e)m(r,n,{get:e[n],enumerable:true});},H=(r,e,n,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of G(e))!T.call(r,t)&&t!==n&&m(r,t,{get:()=>e[t],enumerable:!(a=V(e,t))||a.enumerable});return r},J=r=>H(m({},"__esModule",{value:true}),r),w={};q(w,{default:()=>re});var A=r=>Array.isArray(r),d=r=>typeof r=="function",Q=r=>r.length===0,W=r=>typeof r=="number",K=r=>typeof r=="object"&&r!==null,X=r=>r instanceof RegExp,b=r=>typeof r=="string",h=r=>r===void 0,Y=r=>{const e=new Map;return n=>{const a=e.get(n);if(a)return a;const t=r(n);return e.set(n,t),t}},rr=(r,e,n={})=>{const a={cache:{},input:r,index:0,indexMax:0,options:n,output:[]};if(v(e)(a)&&a.index===r.length)return a.output;throw new Error(`Failed to parse at index ${a.indexMax}`)},i=(r,e)=>A(r)?er(r,e):b(r)?ar(r,e):nr(r,e),er=(r,e)=>{const n={};for(const a of r){if(a.length!==1)throw new Error(`Invalid character: "${a}"`);const t=a.charCodeAt(0);n[t]=true;}return a=>{const t=a.index,o=a.input;for(;a.index<o.length&&o.charCodeAt(a.index)in n;)a.index+=1;const u=a.index;if(u>t){if(!h(e)&&!a.options.silent){const s=a.input.slice(t,u),c=d(e)?e(s,o,String(t)):e;h(c)||a.output.push(c);}a.indexMax=Math.max(a.indexMax,a.index);}return true}},nr=(r,e)=>{const n=r.source,a=r.flags.replace(/y|$/,"y"),t=new RegExp(n,a);return g(o=>{t.lastIndex=o.index;const u=t.exec(o.input);if(u){if(!h(e)&&!o.options.silent){const s=d(e)?e(...u,o.input,String(o.index)):e;h(s)||o.output.push(s);}return o.index+=u[0].length,o.indexMax=Math.max(o.indexMax,o.index),true}else return false})},ar=(r,e)=>n=>{if(n.input.startsWith(r,n.index)){if(!h(e)&&!n.options.silent){const t=d(e)?e(r,n.input,String(n.index)):e;h(t)||n.output.push(t);}return n.index+=r.length,n.indexMax=Math.max(n.indexMax,n.index),true}else return false},C=(r,e,n,a)=>{const t=v(r);return g(_(M(o=>{let u=0;for(;u<n;){const s=o.index;if(!t(o)||(u+=1,o.index===s))break}return u>=e})))},tr=(r,e)=>C(r,0,1),f=(r,e)=>C(r,0,1/0),x=(r,e)=>{const n=r.map(v);return g(_(M(a=>{for(let t=0,o=n.length;t<o;t++)if(!n[t](a))return false;return true})))},l=(r,e)=>{const n=r.map(v);return g(_(a=>{for(let t=0,o=n.length;t<o;t++)if(n[t](a))return true;return false}))},M=(r,e=false)=>{const n=v(r);return a=>{const t=a.index,o=a.output.length,u=n(a);return (!u||e)&&(a.index=t,a.output.length!==o&&(a.output.length=o)),u}},_=(r,e)=>{const n=v(r);return n},g=(()=>{let r=0;return e=>{const n=v(e),a=r+=1;return t=>{var o;if(t.options.memoization===false)return n(t);const u=t.index,s=(o=t.cache)[a]||(o[a]=new Map),c=s.get(u);if(c===false)return false;if(W(c))return t.index=c,true;if(c)return t.index=c.index,c.output?.length&&t.output.push(...c.output),true;{const Z=t.output.length;if(n(t)){const D=t.index,U=t.output.length;if(U>Z){const ee=t.output.slice(Z,U);s.set(u,{index:D,output:ee});}else s.set(u,D);return true}else return s.set(u,false),false}}}})(),E=r=>{let e;return n=>(e||(e=v(r())),e(n))},v=Y(r=>{if(d(r))return Q(r)?E(r):r;if(b(r)||X(r))return i(r);if(A(r))return x(r);if(K(r))return l(Object.values(r));throw new Error("Invalid rule")}),P="abcdefghijklmnopqrstuvwxyz",ir=r=>{let e="";for(;r>0;){const n=(r-1)%26;e=P[n]+e,r=Math.floor((r-1)/26);}return e},O=r=>{let e=0;for(let n=0,a=r.length;n<a;n++)e=e*26+P.indexOf(r[n])+1;return e},S=(r,e)=>{if(e<r)return S(e,r);const n=[];for(;r<=e;)n.push(r++);return n},or=(r,e,n)=>S(r,e).map(a=>String(a).padStart(n,"0")),R=(r,e)=>S(O(r),O(e)).map(ir),p=r=>r,z=r=>ur(e=>rr(e,r,{memoization:false}).join("")),ur=r=>{const e={};return n=>e[n]??(e[n]=r(n))},sr=i(/^\*\*\/\*$/,".*"),cr=i(/^\*\*\/(\*)?([ a-zA-Z0-9._-]+)$/,(r,e,n)=>`.*${e?"":"(?:^|/)"}${n.replaceAll(".","\\.")}`),lr=i(/^\*\*\/(\*)?([ a-zA-Z0-9._-]*)\{([ a-zA-Z0-9._-]+(?:,[ a-zA-Z0-9._-]+)*)\}$/,(r,e,n,a)=>`.*${e?"":"(?:^|/)"}${n.replaceAll(".","\\.")}(?:${a.replaceAll(",","|").replaceAll(".","\\.")})`),y=i(/\\./,p),pr=i(/[$.*+?^(){}[\]\|]/,r=>`\\${r}`),vr=i(/./,p),hr=i(/^(?:!!)*!(.*)$/,(r,e)=>`(?!^${L(e)}$).*?`),dr=i(/^(!!)+/,""),fr=l([hr,dr]),xr=i(/\/(\*\*\/)+/,"(?:/.+/|/)"),gr=i(/^(\*\*\/)+/,"(?:^|.*/)"),mr=i(/\/(\*\*)$/,"(?:/.*|$)"),_r=i(/\*\*/,".*"),j=l([xr,gr,mr,_r]),Sr=i(/\*\/(?!\*\*\/)/,"[^/]*/"),yr=i(/\*/,"[^/]*"),N=l([Sr,yr]),k=i("?","[^/]"),$r=i("[",p),wr=i("]",p),Ar=i(/[!^]/,"^/"),br=i(/[a-z]-[a-z]|[0-9]-[0-9]/i,p),Cr=i(/[$.*+?^(){}[\|]/,r=>`\\${r}`),Mr=i(/[^\]]/,p),Er=l([y,Cr,br,Mr]),B=x([$r,tr(Ar),f(Er),wr]),Pr=i("{","(?:"),Or=i("}",")"),Rr=i(/(\d+)\.\.(\d+)/,(r,e,n)=>or(+e,+n,Math.min(e.length,n.length)).join("|")),zr=i(/([a-z]+)\.\.([a-z]+)/,(r,e,n)=>R(e,n).join("|")),jr=i(/([A-Z]+)\.\.([A-Z]+)/,(r,e,n)=>R(e.toLowerCase(),n.toLowerCase()).join("|").toUpperCase()),Nr=l([Rr,zr,jr]),I=x([Pr,Nr,Or]),kr=i("{","(?:"),Br=i("}",")"),Ir=i(",","|"),Fr=i(/[$.*+?^(){[\]\|]/,r=>`\\${r}`),Lr=i(/[^}]/,p),Zr=E(()=>F),Dr=l([j,N,k,B,I,Zr,y,Fr,Ir,Lr]),F=x([kr,f(Dr),Br]),Ur=f(l([sr,cr,lr,fr,j,N,k,B,I,F,y,pr,vr])),Vr=Ur,Gr=z(Vr),L=Gr,Tr=i(/\\./,p),qr=i(/./,p),Hr=i(/\*\*\*+/,"*"),Jr=i(/([^/{[(!])\*\*/,(r,e)=>`${e}*`),Qr=i(/(^|.)\*\*(?=[^*/)\]}])/,(r,e)=>`${e}*`),Wr=f(l([Tr,Hr,Jr,Qr,qr])),Kr=Wr,Xr=z(Kr),Yr=Xr,$=(r,e)=>{const n=Array.isArray(r)?r:[r];if(!n.length)return false;const a=n.map($.compile),t=n.every(s=>/(\/(?:\*\*)?|\[\/\])$/.test(s)),o=e.replace(/[\\\/]+/g,"/").replace(/\/$/,t?"/":"");return a.some(s=>s.test(o))};$.compile=r=>new RegExp(`^${L(Yr(r))}$`,"s");var re=$;return J(w)})();
2
4
  return __lib__.default || __lib__; };
3
5
  let _match;
@@ -801,13 +803,6 @@ function lexer(src, filename = "anonymous") {
801
803
  // LOGIC BLOCKS (${ ... }$) — explicit: static/runtime ${ }$ shorthand: ${ }$ = static ${ }$
802
804
  if (char === "$" && next === "{") {
803
805
  {
804
- const hasExplicitKeyword = last_non_junk_type === TOKEN_TYPES.STATIC_KEYWORD || last_non_junk_type === TOKEN_TYPES.RUNTIME_KEYWORD;
805
- if (!hasExplicitKeyword) {
806
- // Zero-width: synthetic token has no source presence, must not shift position
807
- tokens.push({ type: TOKEN_TYPES.STATIC_KEYWORD, value: "static", source: filename, range: { start: { line, character }, end: { line, character } } });
808
- TOKEN_TYPES.STATIC_KEYWORD;
809
- last_non_junk_type = TOKEN_TYPES.STATIC_KEYWORD;
810
- }
811
806
  addToken(TOKEN_TYPES.LOGIC_OPEN, "${");
812
807
  i += 2;
813
808
 
@@ -1761,6 +1756,22 @@ function parseValue(tokens, i, placeholders = {}, variables = {}, allowLogic = t
1761
1756
  nextI++;
1762
1757
  }
1763
1758
 
1759
+ return [node, nextI, false];
1760
+ } else if (current_token(tokens, i).type === TOKEN_TYPES.LOGIC_OPEN) {
1761
+ if (!allowLogic) {
1762
+ parserError(errorMessage(tokens, i, "literal value", "", "Logic blocks are not allowed in this context."));
1763
+ }
1764
+ let nextI = i + 1;
1765
+ const logicToken = current_token(tokens, nextI);
1766
+ const node = makeLogicNode(STATIC_LOGIC);
1767
+ node.code = logicToken ? logicToken.value : "";
1768
+ node.range = logicToken ? logicToken.range : current_token(tokens, i).range;
1769
+ nextI++;
1770
+
1771
+ if (current_token(tokens, nextI) && current_token(tokens, nextI).type === TOKEN_TYPES.LOGIC_CLOSE) {
1772
+ nextI++;
1773
+ }
1774
+
1764
1775
  return [node, nextI, false];
1765
1776
  } else if (current_token(tokens, i).type === TOKEN_TYPES.PREFIX_V) {
1766
1777
  i++; // consume PREFIX_V keyword
@@ -1777,7 +1788,9 @@ function parseValue(tokens, i, placeholders = {}, variables = {}, allowLogic = t
1777
1788
  }
1778
1789
  variables.__consumed__.add(vKey);
1779
1790
  } else {
1780
- val = vFallback !== undefined ? vFallback : getPrefixValue('v', vKey);
1791
+ // Encode fallback in the envelope key so resolveAstVariables can apply it
1792
+ // at instantiation time instead of baking it in now.
1793
+ val = getPrefixValue('v', vFallback !== undefined ? `${vKey}|${vFallback}` : vKey);
1781
1794
  }
1782
1795
  return [val, i, false];
1783
1796
  } else if (current_token(tokens, i).type === TOKEN_TYPES.PREFIX_P) {
@@ -2171,7 +2184,8 @@ function parseText(tokens, i, placeholders = {}, variables = {}, depth = 0, opti
2171
2184
  }
2172
2185
  variables.__consumed__.add(tvKey);
2173
2186
  } else {
2174
- textNode.text += tvFallback !== undefined ? tvFallback : getPrefixValue('v', tvKey);
2187
+ // Encode fallback in envelope so resolveAstVariables can apply it later.
2188
+ textNode.text += getPrefixValue('v', tvFallback !== undefined ? `${tvKey}|${tvFallback}` : tvKey);
2175
2189
  }
2176
2190
  } else {
2177
2191
  break;
@@ -2269,6 +2283,27 @@ function parseNode(tokens, i, filename = null, placeholders = {}, variables = {}
2269
2283
  return [node, nextI];
2270
2284
  }
2271
2285
  // ========================================================================== //
2286
+ // Bare Logic Block (${ }$ without explicit static/runtime — defaults to static)
2287
+ // ========================================================================== //
2288
+ else if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.LOGIC_OPEN) {
2289
+ let nextI = i + 1;
2290
+ const logicToken = current_token(tokens, nextI);
2291
+ const node = makeLogicNode(STATIC_LOGIC);
2292
+ node.code = logicToken ? logicToken.value : "";
2293
+ node.depth = depth;
2294
+ node.range = {
2295
+ start: current_token(tokens, i).range.start,
2296
+ end: logicToken ? logicToken.range.end : current_token(tokens, i).range.end
2297
+ };
2298
+ nextI++;
2299
+
2300
+ if (current_token(tokens, nextI) && current_token(tokens, nextI).type === TOKEN_TYPES.LOGIC_CLOSE) {
2301
+ nextI++;
2302
+ }
2303
+
2304
+ return [node, nextI];
2305
+ }
2306
+ // ========================================================================== //
2272
2307
  // Text or Placeholder //
2273
2308
  // ========================================================================== //
2274
2309
  else if (
@@ -8592,7 +8627,7 @@ function registerHostSettings(settings) {
8592
8627
  hostSettings = settings || {};
8593
8628
  }
8594
8629
 
8595
- const version = "5.0.2";
8630
+ const version = "5.0.4";
8596
8631
 
8597
8632
  const SomMark$1 = {
8598
8633
  version,
@@ -8643,6 +8678,17 @@ const SomMark$1 = {
8643
8678
  // Freeze the entire Standard Library to make it completely immutable and tamper-proof
8644
8679
  Object.freeze(SomMark$1);
8645
8680
 
8681
+ // Each transpile() call gets its own isolated EvaluatorState stack via async context.
8682
+ const evaluatorStorage = new AsyncLocalStorage();
8683
+
8684
+ /**
8685
+ * Runs fn inside an isolated evaluator context.
8686
+ * Concurrent transpile() calls each get their own stack — no cross-contamination.
8687
+ */
8688
+ function withEvaluator(fn) {
8689
+ return evaluatorStorage.run([], fn);
8690
+ }
8691
+
8646
8692
  // Global tracker to ensure deep recursive Smark compilation never exceeds safe boundaries
8647
8693
  let globalCompilationDepth = 0;
8648
8694
 
@@ -9369,8 +9415,10 @@ class EvaluatorState {
9369
9415
  this.expose(vars);
9370
9416
  }
9371
9417
 
9372
- async execute(code) {
9418
+ async execute(code, baseDir = null) {
9373
9419
  if (!this.context) throw new Error("Evaluator not initialized");
9420
+ const prevBaseDir = this.baseDir;
9421
+ if (baseDir) this.baseDir = baseDir;
9374
9422
 
9375
9423
  const timeout = this.security?.timeout ?? 5000;
9376
9424
  this.deadline = Date.now() + timeout;
@@ -9604,6 +9652,7 @@ class EvaluatorState {
9604
9652
  } finally {
9605
9653
  this.deadline = 0;
9606
9654
  clearInterval(interval);
9655
+ if (baseDir) this.baseDir = prevBaseDir;
9607
9656
  }
9608
9657
  }
9609
9658
 
@@ -9640,30 +9689,42 @@ class EvaluatorState {
9640
9689
 
9641
9690
  class Evaluator {
9642
9691
  constructor() {
9643
- this.instances = [];
9692
+ // Fallback stack for callers that use init() outside withEvaluator() (e.g. tests).
9693
+ this._fallbackStack = [];
9694
+ }
9695
+
9696
+ _getStack() {
9697
+ return evaluatorStorage.getStore() ?? this._fallbackStack;
9644
9698
  }
9645
9699
 
9700
+ // Expose the active stack so tests can check .instances.length
9701
+ get instances() { return this._getStack(); }
9702
+
9646
9703
  setDefaultFs(fs) {
9647
9704
  defaultFs$1 = fs;
9648
9705
  }
9649
9706
 
9650
9707
  get active() {
9651
- if (this.instances.length === 0) {
9708
+ const stack = this._getStack();
9709
+ if (stack.length === 0) {
9652
9710
  throw new Error("No active EvaluatorState instance. Did you call init()?");
9653
9711
  }
9654
- return this.instances[this.instances.length - 1];
9712
+ return stack[stack.length - 1];
9655
9713
  }
9656
9714
 
9715
+ // Forward .runtime to the active state so tests can assert on it
9716
+ get runtime() { return this.active?.runtime ?? null; }
9717
+
9657
9718
  async init(baseDir = null, security = {}, settings = {}, mapperFile = null) {
9658
9719
  const state = new EvaluatorState();
9659
9720
  await state.init(baseDir, security, settings, mapperFile);
9660
- this.instances.push(state);
9721
+ this._getStack().push(state);
9661
9722
  }
9662
9723
 
9663
9724
  destroy() {
9664
- if (this.instances.length > 0) {
9665
- const state = this.instances.pop();
9666
- state.destroy();
9725
+ const stack = this._getStack();
9726
+ if (stack.length > 0) {
9727
+ stack.pop().destroy();
9667
9728
  }
9668
9729
  }
9669
9730
 
@@ -9679,8 +9740,8 @@ class Evaluator {
9679
9740
  this.active.inject(vars);
9680
9741
  }
9681
9742
 
9682
- async execute(code) {
9683
- return await this.active.execute(code);
9743
+ async execute(code, baseDir = null) {
9744
+ return await this.active.execute(code, baseDir);
9684
9745
  }
9685
9746
 
9686
9747
  hasDynamicTag(id) {
@@ -10045,7 +10106,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10045
10106
 
10046
10107
  if (node.type === STATIC_LOGIC) {
10047
10108
  try {
10048
- const result = await Evaluator$1.execute(node.code);
10109
+ const result = await Evaluator$1.execute(node.code, node.baseDir || null);
10049
10110
  if (generateRuntimeOutput) return "";
10050
10111
  if (result && typeof result === "object" && result.__raw !== undefined) {
10051
10112
  if (security?.allowRaw === false) {
@@ -10251,7 +10312,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10251
10312
  }
10252
10313
  } else if (child.type === STATIC_LOGIC) {
10253
10314
  try {
10254
- const val = await Evaluator$1.execute(child.code);
10315
+ const val = await Evaluator$1.execute(child.code, child.baseDir || null);
10255
10316
  if (val !== undefined && typeof val !== "object") richText += String(val);
10256
10317
  } catch (err) {
10257
10318
  transpilerError([
@@ -10368,7 +10429,7 @@ async function generateOutput(ast, i, format, mapper_file, security = {}, parent
10368
10429
 
10369
10430
  case STATIC_LOGIC:
10370
10431
  try {
10371
- const result = await Evaluator$1.execute(body_node.code);
10432
+ const result = await Evaluator$1.execute(body_node.code, body_node.baseDir || null);
10372
10433
  if (result && typeof result === "object" && result.__raw !== undefined) {
10373
10434
  if (security?.allowRaw === false) {
10374
10435
  bodyOutput = mapper_file ? mapper_file.text(String(result.__raw)) : String(result.__raw);
@@ -10482,109 +10543,108 @@ async function transpiler(optionsOrAst, format, mapperFile) {
10482
10543
  })();
10483
10544
 
10484
10545
  const dualOutput = optionsOrAst?.dualOutput || false;
10485
-
10486
- // Initialize Logic Sandbox
10487
- await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10488
- // Inject global data
10489
10546
  const placeholders = optionsOrAst?.placeholders || settings?.placeholders || {};
10490
10547
  const variables = optionsOrAst?.variables || settings?.variables || {};
10491
10548
  warnDroppedVariables(variables);
10492
- Evaluator$1.inject(placeholders);
10493
- Evaluator$1.inject(variables);
10494
10549
 
10495
- let output = "";
10496
- let prev_body_node = null;
10497
- let prev_was_silent = false;
10550
+ return withEvaluator(async () => {
10551
+ // Initialize Logic Sandbox inside isolated async context
10552
+ await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10553
+ Evaluator$1.inject(placeholders);
10554
+ Evaluator$1.inject(variables);
10555
+
10556
+ let output = "";
10557
+ let prev_body_node = null;
10558
+ let prev_was_silent = false;
10498
10559
 
10499
- if (dualOutput) {
10500
- const idState = { mode: 'record', ids: [], idx: 0 };
10560
+ if (dualOutput) {
10561
+ const idState = { mode: 'record', ids: [], idx: 0 };
10501
10562
 
10502
- // HTML pass — generate HTML, record element IDs for runtime blocks
10503
- let htmlOutput = "";
10504
- try {
10505
- for (let i = 0; i < body.length; i++) {
10506
- const node = body[i];
10507
- const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
10508
- let finalBlockOutput = blockOutput;
10509
- if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10510
- if (finalBlockOutput) {
10511
- htmlOutput += finalBlockOutput;
10512
- prev_was_silent = false;
10513
- } else {
10514
- prev_was_silent = true;
10515
- if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10516
- const nextNode = body[i + 1];
10517
- if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
10563
+ // HTML pass — generate HTML, record element IDs for runtime blocks
10564
+ let htmlOutput = "";
10565
+ try {
10566
+ for (let i = 0; i < body.length; i++) {
10567
+ const node = body[i];
10568
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, true, instance, idState);
10569
+ let finalBlockOutput = blockOutput;
10570
+ if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10571
+ if (finalBlockOutput) {
10572
+ htmlOutput += finalBlockOutput;
10573
+ prev_was_silent = false;
10574
+ } else {
10575
+ prev_was_silent = true;
10576
+ if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10577
+ const nextNode = body[i + 1];
10578
+ if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) i++;
10579
+ }
10518
10580
  }
10519
10581
  }
10582
+ } finally {
10583
+ Evaluator$1.destroy();
10520
10584
  }
10521
- } finally {
10522
- Evaluator$1.destroy();
10523
- }
10524
10585
 
10525
- // JS pass — replay the same IDs so querySelector targets match HTML
10526
- idState.mode = 'replay';
10527
- idState.idx = 0;
10528
- prev_was_silent = false;
10586
+ // JS pass — replay the same IDs so querySelector targets match HTML
10587
+ idState.mode = 'replay';
10588
+ idState.idx = 0;
10589
+ prev_was_silent = false;
10529
10590
 
10530
- await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10531
- Evaluator$1.inject(placeholders);
10532
- Evaluator$1.inject(variables);
10591
+ await Evaluator$1.init(fileBaseDir, security, settings, targetMapper);
10592
+ Evaluator$1.inject(placeholders);
10593
+ Evaluator$1.inject(variables);
10594
+
10595
+ let jsOutput = "";
10596
+ try {
10597
+ for (let i = 0; i < body.length; i++) {
10598
+ const node = body[i];
10599
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
10600
+ let finalBlockOutput = blockOutput;
10601
+ if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10602
+ if (finalBlockOutput) {
10603
+ jsOutput += finalBlockOutput;
10604
+ prev_was_silent = false;
10605
+ } else {
10606
+ prev_was_silent = true;
10607
+ }
10608
+ }
10609
+ } finally {
10610
+ Evaluator$1.destroy();
10611
+ }
10612
+
10613
+ return [htmlOutput.trim(), jsOutput.trim()];
10614
+ }
10533
10615
 
10534
- let jsOutput = "";
10535
10616
  try {
10536
10617
  for (let i = 0; i < body.length; i++) {
10537
10618
  const node = body[i];
10538
- const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, true, false, instance, idState);
10619
+ const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, false, instance);
10620
+
10539
10621
  let finalBlockOutput = blockOutput;
10540
- if (prev_was_silent && node.type === TEXT$1) finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10622
+ if (prev_was_silent && node.type === TEXT$1) {
10623
+ finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10624
+ }
10625
+
10541
10626
  if (finalBlockOutput) {
10542
- jsOutput += finalBlockOutput;
10627
+ output += finalBlockOutput;
10543
10628
  prev_was_silent = false;
10629
+ if (node.type !== TEXT$1 || node.text.trim().length > 0) {
10630
+ prev_body_node = node;
10631
+ }
10544
10632
  } else {
10545
10633
  prev_was_silent = true;
10634
+ if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10635
+ const nextNode = body[i + 1];
10636
+ if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) {
10637
+ i++;
10638
+ }
10639
+ }
10546
10640
  }
10547
10641
  }
10548
10642
  } finally {
10549
10643
  Evaluator$1.destroy();
10550
10644
  }
10551
10645
 
10552
- return [htmlOutput.trim(), jsOutput.trim()];
10553
- }
10554
-
10555
- try {
10556
- for (let i = 0; i < body.length; i++) {
10557
- const node = body[i];
10558
- const blockOutput = await generateOutput(body, i, targetFormat, targetMapper, security, null, false, false, instance);
10559
-
10560
- let finalBlockOutput = blockOutput;
10561
- if (prev_was_silent && node.type === TEXT$1) {
10562
- finalBlockOutput = finalBlockOutput.replace(/^\n/, "");
10563
- }
10564
-
10565
- if (finalBlockOutput) {
10566
- output += finalBlockOutput;
10567
- prev_was_silent = false;
10568
- if (node.type !== TEXT$1 || node.text.trim().length > 0) {
10569
- prev_body_node = node;
10570
- }
10571
- } else {
10572
- prev_was_silent = true;
10573
- if ((node.type === COMMENT || node.type === COMMENT_BLOCK) && targetMapper?.options?.removeComments) {
10574
- // If a comment is removed, check the next node.
10575
- // If it's just a blank line, skip it so we don't have extra gaps in the output.
10576
- const nextNode = body[i + 1];
10577
- if (nextNode && nextNode.type === TEXT$1 && (nextNode.text === "\n" || nextNode.text === "\r\n")) {
10578
- i++; // Skip the next newline node
10579
- }
10580
- }
10581
- }
10582
- }
10583
- } finally {
10584
- Evaluator$1.destroy();
10585
- }
10586
-
10587
- return output.trim();
10646
+ return output.trim();
10647
+ });
10588
10648
  }
10589
10649
 
10590
10650
  /**
@@ -10607,7 +10667,7 @@ async function transpileArgs(props) {
10607
10667
  result[key] = "";
10608
10668
  } else if (value.type === STATIC_LOGIC) {
10609
10669
  try {
10610
- result[key] = await Evaluator$1.execute(value.code);
10670
+ result[key] = await Evaluator$1.execute(value.code, value.baseDir || null);
10611
10671
  } catch (err) {
10612
10672
  transpilerError([
10613
10673
  `<$red:Logic Error (Argument):$> ${err.message}{line}`,
@@ -13245,7 +13305,10 @@ const resolveAstVariables = (nodes, variables) => {
13245
13305
  for (const node of nodes) {
13246
13306
  if (node.type === TEXT$1) {
13247
13307
  if (node.text.includes(VAR_PREFIX)) {
13248
- node.text = node.text.replace(VAR_PATTERN, (match, key) => {
13308
+ node.text = node.text.replace(VAR_PATTERN, (match, keyAndFallback) => {
13309
+ const pipeIdx = keyAndFallback.indexOf('|');
13310
+ const key = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
13311
+ const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
13249
13312
  if (variables[key] !== undefined) {
13250
13313
  if (!variables.__consumed__) {
13251
13314
  Object.defineProperty(variables, "__consumed__", {
@@ -13258,14 +13321,21 @@ const resolveAstVariables = (nodes, variables) => {
13258
13321
  variables.__consumed__.add(key);
13259
13322
  return String(variables[key]);
13260
13323
  }
13324
+ if (fallback !== undefined) return fallback;
13261
13325
  return match;
13262
13326
  });
13263
13327
  }
13264
13328
  } else if (node.type === BLOCK) {
13265
13329
  // Resolve any unresolved variables in block arguments
13266
13330
  for (const [argKey, argVal] of Object.entries(node.props)) {
13267
- if (typeof argVal === "string" && argVal.startsWith(VAR_PREFIX) && argVal.endsWith(VAR_SUFFIX)) {
13268
- const varKey = argVal.slice(VAR_PREFIX.length, -VAR_SUFFIX.length);
13331
+ if (typeof argVal !== "string" || !argVal.includes(VAR_PREFIX)) continue;
13332
+
13333
+ if (argVal.startsWith(VAR_PREFIX) && argVal.endsWith(VAR_SUFFIX)) {
13334
+ // Entire value is an envelope — resolve to scalar or fallback
13335
+ const keyAndFallback = argVal.slice(VAR_PREFIX.length, -VAR_SUFFIX.length);
13336
+ const pipeIdx = keyAndFallback.indexOf('|');
13337
+ const varKey = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
13338
+ const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
13269
13339
  if (variables[varKey] !== undefined) {
13270
13340
  node.props[argKey] = variables[varKey];
13271
13341
  if (!variables.__consumed__) {
@@ -13277,7 +13347,31 @@ const resolveAstVariables = (nodes, variables) => {
13277
13347
  });
13278
13348
  }
13279
13349
  variables.__consumed__.add(varKey);
13350
+ } else if (fallback !== undefined) {
13351
+ node.props[argKey] = fallback;
13280
13352
  }
13353
+ } else {
13354
+ // Envelope embedded inside a larger string — replace in-place.
13355
+ // Unresolved envelopes become "" so they don't pollute class names etc.
13356
+ node.props[argKey] = argVal.replace(VAR_PATTERN, (match, keyAndFallback) => {
13357
+ const pipeIdx = keyAndFallback.indexOf('|');
13358
+ const key = pipeIdx >= 0 ? keyAndFallback.slice(0, pipeIdx) : keyAndFallback;
13359
+ const fallback = pipeIdx >= 0 ? keyAndFallback.slice(pipeIdx + 1) : undefined;
13360
+ if (variables[key] !== undefined) {
13361
+ if (!variables.__consumed__) {
13362
+ Object.defineProperty(variables, "__consumed__", {
13363
+ value: new Set(),
13364
+ writable: true,
13365
+ enumerable: false,
13366
+ configurable: true
13367
+ });
13368
+ }
13369
+ variables.__consumed__.add(key);
13370
+ return String(variables[key]);
13371
+ }
13372
+ if (fallback !== undefined) return fallback;
13373
+ return "";
13374
+ });
13281
13375
  }
13282
13376
  }
13283
13377
  if (node.body) {
@@ -13307,6 +13401,7 @@ const cloneAst = (nodes) => {
13307
13401
  if (node.id !== undefined) nodeCopy.id = node.id;
13308
13402
  if (node.code !== undefined) nodeCopy.code = node.code;
13309
13403
  if (node.isSelfClosing !== undefined) nodeCopy.isSelfClosing = node.isSelfClosing;
13404
+ if (node.baseDir !== undefined) nodeCopy.baseDir = node.baseDir;
13310
13405
  if (node.props !== undefined) {
13311
13406
  nodeCopy.props = { ...node.props };
13312
13407
  }
@@ -13318,6 +13413,20 @@ const cloneAst = (nodes) => {
13318
13413
  return copy;
13319
13414
  };
13320
13415
 
13416
+ /**
13417
+ * Tags all STATIC_LOGIC and RUNTIME_LOGIC nodes in a subtree with their
13418
+ * source module's baseDir so the evaluator can resolve imports correctly.
13419
+ */
13420
+ const tagLogicNodes = (nodes, baseDir) => {
13421
+ if (!nodes) return;
13422
+ for (const node of nodes) {
13423
+ if ((node.type === STATIC_LOGIC || node.type === RUNTIME_LOGIC) && !node.baseDir) {
13424
+ node.baseDir = baseDir;
13425
+ }
13426
+ if (node.body) tagLogicNodes(node.body, baseDir);
13427
+ }
13428
+ };
13429
+
13321
13430
  /**
13322
13431
  * Handles all [import] and [$use-module] blocks in your code.
13323
13432
  * It loads the requested files, checks for errors, and puts the content into the main document.
@@ -13491,6 +13600,7 @@ async function resolveModules(ast, context) {
13491
13600
  format: context.format,
13492
13601
  filename: mod.path,
13493
13602
  baseDir: posix.dirname(mod.localPath),
13603
+ fs: context.instance.fs,
13494
13604
  mapperFile: context.instance.mapperFile,
13495
13605
  placeholders: context.instance.placeholders,
13496
13606
  variables: {},
@@ -13506,6 +13616,7 @@ async function resolveModules(ast, context) {
13506
13616
  });
13507
13617
 
13508
13618
  const subAst = await subSmark.parse();
13619
+ tagLogicNodes(subAst, posix.dirname(mod.localPath));
13509
13620
  context.instance.moduleCache.set(mod.localPath, subAst);
13510
13621
  expandedNodes = trimAst(subAst);
13511
13622
  }
@@ -13549,6 +13660,7 @@ async function resolveModules(ast, context) {
13549
13660
  format: context.format,
13550
13661
  filename: mod.path,
13551
13662
  baseDir: posix.dirname(mod.localPath),
13663
+ fs: context.instance.fs,
13552
13664
  mapperFile: context.instance.mapperFile,
13553
13665
  placeholders: context.instance.placeholders,
13554
13666
  variables: {}, // Parse without variables to keep the cached AST pure
@@ -13564,6 +13676,7 @@ async function resolveModules(ast, context) {
13564
13676
  });
13565
13677
 
13566
13678
  subAst = await subSmark.parse();
13679
+ tagLogicNodes(subAst, posix.dirname(mod.localPath));
13567
13680
  context.instance.moduleCache.set(mod.localPath, subAst);
13568
13681
  subAst = cloneAst(subAst);
13569
13682
  }
@@ -13633,6 +13746,13 @@ async function resolveModules(ast, context) {
13633
13746
  return ast;
13634
13747
  }
13635
13748
 
13749
+ /**
13750
+ * After full transpilation of the top-level file, apply any v{} fallbacks that
13751
+ * remain unresolved. Envelopes with no fallback are kept as-is (debugging signal).
13752
+ * Must NOT be called on sub-module ASTs — only on the final top-level AST.
13753
+ */
13754
+ const applyVariableFallbacks = (ast) => resolveAstVariables(ast, {});
13755
+
13636
13756
  /**
13637
13757
  * SomMark Rules Validator
13638
13758
  *
@@ -14127,6 +14247,7 @@ class SomMark {
14127
14247
  if (this.showSpinner) startSpinner();
14128
14248
  try {
14129
14249
  const ast = this.ast || await this.parse(src);
14250
+ applyVariableFallbacks(ast);
14130
14251
  let result = await transpiler({
14131
14252
  ast,
14132
14253
  format: this.targetFormat,