script-engine-lib 0.4.3 → 1.0.0-rc0

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.
Files changed (62) hide show
  1. package/dist/index.d.mts +191 -0
  2. package/dist/index.d.ts +191 -5
  3. package/dist/index.js +21 -29
  4. package/dist/index.mjs +21 -0
  5. package/package.json +56 -52
  6. package/src/index.ts +10 -0
  7. package/dist/decorators/action-before-testing.d.ts +0 -2
  8. package/dist/decorators/action-before-testing.js +0 -23
  9. package/dist/decorators/action-before-testing.test.d.ts +0 -1
  10. package/dist/decorators/action-before-testing.test.js +0 -82
  11. package/dist/engine/script-engine.d.ts +0 -35
  12. package/dist/engine/script-engine.js +0 -175
  13. package/dist/engine/script-engine.test.d.ts +0 -1
  14. package/dist/engine/script-engine.test.js +0 -1277
  15. package/dist/engine/story-engine.d.ts +0 -35
  16. package/dist/engine/story-engine.js +0 -175
  17. package/dist/engine/story-engine.test.d.ts +0 -1
  18. package/dist/engine/story-engine.test.js +0 -1277
  19. package/dist/interfaces.d.ts +0 -51
  20. package/dist/interfaces.js +0 -27
  21. package/dist/simulator/decorators.d.ts +0 -2
  22. package/dist/simulator/decorators.js +0 -23
  23. package/dist/simulator/decorators.test.d.ts +0 -1
  24. package/dist/simulator/decorators.test.js +0 -81
  25. package/dist/simulator/event-branches-map.d.ts +0 -16
  26. package/dist/simulator/event-branches-map.js +0 -114
  27. package/dist/simulator/event-branches-map.test.d.ts +0 -1
  28. package/dist/simulator/event-branches-map.test.js +0 -120
  29. package/dist/simulator/event-test-simulator.d.ts +0 -42
  30. package/dist/simulator/event-test-simulator.js +0 -373
  31. package/dist/simulator/event-test-simulator.test.d.ts +0 -1
  32. package/dist/simulator/event-test-simulator.test.js +0 -1960
  33. package/dist/simulator/execution-history.d.ts +0 -6
  34. package/dist/simulator/execution-history.js +0 -22
  35. package/dist/simulator/helpers/event-branches-map.d.ts +0 -16
  36. package/dist/simulator/helpers/event-branches-map.js +0 -114
  37. package/dist/simulator/helpers/event-branches-map.test.d.ts +0 -1
  38. package/dist/simulator/helpers/event-branches-map.test.js +0 -120
  39. package/dist/simulator/helpers/execution-history.d.ts +0 -6
  40. package/dist/simulator/helpers/execution-history.js +0 -22
  41. package/dist/simulator/helpers/script-branches-map.d.ts +0 -16
  42. package/dist/simulator/helpers/script-branches-map.js +0 -114
  43. package/dist/simulator/helpers/script-branches-map.test.d.ts +0 -1
  44. package/dist/simulator/helpers/script-branches-map.test.js +0 -120
  45. package/dist/simulator/script-test-simulator.d.ts +0 -42
  46. package/dist/simulator/script-test-simulator.js +0 -373
  47. package/dist/simulator/script-test-simulator.test.d.ts +0 -1
  48. package/dist/simulator/script-test-simulator.test.js +0 -2013
  49. package/dist/structured-text/index.d.ts +0 -9
  50. package/dist/structured-text/index.js +0 -8
  51. package/dist/structured-text/rich-text-separator.d.ts +0 -48
  52. package/dist/structured-text/rich-text-separator.js +0 -146
  53. package/dist/structured-text/rich-text-separator.test.d.ts +0 -1
  54. package/dist/structured-text/rich-text-separator.test.js +0 -166
  55. package/dist/structured-text/rich-text-validator.d.ts +0 -19
  56. package/dist/structured-text/rich-text-validator.js +0 -73
  57. package/dist/structured-text/rich-text-validator.test.d.ts +0 -1
  58. package/dist/structured-text/rich-text-validator.test.js +0 -59
  59. package/dist/test.spec.d.ts +0 -0
  60. package/dist/test.spec.js +0 -7
  61. package/dist/test.test.d.ts +0 -1
  62. package/dist/test.test.js +0 -9
