script-engine-lib 1.0.0-rc1 → 1.0.0-rc3

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/dist/index.d.mts CHANGED
@@ -115,6 +115,14 @@ declare class ScriptTestSimulator<T extends ScriptEngineSimulatorFunctions> {
115
115
  getUnvisitedBranchLocations(): ExecutionLocation[];
116
116
  }
117
117
 
118
+ /**
119
+ * Interface representing a text segment with text content and associated tags
120
+ */
121
+ interface TextSegment {
122
+ text: string;
123
+ tags: string[];
124
+ }
125
+
118
126
  /**
119
127
  * Class to separate text with XML tags into structured segments
120
128
  */
@@ -180,12 +188,4 @@ declare class RichTextValidator {
180
188
  static validate(input: string, tags?: Set<string>): void;
181
189
  }
182
190
 
183
- /**
184
- * Interface representing a text segment with text content and associated tags
185
- */
186
- interface TextSegment {
187
- text: string;
188
- tags: string[];
189
- }
190
-
191
191
  export { type ActionBeforeTestType, ActionBeforeTesting, ActionsBeforeTestingError, type BranchByChanceItemDefinition, type BranchByConditionItemDefinition, type BranchByPlayerChoiceItemDefinition, BranchingBeforeTestingError, type DialogItemDefinition, type ExecutionLocation, JSEngine, JSEngineFunction, RichTextSeparator, RichTextValidator, type ScriptActionDefinition, type ScriptActionType, type ScriptDefinition, ScriptEngine, ScriptEngineFunctions, type ScriptEngineOptions, ScriptEngineSimulatorFunctions, ScriptEngineState, type ScriptTestActionDefinition, ScriptTestSimulator, type ScriptTestSimulatorOptions, SimulationError, type TextSegment, type VisitableScriptBranchDefinition };
package/dist/index.d.ts CHANGED
@@ -115,6 +115,14 @@ declare class ScriptTestSimulator<T extends ScriptEngineSimulatorFunctions> {
115
115
  getUnvisitedBranchLocations(): ExecutionLocation[];
116
116
  }
117
117
 
118
+ /**
119
+ * Interface representing a text segment with text content and associated tags
120
+ */
121
+ interface TextSegment {
122
+ text: string;
123
+ tags: string[];
124
+ }
125
+
118
126
  /**
119
127
  * Class to separate text with XML tags into structured segments
120
128
  */
@@ -180,12 +188,4 @@ declare class RichTextValidator {
180
188
  static validate(input: string, tags?: Set<string>): void;
181
189
  }
182
190
 