@@ -0,0 +1,191 @@
1
+ declare function ActionBeforeTesting(): (_: unknown, propertyKey: string, descriptor: PropertyDescriptor) => void;
2
+
3
+ type ScriptActionType = 'command' | 'dialog' | 'jumpTo' | 'branchByCondition' | 'branchByPlayerChoice' | 'branchByChance';
4
+ interface DialogItemDefinition {
5
+ readonly text: string;
6
+ readonly speaker: string;
7
+ }
8
+ interface BranchByConditionItemDefinition {
9
+ readonly condition?: string;
10
+ readonly actions: readonly ScriptActionDefinition[];
11
+ }
12
+ interface BranchByPlayerChoiceItemDefinition {
13
+ readonly text: string;
14
+ readonly condition?: string;
15
+ readonly actions: readonly ScriptActionDefinition[];
16
+ }
17
+ interface BranchByChanceItemDefinition {
18
+ readonly label?: string;
19
+ readonly weight: number | string;
20
+ readonly condition?: string;
21
+ readonly actions: readonly ScriptActionDefinition[];
22
+ }
23
+ interface ScriptActionDefinition {
24
+ readonly type: ScriptActionType;
25
+ readonly value: string | DialogItemDefinition | readonly BranchByConditionItemDefinition[] | readonly BranchByPlayerChoiceItemDefinition[] | readonly BranchByChanceItemDefinition[];
26
+ }
27
+ interface ScriptDefinition {
28
+ readonly id: string;
29
+ readonly actions: readonly ScriptActionDefinition[];
30
+ }
31
+ type VisitableScriptBranchDefinition = ScriptDefinition | BranchByConditionItemDefinition | BranchByPlayerChoiceItemDefinition | BranchByChanceItemDefinition;
32
+ interface ExecutionLocation {
33
+ readonly scriptId: string;
34
+ readonly path: readonly string[];
35
+ }
36
+ type ActionBeforeTestType = 'command' | 'runScript';
37
+ interface ScriptTestActionDefinition {
38
+ readonly type: ActionBeforeTestType;
39
+ readonly value: string;
40
+ }
41
+ declare class SimulationError extends Error {
42
+ readonly location: ExecutionLocation;
43
+ constructor(message: string, location: ExecutionLocation);
44
+ }
45
+ declare class ActionsBeforeTestingError extends Error {
46
+ readonly actionIndex: number;
47
+ constructor(message: string, actionIndex: number);
48
+ }
49
+ declare class BranchingBeforeTestingError extends Error {
50
+ readonly actionIndex: number;
51
+ readonly location: ExecutionLocation;
52
+ constructor(message: string, actionIndex: number, location: ExecutionLocation);
53
+ }
54
+
55
+ declare function JSEngineFunction(): (_: unknown, propertyKey: string, descriptor: PropertyDescriptor) => void;
56
+ declare class JSEngine<FunctionsType extends object> {
57
+ static isJSEngineFunction(fn: (...args: any[]) => any): boolean;
58
+ static setAsJSEngineFunction(fn: (...args: any[]) => any): void;
59
+ readonly variables: {
60
+ [key: string]: any;
61
+ };
62
+ readonly functions: FunctionsType;
63
+ globalNameSpace: Map<string, unknown> | undefined;
64
+ constructor(functions: FunctionsType, variables: {
65
+ [key: string]: any;
66
+ }, globalNameSpace?: Map<string, unknown>);
67
+ execute(code: string): void;
68
+ boolean(expression: string): boolean;
69
+ number(expression: string): number;
70
+ string(expression: string): string;
71
+ duplicate(): JSEngine<FunctionsType>;
72
+ }
73
+
74
+ interface ScriptEngineOptions {
75
+ readonly manualTestingMode?: boolean;
76
+ }
77
+ declare abstract class ScriptEngineFunctions {
78
+ abstract onPlayerChoice(choices: readonly string[]): void;
79
+ abstract onDialog(text: string, speaker: string): void;
80
+ }
81
+ declare enum ScriptEngineState {
82
+ Idle = 1,
83
+ Running = 2,
84
+ WaitingForPlayerChoice = 3
85
+ }
86
+ declare class ScriptEngine<ScriptFunctions extends ScriptEngineFunctions> {
87
+ get variables(): {
88
+ [key: string]: any;
89
+ };
90
+ get state(): ScriptEngineState;
91
+ constructor(scriptDefinitions: Readonly<Record<string, ScriptDefinition>>, functions: ScriptFunctions, variables: {
92
+ [key: string]: any;
93
+ }, options?: ScriptEngineOptions);
94
+ start(scriptId: string): void;
95
+ next(): void;
96
+ playerChoice(choice: number): void;
97
+ }
98
+
99
+ declare abstract class ScriptEngineSimulatorFunctions {
100
+ abstract onDialog(text: string, speaker: string): void;
101
+ abstract onPlayerChoice(choices: readonly string[]): void;
102
+ abstract onEnd(): void;
103
+ abstract onScriptBranchingEnd(): void;
104
+ }
105
+ interface ScriptTestSimulatorOptions {
106
+ readonly prscriptTypeChanges?: boolean;
107
+ readonly richTextTags?: Set<string>;
108
+ }
109
+ declare class ScriptTestSimulator<T extends ScriptEngineSimulatorFunctions> {
110
+ constructor(_scriptDefinitions: ScriptDefinition[], _options?: ScriptTestSimulatorOptions);
111
+ /**
112
+ * @throws SimulationError, ActionsBeforeTestingError, BranchingBeforeTestingError
113
+ */
114
+ simulateScript(scriptDefinition: ScriptDefinition, jsEngine: JSEngine<T>, actionsBeforeTesting: readonly ScriptTestActionDefinition[]): void;
115
+ getUnvisitedBranchLocations(): ExecutionLocation[];
116
+ }
117
+
118
+ /**
119
+ * Class to separate text with XML tags into structured segments
120
+ */
121
+ declare class RichTextSeparator {
122
+ /**
123
+ * Processes a string with XML tags and returns structured text segments
124
+ * @param input The input string containing XML tags Sample: "text <outer>with <inner>nested</inner> content</outer>"
125
+ * @param tags Optional set of allowed tags. If provided, all tags must be included in this set
126
+ * @returns Array of TextSegment objects Sample: [
127
+ {
128
+ text: 'text ',
129
+ tags: []
130
+ },
131
+ {
132
+ text: 'with ',
133
+ tags: ['outer']
134
+ },
135
+ {
136
+ text: 'nested',
137
+ tags: ['outer', 'inner']
138
+ },
139
+ {
140
+ text: ' content',
141
+ tags: ['outer']
142
+ }
143
+ ]
144
+ * @throws Error if the input contains invalid tag structure
145
+ */
146
+ static process(input: string, tags?: Set<string>): TextSegment[];
147
+ /**
148
+ * Combines adjacent segments that have the same tags
149
+ * @param segments Array of text segments
150
+ * @returns Array of combined text segments
151
+ */
152
+ /**
153
+ * Processes text with a current stack of tags
154
+ * @param input Text to process
155
+ * @param currentTags Current stack of tags
156
+ * @returns Array of flat TextSegment objects
157
+ */
158
+ /**
159
+ * Finds the position of the matching closing tag, taking into account nesting
160
+ */
161
+ }
162
+
163
+ /**
164
+ * Class responsible for validating XML-like tags in text
165
+ */
166
+ declare class RichTextValidator {
167
+ /**
168
+ * Checks if the input has valid tag structure
169
+ * @param input The input string containing XML-like tags
170
+ * @param tags Optional set of allowed tags. If provided, all tags must be included in this set
171
+ * @returns true if all tags are valid, false otherwise
172
+ */
173
+ static isValid(input: string, tags?: Set<string>): boolean;
174
+ /**
175
+ * Validates if all tags in the text are properly opened and closed
176
+ * @param input The input string containing XML-like tags
177
+ * @param tags Optional set of allowed tags. If provided, all tags must be included in this set
178
+ * @throws Error if tags are not properly formed with details about the issue
179
+ */
180
+ static validate(input: string, tags?: Set<string>): void;
181
+ }
182
+
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
+ 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
@@ -1,5 +1,191 @@
1
- export { ActionBeforeTesting } from './decorators/action-before-testing';
2
- export { ScriptEngine, ScriptEngineFunctions, ScriptEngineOptions, ScriptEngineState } from './engine/script-engine';
3
- export * from './interfaces';
4
- export { ScriptEngineSimulatorFunctions, ScriptTestSimulator, ScriptTestSimulatorOptions } from './simulator/script-test-simulator';
5
- export * from './structured-text';
1
+ declare function ActionBeforeTesting(): (_: unknown, propertyKey: string, descriptor: PropertyDescriptor) => void;
2
+
3
+ type ScriptActionType = 'command' | 'dialog' | 'jumpTo' | 'branchByCondition' | 'branchByPlayerChoice' | 'branchByChance';
4
+ interface DialogItemDefinition {
5
+ readonly text: string;
6
+ readonly speaker: string;
7
+ }
8
+ interface BranchByConditionItemDefinition {
9
+ readonly condition?: string;
10
+ readonly actions: readonly ScriptActionDefinition[];
11
+ }
12
+ interface BranchByPlayerChoiceItemDefinition {
13
+ readonly text: string;
14
+ readonly condition?: string;
15
+ readonly actions: readonly ScriptActionDefinition[];
16
+ }
17
+ interface BranchByChanceItemDefinition {
18
+ readonly label?: string;
19
+ readonly weight: number | string;
20
+ readonly condition?: string;
21
+ readonly actions: readonly ScriptActionDefinition[];
22
+ }
23
+ interface ScriptActionDefinition {
24
+ readonly type: ScriptActionType;
25
+ readonly value: string | DialogItemDefinition | readonly BranchByConditionItemDefinition[] | readonly BranchByPlayerChoiceItemDefinition[] | readonly BranchByChanceItemDefinition[];
26
+ }
27
+ interface ScriptDefinition {
28
+ readonly id: string;
29
+ readonly actions: readonly ScriptActionDefinition[];
30
+ }
31
+ type VisitableScriptBranchDefinition = ScriptDefinition | BranchByConditionItemDefinition | BranchByPlayerChoiceItemDefinition | BranchByChanceItemDefinition;
32
+ interface ExecutionLocation {
33
+ readonly scriptId: string;
34
+ readonly path: readonly string[];
35
+ }
36
+ type ActionBeforeTestType = 'command' | 'runScript';
37
+ interface ScriptTestActionDefinition {
38
+ readonly type: ActionBeforeTestType;
39
+ readonly value: string;
40
+ }
41
+ declare class SimulationError extends Error {
42
+ readonly location: ExecutionLocation;
43
+ constructor(message: string, location: ExecutionLocation);
44
+ }
45
+ declare class ActionsBeforeTestingError extends Error {
46
+ readonly actionIndex: number;
47
+ constructor(message: string, actionIndex: number);
48
+ }
49
+ declare class BranchingBeforeTestingError extends Error {
50
+ readonly actionIndex: number;
51
+ readonly location: ExecutionLocation;
52
+ constructor(message: string, actionIndex: number, location: ExecutionLocation);
53
+ }
54
+
55
+ declare function JSEngineFunction(): (_: unknown, propertyKey: string, descriptor: PropertyDescriptor) => void;
56
+ declare class JSEngine<FunctionsType extends object> {
57
+ static isJSEngineFunction(fn: (...args: any[]) => any): boolean;
58
+ static setAsJSEngineFunction(fn: (...args: any[]) => any): void;
59
+ readonly variables: {
60
+ [key: string]: any;
61
+ };
62
+ readonly functions: FunctionsType;
63
+ globalNameSpace: Map<string, unknown> | undefined;
64
+ constructor(functions: FunctionsType, variables: {
65
+ [key: string]: any;
66
+ }, globalNameSpace?: Map<string, unknown>);
67
+ execute(code: string): void;
68
+ boolean(expression: string): boolean;
69
+ number(expression: string): number;
70
+ string(expression: string): string;
71
+ duplicate(): JSEngine<FunctionsType>;
72
+ }
73
+
74
+ interface ScriptEngineOptions {
75
+ readonly manualTestingMode?: boolean;
76
+ }
77
+ declare abstract class ScriptEngineFunctions {
78
+ abstract onPlayerChoice(choices: readonly string[]): void;
79
+ abstract onDialog(text: string, speaker: string): void;
80
+ }
81
+ declare enum ScriptEngineState {
82
+ Idle = 1,
83
+ Running = 2,
84
+ WaitingForPlayerChoice = 3
85
+ }
86
+ declare class ScriptEngine<ScriptFunctions extends ScriptEngineFunctions> {
87
+ get variables(): {
88
+ [key: string]: any;
89
+ };
90
+ get state(): ScriptEngineState;
91
+ constructor(scriptDefinitions: Readonly<Record<string, ScriptDefinition>>, functions: ScriptFunctions, variables: {
92
+ [key: string]: any;
93
+ }, options?: ScriptEngineOptions);
94
+ start(scriptId: string): void;
95
+ next(): void;
96
+ playerChoice(choice: number): void;
97
+ }
98
+
99
+ declare abstract class ScriptEngineSimulatorFunctions {
100
+ abstract onDialog(text: string, speaker: string): void;
101
+ abstract onPlayerChoice(choices: readonly string[]): void;
102
+ abstract onEnd(): void;
103
+ abstract onScriptBranchingEnd(): void;
104
+ }
105
+ interface ScriptTestSimulatorOptions {
106
+ readonly prscriptTypeChanges?: boolean;
107
+ readonly richTextTags?: Set<string>;
108
+ }
109
+ declare class ScriptTestSimulator<T extends ScriptEngineSimulatorFunctions> {
110
+ constructor(_scriptDefinitions: ScriptDefinition[], _options?: ScriptTestSimulatorOptions);
111
+ /**
112
+ * @throws SimulationError, ActionsBeforeTestingError, BranchingBeforeTestingError
113
+ */
114
+ simulateScript(scriptDefinition: ScriptDefinition, jsEngine: JSEngine<T>, actionsBeforeTesting: readonly ScriptTestActionDefinition[]): void;
115
+ getUnvisitedBranchLocations(): ExecutionLocation[];
116
+ }
117
+
118
+ /**
119
+ * Class to separate text with XML tags into structured segments
120
+ */
121
+ declare class RichTextSeparator {
122
+ /**
123
+ * Processes a string with XML tags and returns structured text segments
124
+ * @param input The input string containing XML tags Sample: "text <outer>with <inner>nested</inner> content</outer>"
125
+ * @param tags Optional set of allowed tags. If provided, all tags must be included in this set
126
+ * @returns Array of TextSegment objects Sample: [
127
+ {
128
+ text: 'text ',
129
+ tags: []
130
+ },
131
+ {
132
+ text: 'with ',
133
+ tags: ['outer']
134
+ },
135
+ {
136
+ text: 'nested',
137
+ tags: ['outer', 'inner']
138
+ },
139
+ {
140
+ text: ' content',
141
+ tags: ['outer']
142
+ }
143
+ ]
144
+ * @throws Error if the input contains invalid tag structure
145
+ */
146
+ static process(input: string, tags?: Set<string>): TextSegment[];
147
+ /**
148
+ * Combines adjacent segments that have the same tags
149
+ * @param segments Array of text segments
150
+ * @returns Array of combined text segments
151
+ */
152
+ /**
153
+ * Processes text with a current stack of tags
154
+ * @param input Text to process
155
+ * @param currentTags Current stack of tags
156
+ * @returns Array of flat TextSegment objects
157
+ */
158
+ /**
159
+ * Finds the position of the matching closing tag, taking into account nesting
160
+ */
161
+ }
162
+
163
+ /**
164
+ * Class responsible for validating XML-like tags in text
165
+ */
166
+ declare class RichTextValidator {
167
+ /**
168
+ * Checks if the input has valid tag structure
169
+ * @param input The input string containing XML-like tags
170
+ * @param tags Optional set of allowed tags. If provided, all tags must be included in this set
171
+ * @returns true if all tags are valid, false otherwise
172
+ */
173
+ static isValid(input: string, tags?: Set<string>): boolean;
174
+ /**
175
+ * Validates if all tags in the text are properly opened and closed
176
+ * @param input The input string containing XML-like tags
177
+ * @param tags Optional set of allowed tags. If provided, all tags must be included in this set
178
+ * @throws Error if tags are not properly formed with details about the issue
179
+ */
180
+ static validate(input: string, tags?: Set<string>): void;
181
+ }
182
+
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
+ 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,29 +1,21 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.ScriptTestSimulator = exports.ScriptEngineSimulatorFunctions = exports.ScriptEngineState = exports.ScriptEngineFunctions = exports.ScriptEngine = exports.ActionBeforeTesting = void 0;
18
- var action_before_testing_1 = require("./decorators/action-before-testing");
19
- Object.defineProperty(exports, "ActionBeforeTesting", { enumerable: true, get: function () { return action_before_testing_1.ActionBeforeTesting; } });
20
- var script_engine_1 = require("./engine/script-engine");
21
- Object.defineProperty(exports, "ScriptEngine", { enumerable: true, get: function () { return script_engine_1.ScriptEngine; } });
22
- Object.defineProperty(exports, "ScriptEngineFunctions", { enumerable: true, get: function () { return script_engine_1.ScriptEngineFunctions; } });
23
- Object.defineProperty(exports, "ScriptEngineState", { enumerable: true, get: function () { return script_engine_1.ScriptEngineState; } });
24
- __exportStar(require("./interfaces"), exports);
25
- var script_test_simulator_1 = require("./simulator/script-test-simulator");
26
- Object.defineProperty(exports, "ScriptEngineSimulatorFunctions", { enumerable: true, get: function () { return script_test_simulator_1.ScriptEngineSimulatorFunctions; } });
27
- Object.defineProperty(exports, "ScriptTestSimulator", { enumerable: true, get: function () { return script_test_simulator_1.ScriptTestSimulator; } });
28
- __exportStar(require("./structured-text"), exports);
29
- //# sourceMappingURL=index.js.map
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",`
2
+ with (vars) {
3
+ ${e};
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",`
6
+ with (vars) {
7
+ return Boolean(${e});
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",`
10
+ with (vars) {
11
+ return Number(${e});
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",`
14
+ with (vars) {
15
+ return String(\`${e}\`);
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}
19
+
20
+ ----Execution history----
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 ADDED
@@ -0,0 +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",`
2
+ with (vars) {
3
+ ${e};
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",`
6
+ with (vars) {
7
+ return Boolean(${e});
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",`
10
+ with (vars) {
11
+ return Number(${e});
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",`
14
+ with (vars) {
15
+ return String(\`${e}\`);
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}
19
+
20
+ ----Execution history----
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,52 +1,56 @@
1
- {
2
- "name": "script-engine-lib",
3
- "version": "0.4.3",
4
- "description": "Script Engine",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": [
8
- "dist/**/*.js",
9
- "dist/**/*.d.ts"
10
- ],
11
- "repository": {
12
- "type": "git",
13
- "url": "git+https://github.com/sefabaser/script-engine-lib.git"
14
- },
15
- "author": "sefabaser",
16
- "license": "UNLICENSED",
17
- "bugs": {
18
- "url": "https://github.com/sefabaser/script-engine-lib/issues"
19
- },
20
- "prettier": {
21
- "printWidth": 120,
22
- "tabWidth": 2,
23
- "semicolons": true,
24
- "singleQuote": true,
25
- "trailingComma": "none",
26
- "arrowParens": "avoid"
27
- },
28
- "homepage": "https://github.com/sefabaser/script-engine-lib#readme",
29
- "scripts": {
30
- "clean-install": "cls && rm -rf node_modules && rm -rf package-lock.json && npm install",
31
- "pretest": "clear",
32
- "test": "vitest",
33
- "start": "npm run build && node dist",
34
- "build": "tsc",
35
- "lint": "eslint \"src/**/*.ts\" --fix",
36
- "deploy": "vitest run && npm run build && npm publish"
37
- },
38
- "dependencies": {
39
- "helpers-lib": "1.13.10"
40
- },
41
- "devDependencies": {
42
- "@typescript-eslint/eslint-plugin": "6.16.0",
43
- "@typescript-eslint/parser": "6.16.0",
44
- "eslint": "8.56.0",
45
- "eslint-config-prettier": "9.1.0",
46
- "eslint-plugin-import": "2.29.1",
47
- "eslint-plugin-no-null": "1.0.2",
48
- "eslint-plugin-simple-import-sort": "12.1.1",
49
- "typescript": "5.3.3",
50
- "vitest": "1.6.0"
51
- }
52
- }
1
+ {
2
+ "name": "script-engine-lib",
3
+ "version": "1.0.0-rc0",
4
+ "description": "Script Engine",
5
+ "main": "src/index.ts",
6
+ "publishConfig": {
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
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",
30
+ "scripts": {
31
+ "clean-install": "cls && rm -rf node_modules && rm -rf package-lock.json && npm run i",
32
+ "i": "npm cache clean --force && npm install",
33
+ "pretest": "clear",
34
+ "test": "vitest run --mode=quick",
35
+ "test-full": "vitest run",
36
+ "start": "npm run build && node dist",
37
+ "build": "tsup && npx ts-node scripts/remove-private-properties-from-build.ts && npm run restart-vitest",
38
+ "restart-vitest": "touch vitest.config.ts",
39
+ "lint": "biome check . --write --max-diagnostics=1",
40
+ "deploy": "npm run test-full && npm run build && npm publish",
41
+ "prepack": "cp package.json package.json.bak && npx ts-node scripts/prepare-package-json.ts",
42
+ "postpack": "mv package.json.bak package.json"
43
+ },
44
+ "peerDependencies": {
45
+ "helpers-lib": "^1.14.0",
46
+ "reflect-metadata": "^0.2.2"
47
+ },
48
+ "devDependencies": {
49
+ "@biomejs/biome": "2.2.4",
50
+ "@types/node": "20.12.11",
51
+ "tsup": "8.5.1",
52
+ "typescript": "5.3.3",
53
+ "vitest": "3.2.4",
54
+ "joi": "17.11.1"
55
+ }
56
+ }