183
- /**
184
- * Interface representing a text segment with text content and associated tags
185
- */
186
- interface TextSegment {
187
- text: string;
188
- tags: string[];
189
- }
190
-
191
191
  export { type ActionBeforeTestType, ActionBeforeTesting, ActionsBeforeTestingError, type BranchByChanceItemDefinition, type BranchByConditionItemDefinition, type BranchByPlayerChoiceItemDefinition, BranchingBeforeTestingError, type DialogItemDefinition, type ExecutionLocation, JSEngine, JSEngineFunction, RichTextSeparator, RichTextValidator, type ScriptActionDefinition, type ScriptActionType, type ScriptDefinition, ScriptEngine, ScriptEngineFunctions, type ScriptEngineOptions, ScriptEngineSimulatorFunctions, ScriptEngineState, type ScriptTestActionDefinition, ScriptTestSimulator, type ScriptTestSimulatorOptions, SimulationError, type TextSegment, type VisitableScriptBranchDefinition };
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
- 'use strict';var helpersLib=require('helpers-lib');require('reflect-metadata');var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function P(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.c=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.T(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return helpersLib.Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
1
+ 'use strict';var helpersLib=require('helpers-lib');require('reflect-metadata');var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function P(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.šap=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.šan(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return helpersLib.Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
2
2
  with (vars) {
3
3
  ${e};
4
4
  }
5
- `);try{t(this.c);}catch(i){this.l(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
5
+ `);try{t(this.šap);}catch(i){this.šaf(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
6
6
  with (vars) {
7
7
  return Boolean(${e});
8
8
  }
9
- `)(this.c);}catch(i){return this.l(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
9
+ `)(this.šap);}catch(i){return this.šaf(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
10
10
  with (vars) {
11
11
  return Number(${e});
12
12
  }
13
- `)(this.c);}catch(i){return this.l(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
13
+ `)(this.šap);}catch(i){return this.šaf(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
14
14
  with (vars) {
15
15
  return String(\`${e}\`);
16
16
  }
17
- `)(this.c);}catch(i){return this.l(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=helpersLib.JsonHelper.deepCopy(this.variables),t=helpersLib.JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}T(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}l(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function J(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},C=(i=>(i[i.Idle=1]="Idle",i[i.Running=2]="Running",i[i.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",i))(C||{}),B=class{constructor(e,t,i,n){this.o=new helpersLib.Stack;this.x=false;this.A(t),this.x=!!n?.manualTestingMode,this.h=e,this.i=new p(t,i);}get variables(){return this.i.variables}get state(){return this.n?3:this.a?2:1}get a(){return !this.o.isEmpty}start(e){if(!this.o.isEmpty)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.w(e);}next(){if(this.o.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(this.state!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${C[this.state]}`);let e=this.o.pop();this.E(e);}playerChoice(e){if(!this.n)throw new Error("ScriptEngine: No player branching choices available.");let t=this.n[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.u(t),this.n=void 0,this.a&&this.next();}w(e){let t=this.h[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.u(t.actions);}E(e){switch(e.type){case "command":this.i.execute(e.value);break;case "dialog":{let t=e.value;this.i.functions.onDialog(this.i.string(t.text),t.speaker);break}case "jumpTo":{this.w(e.value),this.a&&this.next();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.i.boolean(n.condition):true);if(i)this.u(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.a&&this.next();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.i.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.x)this.n=t.map(i=>i.actions),this.i.functions.onPlayerChoice(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=helpersLib.Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:helpersLib.Comparator.isString(n.weight)?this.i.number(n.weight):n.weight})));this.u(i),this.a&&this.next();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.i.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.n=t.map(i=>i.actions),this.i.functions.onPlayerChoice(t.map(i=>this.i.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}u(e){this.o.add(...e);}A(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var D=class{static process(e,t){y.validate(e,t);let i=this.m(e,[]);return this.I(i)}static I(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static m(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.$(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.m(c,u);i.push(...f),n=s+o.length+3;}return i}static $(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var w=class h{constructor(){this.g=[];}addEntry(e){return this.g.push(e),this}toString(){return this.g.join(`
18
- `)}duplicate(){let e=new h;return e.g=[...this.g],e}};var m=class{constructor(e){this.b=new Map;this.f=new Set;this.d=new Map;this.v=new Map;e.forEach(t=>{this.b.set(t.id,t),this.p(t,1,t.id,void 0,[]);});}getScript(e){let t=this.b.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.d.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.d.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.v.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.f.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.f).map(e=>this.d.get(e)).filter(e=>!e.parentBranchInfo||!this.f.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}p(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.f.add(e),this.d.set(e,a),this.F(e.actions,i,a,r);}F(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.v.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.p(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.p(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.p(u,4,t,i,g);});});}};var T=class{},M=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.h=e;this.y=t;this.s=new Map;this.t=new m(this.h),this.y.prscriptTypeChanges&&(this.B=new Map);}simulateScript(e,t,i){this.y.prscriptTypeChanges&&(t.globalNameSpace=this.B);let r=[{executionHistory:new w,stack:new helpersLib.Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.M(i,r),this.s.clear(),this.R(e,r),this.s.clear();}getUnvisitedBranchLocations(){return this.t.getUnvisitedBranchLocations()}M(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.k(i,t,n);break;case "runScript":this.P(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}k(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.e(`${n}`,t[0].executionHistory);throw new S(r,i)}}P(e,t,i){let n=[],r=this.t.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.s.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.r(r,o,!1);if(!s.exitFound){let c=this.e(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.t.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.C(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.t.getActionLocation(o.lastExecutedAction),u=this.e(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}R(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.r(e,i,true);if(!n.exitFound){let r=this.e(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.t.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.t.getActionLocation(r.lastExecutedAction),s=this.e(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}r(e,t,i){if(!this.O(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.t.setVisited(e),t.depth++,t.depth>M){let a=this.e(`Maximum depth "${M}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.t.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.E(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.C(n):[],{allEndings:n,exitFound:r}}E(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.J(e,t);case "dialog":return this.L(e,t);case "jumpTo":return this.j(e,t,i);case "branchByCondition":return this.H(e,t,i);case "branchByChance":return this.N(e,t,i);case "branchByPlayerChoice":return this.V(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}J(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.t.getActionLocation(e),r=this.e(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}L(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.y.richTextTags&&y.validate(n,this.y.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.t.getActionLocation(e),o=this.e(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}j(e,t,i){let n=this.t.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.r(n,t,i)}H(e,t,i){let r=e.value.find(a=>this.S(a,t));if(!r){let a=this.t.getActionLocation(e),o=this.e("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.r(r,t,i)}N(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.S(o.subBranch,t));if(r.length===0){let o=this.t.getActionLocation(e),s=this.e("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=helpersLib.Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!helpersLib.Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.t.getActionLocation(e),f=this.e(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.D(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.r(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}V(e,t,i){let r=e.value.filter(o=>this.S(o,t));if(r.length===0){let o=this.t.getActionLocation(e),s=this.e("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.t.getActionLocation(e),c=this.e(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.D(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.r(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}S(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.t.getBranchLocation(e),r=this.e(`${i}`,t.executionHistory);throw new l(r,n)}else return true}C(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}O(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.s.get(e);return n||(n=new Set,this.s.set(e,n)),n.has(i)?false:(n.add(i),true)}D(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}e(e,t){return `${e}
17
+ `)(this.šap);}catch(i){return this.šaf(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=helpersLib.JsonHelper.deepCopy(this.variables),t=helpersLib.JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}šan(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}šaf(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function J(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},helpersLib.MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},C=(i=>(i[i.Idle=1]="Idle",i[i.Running=2]="Running",i[i.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",i))(C||{}),B=class{constructor(e,t,i,n){this.šal=new helpersLib.Stack;this.šz=false;this.šao(t),this.šz=!!n?.manualTestingMode,this.šak=e,this.šx=new p(t,i);}get variables(){return this.šx.variables}get state(){return this.šab?3:this.šai?2:1}get šai(){return !this.šal.isEmpty}start(e){if(!this.šal.isEmpty)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.šy(e);}next(){if(this.šal.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(this.state!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${C[this.state]}`);let e=this.šal.pop();this.ši(e);}playerChoice(e){if(!this.šab)throw new Error("ScriptEngine: No player branching choices available.");let t=this.šab[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.šv(t),this.šab=void 0,this.šai&&this.next();}šy(e){let t=this.šak[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.šv(t.actions);}ši(e){switch(e.type){case "command":this.šx.execute(e.value);break;case "dialog":{let t=e.value;this.šx.functions.onDialog(this.šx.string(t.text),t.speaker);break}case "jumpTo":{this.šy(e.value),this.šai&&this.next();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.šx.boolean(n.condition):true);if(i)this.šv(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.šai&&this.next();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.šz)this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=helpersLib.Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:helpersLib.Comparator.isString(n.weight)?this.šx.number(n.weight):n.weight})));this.šv(i),this.šai&&this.next();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map(i=>this.šx.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}šv(e){this.šal.add(...e);}šao(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var D=class{static process(e,t){y.validate(e,t);let i=this.šae(e,[]);return this.šf(i)}static šf(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static šae(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.šs(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.šae(c,u);i.push(...f),n=s+o.length+3;}return i}static šs(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var m=class h{constructor(){this.šh=[];}addEntry(e){return this.šh.push(e),this}toString(){return this.šh.join(`
18
+ `)}duplicate(){let e=new h;return e.šh=[...this.šh],e}};var w=class{constructor(e){this.šu=new Map;this.šam=new Set;this.šc=new Map;this.ša=new Map;e.forEach(t=>{this.šu.set(t.id,t),this.šag(t,1,t.id,void 0,[]);});}getScript(e){let t=this.šu.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.ša.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.šam.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.šam).map(e=>this.šc.get(e)).filter(e=>!e.parentBranchInfo||!this.šam.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}šag(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.šam.add(e),this.šc.set(e,a),this.še(e.actions,i,a,r);}še(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.ša.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.šag(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.šag(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.šag(u,4,t,i,g);});});}};var T=class{},M=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.šak=e;this.šaa=t;this.šaq=new Map;this.šaj=new w(this.šak),this.šaa.prscriptTypeChanges&&(this.št=new Map);}simulateScript(e,t,i){this.šaa.prscriptTypeChanges&&(t.globalNameSpace=this.št);let r=[{executionHistory:new m,stack:new helpersLib.Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.šad(i,r),this.šaq.clear(),this.šr(e,r),this.šaq.clear();}getUnvisitedBranchLocations(){return this.šaj.getUnvisitedBranchLocations()}šad(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.šj(i,t,n);break;case "runScript":this.šb(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}šj(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.šac(`${n}`,t[0].executionHistory);throw new S(r,i)}}šb(e,t,i){let n=[],r=this.šaj.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.šaq.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.šp(r,o,!1);if(!s.exitFound){let c=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.šaj.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.šah(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.šaj.getActionLocation(o.lastExecutedAction),u=this.šac(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}šr(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.šp(e,i,true);if(!n.exitFound){let r=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.šaj.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.šaj.getActionLocation(r.lastExecutedAction),s=this.šac(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}šp(e,t,i){if(!this.šw(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.šaj.setVisited(e),t.depth++,t.depth>M){let a=this.šac(`Maximum depth "${M}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.šaj.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.ši(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.šah(n):[],{allEndings:n,exitFound:r}}ši(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.šn(e,t);case "dialog":return this.šq(e,t);case "jumpTo":return this.šo(e,t,i);case "branchByCondition":return this.šl(e,t,i);case "branchByChance":return this.šk(e,t,i);case "branchByPlayerChoice":return this.šm(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}šn(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.šaj.getActionLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}šq(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.šaa.richTextTags&&y.validate(n,this.šaa.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.šaj.getActionLocation(e),o=this.šac(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}šo(e,t,i){let n=this.šaj.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.šp(n,t,i)}šl(e,t,i){let r=e.value.find(a=>this.šd(a,t));if(!r){let a=this.šaj.getActionLocation(e),o=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.šp(r,t,i)}šk(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.šd(o.subBranch,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=helpersLib.Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!helpersLib.Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.šaj.getActionLocation(e),f=this.šac(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.šg(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.šp(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šm(e,t,i){let r=e.value.filter(o=>this.šd(o,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.šaj.getActionLocation(e),c=this.šac(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.šg(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.šp(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šd(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.šaj.getBranchLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}else return true}šah(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}šw(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.šaq.get(e);return n||(n=new Set,this.šaq.set(e,n)),n.has(i)?false:(n.add(i),true)}šg(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}šac(e,t){return `${e}
19
19
 
20
20
  ----Execution history----
21
21
  ${t.toString()}`}};exports.ActionBeforeTesting=J;exports.ActionsBeforeTestingError=S;exports.BranchingBeforeTestingError=x;exports.JSEngine=p;exports.JSEngineFunction=P;exports.RichTextSeparator=D;exports.RichTextValidator=y;exports.ScriptEngine=B;exports.ScriptEngineFunctions=v;exports.ScriptEngineSimulatorFunctions=T;exports.ScriptEngineState=C;exports.ScriptTestSimulator=A;exports.SimulationError=l;
package/dist/index.mjs CHANGED
@@ -1,21 +1,21 @@
1
- import {MetaDataHelper,Comparator,JsonHelper,Stack,Random}from'helpers-lib';import'reflect-metadata';var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function P(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.c=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.T(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
1
+ import {MetaDataHelper,Comparator,JsonHelper,Stack,Random}from'helpers-lib';import'reflect-metadata';var b=new Set(["Boolean","Number","String"]),d="jsEngineExecutionFlag",$="JSEngineFunctionFlag";function P(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(d,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be usable by JSEngine.`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var p=class h{constructor(e,t,i){this.variables={};this.šap=new Proxy({},{get:(e,t)=>{if(typeof t!="symbol"){if(t==="Boolean")return Boolean;if(t==="Number")return Number;if(t==="String")return String;if(typeof this.functions[t]=="function"){if(!h.isJSEngineFunction(this.functions[t]))throw new Error(`"${t}(...)" is not a JSEngine function, it cannot be called during the executions.`);return (...i)=>this.functions[t](...i)}else if(Object.hasOwn(this.variables,t))return this.variables[t]}},set:(e,t,i)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be assigned.`);if(Object.hasOwn(this.functions,t))throw new Error(`JSEngine: Cannot set a value to the property "${t}". It is already in use as a function.`);if(this.globalNameSpace&&this.globalNameSpace.has(t)&&typeof i!==this.globalNameSpace.get(t))throw new Error(`JSEngine: Type mismatch during variable set. The type of "${t}" is "${typeof i}", and it is tried to set to "${this.globalNameSpace.get(t)}".`);return this.variables[t]=i,this.globalNameSpace&&this.globalNameSpace.set(t,typeof i),true},has(){return true},deleteProperty:(e,t)=>{if(b.has(t))throw new Error(`JSEngine: Reserved word "${t}" cannot be deleted.`);return delete this.variables[t],true}});this.šan(e,t),this.variables=t,this.functions=e,this.globalNameSpace=i;}static isJSEngineFunction(e){return Comparator.isFunction(e)?Reflect.getOwnMetadata($,e)===true:false}static setAsJSEngineFunction(e){Reflect.defineMetadata($,true,e);}execute(e){Reflect.defineMetadata(d,true,this.functions);let t=new Function("vars",`
2
2
  with (vars) {
3
3
  ${e};
4
4
  }
5
- `);try{t(this.c);}catch(i){this.l(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
5
+ `);try{t(this.šap);}catch(i){this.šaf(i);}Reflect.defineMetadata(d,false,this.functions);}boolean(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
6
6
  with (vars) {
7
7
  return Boolean(${e});
8
8
  }
9
- `)(this.c);}catch(i){return this.l(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
9
+ `)(this.šap);}catch(i){return this.šaf(i),false}return Reflect.defineMetadata(d,false,this.functions),t}number(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
10
10
  with (vars) {
11
11
  return Number(${e});
12
12
  }
13
- `)(this.c);}catch(i){return this.l(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
13
+ `)(this.šap);}catch(i){return this.šaf(i),0}return Reflect.defineMetadata(d,false,this.functions),t}string(e){Reflect.defineMetadata(d,true,this.functions);let t;try{t=new Function("vars",`
14
14
  with (vars) {
15
15
  return String(\`${e}\`);
16
16
  }
17
- `)(this.c);}catch(i){return this.l(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=JsonHelper.deepCopy(this.variables),t=JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}T(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}l(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function J(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},C=(i=>(i[i.Idle=1]="Idle",i[i.Running=2]="Running",i[i.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",i))(C||{}),B=class{constructor(e,t,i,n){this.o=new Stack;this.x=false;this.A(t),this.x=!!n?.manualTestingMode,this.h=e,this.i=new p(t,i);}get variables(){return this.i.variables}get state(){return this.n?3:this.a?2:1}get a(){return !this.o.isEmpty}start(e){if(!this.o.isEmpty)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.w(e);}next(){if(this.o.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(this.state!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${C[this.state]}`);let e=this.o.pop();this.E(e);}playerChoice(e){if(!this.n)throw new Error("ScriptEngine: No player branching choices available.");let t=this.n[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.u(t),this.n=void 0,this.a&&this.next();}w(e){let t=this.h[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.u(t.actions);}E(e){switch(e.type){case "command":this.i.execute(e.value);break;case "dialog":{let t=e.value;this.i.functions.onDialog(this.i.string(t.text),t.speaker);break}case "jumpTo":{this.w(e.value),this.a&&this.next();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.i.boolean(n.condition):true);if(i)this.u(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.a&&this.next();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.i.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.x)this.n=t.map(i=>i.actions),this.i.functions.onPlayerChoice(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:Comparator.isString(n.weight)?this.i.number(n.weight):n.weight})));this.u(i),this.a&&this.next();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.i.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.n=t.map(i=>i.actions),this.i.functions.onPlayerChoice(t.map(i=>this.i.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}u(e){this.o.add(...e);}A(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var D=class{static process(e,t){y.validate(e,t);let i=this.m(e,[]);return this.I(i)}static I(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static m(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.$(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.m(c,u);i.push(...f),n=s+o.length+3;}return i}static $(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var w=class h{constructor(){this.g=[];}addEntry(e){return this.g.push(e),this}toString(){return this.g.join(`
18
- `)}duplicate(){let e=new h;return e.g=[...this.g],e}};var m=class{constructor(e){this.b=new Map;this.f=new Set;this.d=new Map;this.v=new Map;e.forEach(t=>{this.b.set(t.id,t),this.p(t,1,t.id,void 0,[]);});}getScript(e){let t=this.b.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.d.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.d.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.v.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.f.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.f).map(e=>this.d.get(e)).filter(e=>!e.parentBranchInfo||!this.f.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}p(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.f.add(e),this.d.set(e,a),this.F(e.actions,i,a,r);}F(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.v.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.p(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.p(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.p(u,4,t,i,g);});});}};var T=class{},M=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.h=e;this.y=t;this.s=new Map;this.t=new m(this.h),this.y.prscriptTypeChanges&&(this.B=new Map);}simulateScript(e,t,i){this.y.prscriptTypeChanges&&(t.globalNameSpace=this.B);let r=[{executionHistory:new w,stack:new Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.M(i,r),this.s.clear(),this.R(e,r),this.s.clear();}getUnvisitedBranchLocations(){return this.t.getUnvisitedBranchLocations()}M(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.k(i,t,n);break;case "runScript":this.P(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}k(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.e(`${n}`,t[0].executionHistory);throw new S(r,i)}}P(e,t,i){let n=[],r=this.t.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.s.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.r(r,o,!1);if(!s.exitFound){let c=this.e(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.t.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.C(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.t.getActionLocation(o.lastExecutedAction),u=this.e(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}R(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.r(e,i,true);if(!n.exitFound){let r=this.e(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.t.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.t.getActionLocation(r.lastExecutedAction),s=this.e(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}r(e,t,i){if(!this.O(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.t.setVisited(e),t.depth++,t.depth>M){let a=this.e(`Maximum depth "${M}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.t.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.E(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.C(n):[],{allEndings:n,exitFound:r}}E(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.J(e,t);case "dialog":return this.L(e,t);case "jumpTo":return this.j(e,t,i);case "branchByCondition":return this.H(e,t,i);case "branchByChance":return this.N(e,t,i);case "branchByPlayerChoice":return this.V(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}J(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.t.getActionLocation(e),r=this.e(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}L(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.y.richTextTags&&y.validate(n,this.y.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.t.getActionLocation(e),o=this.e(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}j(e,t,i){let n=this.t.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.r(n,t,i)}H(e,t,i){let r=e.value.find(a=>this.S(a,t));if(!r){let a=this.t.getActionLocation(e),o=this.e("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.r(r,t,i)}N(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.S(o.subBranch,t));if(r.length===0){let o=this.t.getActionLocation(e),s=this.e("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.t.getActionLocation(e),f=this.e(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.D(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.r(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}V(e,t,i){let r=e.value.filter(o=>this.S(o,t));if(r.length===0){let o=this.t.getActionLocation(e),s=this.e("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.t.getActionLocation(e),c=this.e(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.D(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.r(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}S(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.t.getBranchLocation(e),r=this.e(`${i}`,t.executionHistory);throw new l(r,n)}else return true}C(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}O(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.s.get(e);return n||(n=new Set,this.s.set(e,n)),n.has(i)?false:(n.add(i),true)}D(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}e(e,t){return `${e}
17
+ `)(this.šap);}catch(i){return this.šaf(i),""}return Reflect.defineMetadata(d,false,this.functions),t}duplicate(){let e=JsonHelper.deepCopy(this.variables),t=JsonHelper.deepCopy(this.functions);return new h(t,e,this.globalNameSpace)}šan(e,t){b.forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`);if(Object.hasOwn(e,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a function.`)}),Object.getOwnPropertyNames(e).forEach(i=>{if(Object.hasOwn(t,i))throw new Error(`JSEngine: Reserved word "${i}" cannot be used as a variable.`)});}šaf(e){throw e instanceof Error?(e.message=e.message.replace(/^.*Error: /,""),e):new Error(`${e}`.replace(/^.*Error: /,""))}};var E="actionsBeforeTesting";function J(){return function(h,e,t){let i=t.value;p.setAsJSEngineFunction(i),t.value=function(...n){if(Reflect.getOwnMetadata(E,this))return i.apply(this,n);throw new Error(`"${String(e)}(...)" can only be called at "actions before testing".`)},MetaDataHelper.carryMetaDataOfFunction(i,t.value);}}var l=class extends Error{constructor(e,t){super(e),this.location=t;}},S=class extends Error{constructor(e,t){super(e),this.actionIndex=t;}},x=class extends Error{constructor(e,t,i){super(e),this.actionIndex=t,this.location=i;}};var v=class{},C=(i=>(i[i.Idle=1]="Idle",i[i.Running=2]="Running",i[i.WaitingForPlayerChoice=3]="WaitingForPlayerChoice",i))(C||{}),B=class{constructor(e,t,i,n){this.šal=new Stack;this.šz=false;this.šao(t),this.šz=!!n?.manualTestingMode,this.šak=e,this.šx=new p(t,i);}get variables(){return this.šx.variables}get state(){return this.šab?3:this.šai?2:1}get šai(){return !this.šal.isEmpty}start(e){if(!this.šal.isEmpty)throw new Error("ScriptEngine: Existing script haven't been concluded yet.");this.šy(e);}next(){if(this.šal.isEmpty)throw new Error("ScriptEngine: There is no active script to iterate.");if(this.state!==2)throw new Error(`ScriptEngine: The engine is not in running state. Next action cannot be executed. State: ${C[this.state]}`);let e=this.šal.pop();this.ši(e);}playerChoice(e){if(!this.šab)throw new Error("ScriptEngine: No player branching choices available.");let t=this.šab[e];if(!t)throw new Error("ScriptEngine: Invalid player choice.");this.šv(t),this.šab=void 0,this.šai&&this.next();}šy(e){let t=this.šak[e];if(!t)throw new Error(`ScriptEngine: Script definition not found: ${e}`);this.šv(t.actions);}ši(e){switch(e.type){case "command":this.šx.execute(e.value);break;case "dialog":{let t=e.value;this.šx.functions.onDialog(this.šx.string(t.text),t.speaker);break}case "jumpTo":{this.šy(e.value),this.šai&&this.next();break}case "branchByCondition":{let i=e.value.find(n=>n.condition?this.šx.boolean(n.condition):true);if(i)this.šv(i.actions);else throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByCondition action.");this.šai&&this.next();break}case "branchByChance":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByChance action.");if(this.šz)this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map((i,n)=>i.label??`Anonymous ${n}`));else {let i=Random.pickRandomElementWithWeight(t.map(n=>({value:n.actions,weight:Comparator.isString(n.weight)?this.šx.number(n.weight):n.weight})));this.šv(i),this.šai&&this.next();}break}case "branchByPlayerChoice":{let t=e.value.filter(i=>i.condition?this.šx.boolean(i.condition):true);if(t.length===0)throw new Error("ScriptEngine: No branch is fulfilled the condition in branchByPlayerChoice action.");this.šab=t.map(i=>i.actions),this.šx.functions.onPlayerChoice(t.map(i=>this.šx.string(i.text)));break}default:throw new Error(`ScriptEngine: Unknown action type: ${e.type}`)}}šv(e){this.šal.add(...e);}šao(e){if(p.isJSEngineFunction(e.onPlayerChoice))throw new Error("ScriptEngine: onPlayerChoice function shall not be decorated as a JSEngineFunction.")}};var y=class{static isValid(e,t){try{return this.validate(e,t),!0}catch{return false}}static validate(e,t){let i=[],n=0;for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1)break;let a=e[r+1]==="/",o=r+(a?2:1),s=e.indexOf(">",r);if(s===-1)throw new Error(`Malformed tag at position ${r}: missing closing bracket '>'`);let c=e.substring(o,s);if(c.trim()==="")throw new Error(`Empty tag name found at position ${r}`);if(/\s/.test(c))throw new Error(`Invalid tag name '${c}' at position ${r}: contains whitespace`);if(t&&!a&&!t.has(c))throw new Error(`Unknown tag '${c}' at position ${r}. Only these tags are allowed: ${[...t].join(", ")}`);if(a){if(i.length===0)throw new Error(`Closing tag '${c}' at position ${r} has no matching opening tag`);let u=i.pop();if(u!==c)throw new Error(`Mismatched tags: expected closing tag for '${u}', but found '${c}' at position ${r}`)}else i.push(c);n=s+1;}if(i.length>0)throw new Error(`Unclosed tag${i.length>1?"s":""}: ${i.join(", ")}`)}};var D=class{static process(e,t){y.validate(e,t);let i=this.šae(e,[]);return this.šf(i)}static šf(e){if(e.length<=1)return e;let t=[],i=e[0];for(let n=1;n<e.length;n++){let r=e[n];i.tags.length===r.tags.length&&i.tags.every((o,s)=>o===r.tags[s])?i={text:i.text+r.text,tags:i.tags}:(t.push(i),i=r);}return t.push(i),t}static šae(e,t){let i=[],n=0;if(e==="")return [{text:"",tags:[]}];for(;n<e.length;){let r=e.indexOf("<",n);if(r===-1){if(n<e.length){let g=e.substring(n);g.length>0&&i.push({text:g,tags:[...t]});}break}if(r>n){let g=e.substring(n,r);g.length>0&&i.push({text:g,tags:[...t]});}let a=e.indexOf(">",r),o=e.substring(r+1,a),s=this.šs(e,o,a+1),c=e.substring(a+1,s);if(c.length===0){n=s+o.length+3;continue}let u=[...t,o],f=this.šae(c,u);i.push(...f),n=s+o.length+3;}return i}static šs(e,t,i){let n=i,r=1;for(;n<e.length&&r>0;){let a=e.indexOf("<"+t,n),o=e.indexOf("</"+t+">",n);if(a!==-1&&a<o)r++,n=a+1;else {if(r--,r===0)return o;n=o+1;}}return e.length}};var m=class h{constructor(){this.šh=[];}addEntry(e){return this.šh.push(e),this}toString(){return this.šh.join(`
18
+ `)}duplicate(){let e=new h;return e.šh=[...this.šh],e}};var w=class{constructor(e){this.šu=new Map;this.šam=new Set;this.šc=new Map;this.ša=new Map;e.forEach(t=>{this.šu.set(t.id,t),this.šag(t,1,t.id,void 0,[]);});}getScript(e){let t=this.šu.get(e);if(!t)throw new Error(`Script with id ${e} not found.`);return t}getRootBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:[]}}getBranchLocation(e){let t=this.šc.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}getActionLocation(e){let t=this.ša.get(e);if(!t)throw new Error("Branch not found.");return {scriptId:t.rootScriptId,path:t.path}}setVisited(e){this.šam.delete(e);}getUnvisitedBranchLocations(){return Array.from(this.šam).map(e=>this.šc.get(e)).filter(e=>!e.parentBranchInfo||!this.šam.has(e.parentBranchInfo.branch)).map(e=>({scriptId:e.rootScriptId,path:e.path}))}šag(e,t,i,n,r){let a={branch:e,type:t,parentBranchInfo:n,rootScriptId:i,path:r};this.šam.add(e),this.šc.set(e,a),this.še(e.actions,i,a,r);}še(e,t,i,n){e.forEach((r,a)=>{let o=[...n,"actions",`${a}`],s={action:r,rootScriptId:t,path:o};this.ša.set(r,s),r.type==="branchByCondition"?r.value.forEach((u,f)=>{let g=[...o,"branchByCondition",`${f}`];this.šag(u,2,t,i,g);}):r.type==="branchByPlayerChoice"?r.value.forEach((u,f)=>{let g=[...o,"branchByPlayerChoice",`${f}`];this.šag(u,3,t,i,g);}):r.type==="branchByChance"&&r.value.forEach((u,f)=>{let g=[...o,"branchByChance",`${f}`];this.šag(u,4,t,i,g);});});}};var T=class{},M=100,A=class{constructor(e,t={prscriptTypeChanges:true,richTextTags:void 0}){this.šak=e;this.šaa=t;this.šaq=new Map;this.šaj=new w(this.šak),this.šaa.prscriptTypeChanges&&(this.št=new Map);}simulateScript(e,t,i){this.šaa.prscriptTypeChanges&&(t.globalNameSpace=this.št);let r=[{executionHistory:new m,stack:new Stack,jsEngine:t,depth:0,lastExecutedAction:void 0}];this.šad(i,r),this.šaq.clear(),this.šr(e,r),this.šaq.clear();}getUnvisitedBranchLocations(){return this.šaj.getUnvisitedBranchLocations()}šad(e,t){e.forEach((i,n)=>{switch(i.type){case "command":this.šj(i,t,n);break;case "runScript":this.šb(i,t,n);break;default:throw new Error(`Unknown action before testing type: ${i.type}`)}});}šj(e,t,i){try{t.forEach(n=>{Reflect.defineMetadata(E,!0,n.jsEngine.functions),n.jsEngine.execute(e.value);});}catch(n){let r=this.šac(`${n}`,t[0].executionHistory);throw new S(r,i)}}šb(e,t,i){let n=[],r=this.šaj.getScript(e.value);if(!r)throw new Error(`Target script is not found: ${e.value}`);try{this.šaq.clear(),t.forEach(o=>{o.executionHistory.addEntry(`Branching out from script: ${e.value}`),o.depth=0,Reflect.defineMetadata(E,!1,o.jsEngine.functions);let s=this.šp(r,o,!1);if(!s.exitFound){let c=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.value}`,o.executionHistory);throw new l(c,this.šaj.getRootBranchLocation(r))}s.allEndings.forEach(c=>{n.push(c);});});let a=this.šah(n);t.length=0,t.push(...a),t.forEach(o=>{if(o.executionHistory.addEntry(`${a.length} unique ending${a.length>1?"s":""} generated.`).addEntry(""),o.lastExecutedAction)try{o.jsEngine.functions.onScriptBranchingEnd();}catch(s){let c=this.šaj.getActionLocation(o.lastExecutedAction),u=this.šac(`${s}`,o.executionHistory);throw new l(u,c)}else throw new Error("Script execution is ended without executing any command!")});}catch(a){throw a instanceof l?new x(`${a.message}`,i,a.location):a}}šr(e,t){t.forEach(i=>{i.executionHistory.addEntry(`Running script: ${e.id}`),i.depth=0,Reflect.defineMetadata(E,false,i.jsEngine.functions);let n=this.šp(e,i,true);if(!n.exitFound){let r=this.šac(`All possible paths are creating infinite loops, no exit found while executing: ${e.id}`,i.executionHistory);throw new l(r,this.šaj.getRootBranchLocation(e))}n.allEndings.forEach(r=>{if(r.lastExecutedAction)try{r.jsEngine.functions.onEnd();}catch(a){let o=this.šaj.getActionLocation(r.lastExecutedAction),s=this.šac(`${a}`,r.executionHistory);throw new l(s,o)}else throw new Error("Script execution is ended without executing any command!")});});}šp(e,t,i){if(!this.šw(e,t))return {allEndings:[],exitFound:false};let n=[],r=true;if(i&&this.šaj.setVisited(e),t.depth++,t.depth>M){let a=this.šac(`Maximum depth "${M}" is reached. Try to reduce the script branching tree or check infinite loops with trivial state changes.`,t.executionHistory);throw new l(a,this.šaj.getRootBranchLocation(e))}for(t.stack.add(...e.actions);!t.stack.isEmpty;){let a=t.stack.pop(),o=this.ši(a,t,i);t.stack.isEmpty&&(n.push(...o.allEndings),r=o.exitFound&&r);}return n=n.length>0?this.šah(n):[],{allEndings:n,exitFound:r}}ši(e,t,i){switch(t.lastExecutedAction=e,e.type){case "command":return this.šn(e,t);case "dialog":return this.šq(e,t);case "jumpTo":return this.šo(e,t,i);case "branchByCondition":return this.šl(e,t,i);case "branchByChance":return this.šk(e,t,i);case "branchByPlayerChoice":return this.šm(e,t,i);default:throw new Error(`Unknown action type: ${e.type}`)}}šn(e,t){try{t.jsEngine.execute(e.value);}catch(i){let n=this.šaj.getActionLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}return {allEndings:[t],exitFound:true}}šq(e,t){let i=e.value,n;try{n=t.jsEngine.string(i.text),this.šaa.richTextTags&&y.validate(n,this.šaa.richTextTags),t.jsEngine.functions.onDialog(n,i.speaker);}catch(r){let a=this.šaj.getActionLocation(e),o=this.šac(`${r}`,t.executionHistory);throw new l(o,a)}return {allEndings:[t],exitFound:true}}šo(e,t,i){let n=this.šaj.getScript(e.value);return t.executionHistory.addEntry(`Jumping to script: ${e.value}`),this.šp(n,t,i)}šl(e,t,i){let r=e.value.find(a=>this.šd(a,t));if(!r){let a=this.šaj.getActionLocation(e),o=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(o,a)}return t.executionHistory.addEntry(`Branching by condition: ${r.condition?r.condition:"default"}`),this.šp(r,t,i)}šk(e,t,i){let r=e.value.map((o,s)=>({index:s,subBranch:o})).filter(o=>this.šd(o.subBranch,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}let a=r.map(o=>{try{let c=Comparator.isString(o.subBranch.weight)?t.jsEngine.number(o.subBranch.weight):o.subBranch.weight;if(!Comparator.isNumber(c)||c<0)throw new Error(`Weight of branch ${o.index} is not a valid number: "${o.subBranch.weight}" -> ${c}.`)}catch(c){let u=this.šaj.getActionLocation(e),f=this.šac(`${c}`,t.executionHistory);throw new l(f,u)}let s=this.šg(t);return s.executionHistory.addEntry(`Branching by chance: ${o.index}`),this.šp(o.subBranch,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šm(e,t,i){let r=e.value.filter(o=>this.šd(o,t));if(r.length===0){let o=this.šaj.getActionLocation(e),s=this.šac("No option is fulfilled the condition during branching.",t.executionHistory);throw new l(s,o)}try{t.jsEngine.functions.onPlayerChoice(r.map(o=>t.jsEngine.string(o.text)));}catch(o){let s=this.šaj.getActionLocation(e),c=this.šac(`${o}`,t.executionHistory);throw new l(c,s)}let a=r.map(o=>{let s=this.šg(t);return s.executionHistory.addEntry(`Player choice: ${o.text}`),this.šp(o,s,i)});return {allEndings:a.flatMap(o=>o.allEndings),exitFound:a.some(o=>o.exitFound)}}šd(e,t){if(e.condition)try{return t.jsEngine.boolean(e.condition)}catch(i){let n=this.šaj.getBranchLocation(e),r=this.šac(`${i}`,t.executionHistory);throw new l(r,n)}else return true}šah(e){let t=[];return e.map(n=>({ending:n,variablesString:JSON.stringify(n.jsEngine.variables),functionsString:JSON.stringify(n.jsEngine.functions)})).forEach(n=>{t.every(a=>n.functionsString!==a.functionsString||n.variablesString!==a.variablesString)&&t.push({ending:n.ending,variablesString:n.variablesString,functionsString:n.functionsString});}),t.map(n=>n.ending)}šw(e,t){let i=JSON.stringify(t.jsEngine.variables)+JSON.stringify(t.jsEngine.functions),n=this.šaq.get(e);return n||(n=new Set,this.šaq.set(e,n)),n.has(i)?false:(n.add(i),true)}šg(e){return {executionHistory:e.executionHistory.duplicate(),stack:e.stack.duplicate(),jsEngine:e.jsEngine.duplicate(),depth:e.depth,lastExecutedAction:e.lastExecutedAction}}šac(e,t){return `${e}
19
19
 
20
20
  ----Execution history----
21
21
  ${t.toString()}`}};export{J as ActionBeforeTesting,S as ActionsBeforeTestingError,x as BranchingBeforeTestingError,p as JSEngine,P as JSEngineFunction,D as RichTextSeparator,y as RichTextValidator,B as ScriptEngine,v as ScriptEngineFunctions,T as ScriptEngineSimulatorFunctions,C as ScriptEngineState,A as ScriptTestSimulator,l as SimulationError};
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
- "name": "script-engine-lib",
3
- "version": "1.0.0-rc1",
4
- "description": "Script Engine",
5
- "main": "src/index.ts",
2
+ "name": "script-engine-lib",
3
+ "version": "1.0.0-rc3",
4
+ "description": "Script Engine",
5
+ "main": "dist/index.js",
6
6
  "publishConfig": {
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/index.mjs",
@@ -17,16 +17,16 @@
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
- "repository": {
21
- "type": "git",
22
- "url": "git+https://github.com/sefabaser/script-engine-lib.git"
23
- },
24
- "author": "sefabaser",
25
- "license": "UNLICENSED",
26
- "bugs": {
27
- "url": "https://github.com/sefabaser/script-engine-lib/issues"
28
- },
29
- "homepage": "https://github.com/sefabaser/script-engine-lib#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/sefabaser/script-engine-lib.git"
23
+ },
24
+ "author": "sefabaser",
25
+ "license": "UNLICENSED",
26
+ "bugs": {
27
+ "url": "https://github.com/sefabaser/script-engine-lib/issues"
28
+ },
29
+ "homepage": "https://github.com/sefabaser/script-engine-lib#readme",
30
30
  "scripts": {
31
31
  "clean-install": "cls && rm -rf node_modules && rm -rf package-lock.json && npm run i",
32
32
  "i": "npm cache clean --force && npm install",
@@ -34,23 +34,31 @@
34
34
  "test": "vitest run --mode=quick",
35
35
  "test-full": "vitest run",
36
36
  "start": "npm run build && node dist",
37
- "build": "tsup && npx ts-node scripts/remove-private-properties-from-build.ts && npm run restart-vitest",
37
+ "build": "npx ts-node scripts/generate-mangle-cache.ts && tsup && npx ts-node scripts/remove-private-properties-from-build.ts && npm run restart-vitest",
38
38
  "restart-vitest": "touch vitest.config.ts",
39
39
  "lint": "biome check . --write --max-diagnostics=1",
40
40
  "deploy": "npm run test-full && npm run build && npm publish",
41
41
  "prepack": "cp package.json package.json.bak && npx ts-node scripts/prepare-package-json.ts",
42
42
  "postpack": "mv package.json.bak package.json"
43
43
  },
44
- "peerDependencies": {
45
- "helpers-lib": "^1.14.0",
44
+ "peerDependencies": {
45
+ "helpers-lib": "^2.0.0-rc3",
46
46
  "reflect-metadata": "^0.2.2"
47
- },
48
- "devDependencies": {
49
- "@biomejs/biome": "2.2.4",
47
+ },
48
+ "devDependencies": {
49
+ "@biomejs/biome": "2.2.4",
50
50
  "@types/node": "20.12.11",
51
51
  "tsup": "8.5.1",
52
- "typescript": "5.3.3",
52
+ "typescript": "5.3.3",
53
53
  "vitest": "3.2.4",
54
54
  "joi": "17.11.1"
55
- }
56
- }
55
+ },
56
+ "types": "dist/index.d.ts",
57
+ "module": "dist/index.mjs",
58
+ "exports": {
59
+ ".": {
60
+ "import": "./dist/index.mjs",
61
+ "require": "./dist/index.js"
62
+ }
63
+ }
64
+ }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- export { ActionBeforeTesting } from './decorators/action-before-testing';
2
- export * from './interfaces';
3
- export { JSEngine, JSEngineFunction } from './js-engine/js-engine';
4
- export { ScriptEngine, ScriptEngineFunctions, ScriptEngineOptions, ScriptEngineState } from './script-engine/script-engine';
5
- export {
6
- ScriptEngineSimulatorFunctions,
7
- ScriptTestSimulator,
8
- ScriptTestSimulatorOptions
9
- } from './simulator/script-test-simulator';
10
- export * from './structured-text';