hzengine-core 0.1.2-dev
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/async/index.js +162 -0
- package/dist/async/zeppos_timer.js +58 -0
- package/dist/audio/index.js +260 -0
- package/dist/config/index.js +57 -0
- package/dist/debug/index.js +8 -0
- package/dist/index.js +103 -0
- package/dist/platform/index.js +1 -0
- package/dist/plugins/basic_command/$.js +8 -0
- package/dist/plugins/basic_command/audio.js +40 -0
- package/dist/plugins/basic_command/basic.js +124 -0
- package/dist/plugins/basic_command/character.js +112 -0
- package/dist/plugins/basic_command/conditional.js +260 -0
- package/dist/plugins/basic_command/config.js +22 -0
- package/dist/plugins/basic_command/decorator.js +24 -0
- package/dist/plugins/basic_command/eval.js +67 -0
- package/dist/plugins/basic_command/img.js +249 -0
- package/dist/plugins/basic_command/index.js +22 -0
- package/dist/plugins/basic_command/menu.js +140 -0
- package/dist/plugins/global_gesture/index.js +25 -0
- package/dist/plugins/transform/animation.js +440 -0
- package/dist/plugins/transform/commands.js +38 -0
- package/dist/plugins/transform/example_profiles.js +32 -0
- package/dist/plugins/transform/hz_anime.js +211 -0
- package/dist/plugins/transform/index.js +93 -0
- package/dist/script/index.js +537 -0
- package/dist/script/readscript.js +15 -0
- package/dist/script/strtools.js +157 -0
- package/dist/storage/decorator.js +260 -0
- package/dist/storage/fs.js +96 -0
- package/dist/storage/index.js +442 -0
- package/dist/system/index.js +144 -0
- package/dist/ui/index.js +535 -0
- package/dist/utils/path.js +289 -0
- package/license.txt +202 -0
- package/package.json +26 -0
- package/src/async/index.ts +124 -0
- package/src/async/zeppos_timer.js +65 -0
- package/src/audio/index.ts +224 -0
- package/src/config/index.ts +80 -0
- package/src/debug/index.ts +11 -0
- package/src/index.ts +122 -0
- package/src/platform/index.ts +158 -0
- package/src/plugins/basic_command/$.ts +11 -0
- package/src/plugins/basic_command/audio.ts +53 -0
- package/src/plugins/basic_command/basic.ts +145 -0
- package/src/plugins/basic_command/character.ts +144 -0
- package/src/plugins/basic_command/conditional.ts +349 -0
- package/src/plugins/basic_command/config.ts +29 -0
- package/src/plugins/basic_command/decorator.ts +29 -0
- package/src/plugins/basic_command/eval.ts +88 -0
- package/src/plugins/basic_command/img.ts +317 -0
- package/src/plugins/basic_command/index.ts +24 -0
- package/src/plugins/basic_command/menu.ts +178 -0
- package/src/plugins/global_gesture/index.ts +29 -0
- package/src/plugins/transform/animation.ts +542 -0
- package/src/plugins/transform/commands.ts +53 -0
- package/src/plugins/transform/example_profiles.ts +36 -0
- package/src/plugins/transform/hz_anime.ts +214 -0
- package/src/plugins/transform/index.ts +141 -0
- package/src/plugins/transform/readme.md +1 -0
- package/src/script/index.ts +623 -0
- package/src/script/readscript.ts +17 -0
- package/src/script/strtools.ts +159 -0
- package/src/storage/decorator.ts +473 -0
- package/src/storage/fs.ts +104 -0
- package/src/storage/index.ts +541 -0
- package/src/system/index.ts +95 -0
- package/src/ui/index.ts +699 -0
- package/src/utils/path.js +338 -0
- package/tsconfig.json +111 -0
- package/types/async/index.d.ts +24 -0
- package/types/async/zeppos_timer.d.ts +14 -0
- package/types/audio/index.d.ts +64 -0
- package/types/config/index.d.ts +9 -0
- package/types/debug/index.d.ts +6 -0
- package/types/index.d.ts +41 -0
- package/types/platform/index.d.ts +134 -0
- package/types/plugins/basic_command/$.d.ts +2 -0
- package/types/plugins/basic_command/audio.d.ts +2 -0
- package/types/plugins/basic_command/basic.d.ts +3 -0
- package/types/plugins/basic_command/character.d.ts +2 -0
- package/types/plugins/basic_command/conditional.d.ts +2 -0
- package/types/plugins/basic_command/config.d.ts +2 -0
- package/types/plugins/basic_command/decorator.d.ts +2 -0
- package/types/plugins/basic_command/eval.d.ts +2 -0
- package/types/plugins/basic_command/img.d.ts +2 -0
- package/types/plugins/basic_command/index.d.ts +2 -0
- package/types/plugins/basic_command/menu.d.ts +2 -0
- package/types/plugins/global_gesture/index.d.ts +2 -0
- package/types/plugins/transform/animation.d.ts +131 -0
- package/types/plugins/transform/commands.d.ts +7 -0
- package/types/plugins/transform/example_profiles.d.ts +2 -0
- package/types/plugins/transform/hz_anime.d.ts +51 -0
- package/types/plugins/transform/index.d.ts +13 -0
- package/types/script/index.d.ts +123 -0
- package/types/script/readscript.d.ts +2 -0
- package/types/script/strtools.d.ts +31 -0
- package/types/storage/decorator.d.ts +41 -0
- package/types/storage/fs.d.ts +1 -0
- package/types/storage/index.d.ts +86 -0
- package/types/system/index.d.ts +35 -0
- package/types/ui/index.d.ts +167 -0
- package/types/utils/path.d.ts +84 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { HZEngineCore } from "../index.js";
|
|
2
|
+
|
|
3
|
+
import { HzsInfo, Storage } from "../storage/index.js";
|
|
4
|
+
import {
|
|
5
|
+
mergeObjs2Str as joinObjs2Str,
|
|
6
|
+
parseInterpolatedStr,
|
|
7
|
+
removeComment,
|
|
8
|
+
splitStr2Objs,
|
|
9
|
+
} from "./strtools.js";
|
|
10
|
+
import { readline } from "./readscript.js";
|
|
11
|
+
import { Save } from "../storage/decorator.js";
|
|
12
|
+
export class Script {
|
|
13
|
+
constructor(public _core: HZEngineCore) {}
|
|
14
|
+
/**
|
|
15
|
+
* 调用栈
|
|
16
|
+
* 在call时保存当前执行位置和语句栈,在return时恢复执行位置和语句栈
|
|
17
|
+
*/
|
|
18
|
+
@Save("script.callStack")
|
|
19
|
+
private accessor _callStack: {
|
|
20
|
+
position: [path: string, index: number] | null;
|
|
21
|
+
statementStack: Script.StatementStack;
|
|
22
|
+
}[] = [];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 语句栈
|
|
26
|
+
* 比如while, if,会在语句开始时入栈,语句结束时出栈
|
|
27
|
+
*/
|
|
28
|
+
@Save("script.statementStack")
|
|
29
|
+
private accessor _statementStack: Script.StatementStack = [];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 下一次执行的脚本位置
|
|
33
|
+
* 注意:存储该值的时候应总是拷贝赋值而非直接引用赋值
|
|
34
|
+
*/
|
|
35
|
+
@Save("script.nextRunPosition")
|
|
36
|
+
private accessor _nextRunPosition: [path: string, index: number] | null =
|
|
37
|
+
null;
|
|
38
|
+
|
|
39
|
+
// Script Run
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 执行_nextRunPosition,并返回下一行_nextRunPosition是否不为null
|
|
43
|
+
*/
|
|
44
|
+
runSingleLine(): boolean {
|
|
45
|
+
if (!this._nextRunPosition) {
|
|
46
|
+
// 文件尾隐式执行 return
|
|
47
|
+
this.return();
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
let nowRunPosition: [path: string, index: number] = [
|
|
51
|
+
...this._nextRunPosition,
|
|
52
|
+
];
|
|
53
|
+
this.incrementNextPosition();
|
|
54
|
+
|
|
55
|
+
let rawCommand = readline(
|
|
56
|
+
this._core,
|
|
57
|
+
nowRunPosition[0],
|
|
58
|
+
nowRunPosition[1]
|
|
59
|
+
)!;
|
|
60
|
+
|
|
61
|
+
// remove comment
|
|
62
|
+
|
|
63
|
+
rawCommand = removeComment(rawCommand);
|
|
64
|
+
|
|
65
|
+
if (rawCommand.trim().length && !rawCommand.trim().startsWith("#")) {
|
|
66
|
+
if (rawCommand.trim().startsWith("*")) {
|
|
67
|
+
if (this._statementStack.length) {
|
|
68
|
+
throw `label between statement is not allowed, at file [${nowRunPosition[0]}] line [${nowRunPosition[1]}]`;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
this._core.debug.log("Run cmd: " + rawCommand);
|
|
72
|
+
|
|
73
|
+
// Process Command
|
|
74
|
+
this._processCmd(rawCommand, [...nowRunPosition]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return !!this._nextRunPosition;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Label Point Control
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 跳转到目标标签
|
|
85
|
+
* @param targetLabel
|
|
86
|
+
*/
|
|
87
|
+
jumpLabel(targetLabel: string) {
|
|
88
|
+
let labelPosition = this._locateLabel(targetLabel);
|
|
89
|
+
this._nextRunPosition = labelPosition;
|
|
90
|
+
this._statementStack = [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
jump(path: string, index: number, clearStatementStack: boolean = false) {
|
|
94
|
+
this._nextRunPosition = [path, index];
|
|
95
|
+
|
|
96
|
+
if (clearStatementStack) {
|
|
97
|
+
this._statementStack = [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 调用目标标签
|
|
103
|
+
* 保存当前执行位置至调用栈,跳转到目标位置,直到return返回
|
|
104
|
+
* @param targetLabel
|
|
105
|
+
*/
|
|
106
|
+
callLabel(targetLabel: string) {
|
|
107
|
+
// console.log(
|
|
108
|
+
// `pending to call label ${targetLabel}, stack=${JSON.stringify(
|
|
109
|
+
// this._routeStack
|
|
110
|
+
// )}`
|
|
111
|
+
// );
|
|
112
|
+
let labelPosition = this._locateLabel(targetLabel);
|
|
113
|
+
this._callStack.push({
|
|
114
|
+
position: this._nextRunPosition ? [...this._nextRunPosition] : null,
|
|
115
|
+
statementStack: this._statementStack,
|
|
116
|
+
});
|
|
117
|
+
this._nextRunPosition = labelPosition;
|
|
118
|
+
this._statementStack = [];
|
|
119
|
+
|
|
120
|
+
// console.log(
|
|
121
|
+
// `finished call label ${targetLabel}, stack=${JSON.stringify(
|
|
122
|
+
// this._routeStack
|
|
123
|
+
// )}`
|
|
124
|
+
// );
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
hasLabel(targetLabel: string) {
|
|
128
|
+
return (
|
|
129
|
+
this._core.storage.preloadedData?.script?.labelMap?.[targetLabel] != null
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return() {
|
|
134
|
+
// console.log(`pending to return, stack=${JSON.stringify(this._routeStack)}`);
|
|
135
|
+
let stackItem = this._callStack.pop();
|
|
136
|
+
if (stackItem) {
|
|
137
|
+
this._statementStack = stackItem.statementStack;
|
|
138
|
+
this._nextRunPosition = stackItem.position;
|
|
139
|
+
} else {
|
|
140
|
+
this._nextRunPosition = null;
|
|
141
|
+
this._statementStack = [];
|
|
142
|
+
|
|
143
|
+
// Game End
|
|
144
|
+
this._core.end();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// console.log(`finished return, stack=${JSON.stringify(this._routeStack)}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
clear() {
|
|
151
|
+
this._nextRunPosition = null;
|
|
152
|
+
this._callStack = [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _locateLabel(labelName: string) {
|
|
156
|
+
if (this._core.storage.preloadedData == null)
|
|
157
|
+
throw "Preloaded Data is Null";
|
|
158
|
+
if (!this._core.storage.preloadedData.script.labelMap[labelName])
|
|
159
|
+
throw `Error: Label [${labelName}] not found`;
|
|
160
|
+
let labelData: [name: string, index: number] = [
|
|
161
|
+
...this._core.storage.preloadedData.script.labelMap[labelName],
|
|
162
|
+
] as any;
|
|
163
|
+
return labelData;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public incrementNextPosition() {
|
|
167
|
+
if (this._core.storage.preloadedData == null)
|
|
168
|
+
throw "Preloaded Data is Null";
|
|
169
|
+
if (!this._nextRunPosition) throw "_nextRunPosition is null";
|
|
170
|
+
// console.log(`hzsInfoMap=${JSON.stringify(this._core.storage.preloadedData.script.hzsInfoMap)}`);
|
|
171
|
+
|
|
172
|
+
let hzsInfo: HzsInfo | undefined =
|
|
173
|
+
this._core.storage.preloadedData.script.hzsInfoMap[
|
|
174
|
+
this._nextRunPosition[0]
|
|
175
|
+
];
|
|
176
|
+
if (!hzsInfo)
|
|
177
|
+
throw `Preloaded hzsInfo of path(${this._nextRunPosition[0]}) not found`;
|
|
178
|
+
this._nextRunPosition[1]++;
|
|
179
|
+
if (this._nextRunPosition[1] >= hzsInfo.totalLines)
|
|
180
|
+
this._nextRunPosition = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Middleware
|
|
184
|
+
private _middlewares: Script.Middleware[] = [];
|
|
185
|
+
use(middleware: Script.Middleware, add_front: boolean = false) {
|
|
186
|
+
if (add_front) {
|
|
187
|
+
this._middlewares.unshift(middleware);
|
|
188
|
+
} else {
|
|
189
|
+
this._middlewares.push(middleware);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private _processCmd(
|
|
194
|
+
cmd: string,
|
|
195
|
+
nowRunPosition: [path: string, index: number]
|
|
196
|
+
) {
|
|
197
|
+
let ctx = this._buildContext(cmd, nowRunPosition);
|
|
198
|
+
if (this._middlewares.length === 0) {
|
|
199
|
+
this._processUnsolvedCmd(cmd);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
let i = 0,
|
|
203
|
+
len = this._middlewares.length;
|
|
204
|
+
let nextFunc = () => {
|
|
205
|
+
i++;
|
|
206
|
+
if (i >= len) {
|
|
207
|
+
this._processUnsolvedCmd(cmd);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this._middlewares[i](ctx, nextFunc);
|
|
211
|
+
};
|
|
212
|
+
this._middlewares[0](ctx, nextFunc);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private _processUnsolvedCmd(cmd: string) {
|
|
216
|
+
if (cmd.trim().length === 0)
|
|
217
|
+
return; // Empty Line
|
|
218
|
+
else if (cmd.trim().startsWith("*"))
|
|
219
|
+
return; // Label Command
|
|
220
|
+
else {
|
|
221
|
+
throw `Can not parse command: ${cmd}`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private _buildContext(
|
|
226
|
+
cmd: string,
|
|
227
|
+
nowRunPosition: [path: string, index: number]
|
|
228
|
+
): Script.Context {
|
|
229
|
+
return new Script.Context(
|
|
230
|
+
this._core,
|
|
231
|
+
cmd,
|
|
232
|
+
nowRunPosition[0],
|
|
233
|
+
nowRunPosition[1],
|
|
234
|
+
this._statementStack
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Statement Analyse
|
|
239
|
+
private _statementAnalyseStack: Script.StatementStack = [];
|
|
240
|
+
private _analyseStatementMiddlewares: Script.Middleware[] = [];
|
|
241
|
+
private _buildAnalyseStatementContext(
|
|
242
|
+
cmd: string,
|
|
243
|
+
nowRunPosition: [path: string, index: number]
|
|
244
|
+
) {
|
|
245
|
+
return new Script.ContextForAnalyseStatement(
|
|
246
|
+
this._core,
|
|
247
|
+
cmd,
|
|
248
|
+
nowRunPosition[0],
|
|
249
|
+
nowRunPosition[1],
|
|
250
|
+
this._statementAnalyseStack
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
useAnalyseStatement(
|
|
254
|
+
middleware: Script.MiddlewareForAnalyseStatement,
|
|
255
|
+
add_front?: boolean
|
|
256
|
+
) {
|
|
257
|
+
if (add_front) {
|
|
258
|
+
this._analyseStatementMiddlewares.unshift(middleware);
|
|
259
|
+
} else {
|
|
260
|
+
this._analyseStatementMiddlewares.push(middleware);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 分析statement
|
|
265
|
+
*
|
|
266
|
+
* Analyze the statement syntax and record the script point location and related information in advance.
|
|
267
|
+
* When the script executes a statement, the regular middleware corresponding to that statement will load
|
|
268
|
+
* the information saved by analyseStatement before the statement, and if there is no analysis, it will call
|
|
269
|
+
* analyseStatement to analyze, and the corresponding analysis middleware will process and save the information.
|
|
270
|
+
* After analysis is complete, reset _nextRunPosition to the location before the call, switch back to the normal mode,
|
|
271
|
+
* and continue executing.
|
|
272
|
+
*/
|
|
273
|
+
analyseStatement(ctx: Script.Context) {
|
|
274
|
+
// this._core.debug.log("[HZEngine] Start statement analyse mode");
|
|
275
|
+
|
|
276
|
+
// Backup _nextRunPosition
|
|
277
|
+
let _nextRunPositionBackup: [path: string, index: number] | null = this
|
|
278
|
+
._nextRunPosition
|
|
279
|
+
? [...this._nextRunPosition]
|
|
280
|
+
: null;
|
|
281
|
+
let covered: boolean = false;
|
|
282
|
+
// Set _nextRunPosition to the current position of the statement
|
|
283
|
+
this._nextRunPosition = [ctx.currentPath, ctx.currentLineIndex];
|
|
284
|
+
while (this._nextRunPosition) {
|
|
285
|
+
let rawCommand = readline(
|
|
286
|
+
this._core,
|
|
287
|
+
this._nextRunPosition[0],
|
|
288
|
+
this._nextRunPosition[1]
|
|
289
|
+
);
|
|
290
|
+
if (rawCommand == null)
|
|
291
|
+
throw `Readline Error(got ${rawCommand}), at file [${
|
|
292
|
+
this._nextRunPosition[0]
|
|
293
|
+
}] line [${this._nextRunPosition[1] + 1}]`;
|
|
294
|
+
|
|
295
|
+
// If the command is not empty and not a comment
|
|
296
|
+
if (rawCommand.trim().length && !rawCommand.trim().startsWith("#")) {
|
|
297
|
+
// If it is a label command, check if the statement stack is empty
|
|
298
|
+
if (rawCommand.trim().startsWith("*")) {
|
|
299
|
+
if (this._statementStack.length) {
|
|
300
|
+
throw `label between statement is not allowed, at file [${
|
|
301
|
+
this._nextRunPosition[0]
|
|
302
|
+
}] line [${this._nextRunPosition[1] + 1}]`;
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
// Build context for analyzing statement
|
|
306
|
+
let sub_ctx = this._buildAnalyseStatementContext(rawCommand, [
|
|
307
|
+
...this._nextRunPosition,
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
// console.log(`[HZEngine] analyse statement command [${rawCommand}]`);
|
|
311
|
+
|
|
312
|
+
// Process command
|
|
313
|
+
if (this._analyseStatementMiddlewares.length === 0) {
|
|
314
|
+
// TODO do nothing
|
|
315
|
+
} else {
|
|
316
|
+
let i = 0,
|
|
317
|
+
len = this._analyseStatementMiddlewares.length;
|
|
318
|
+
let nextFunc = () => {
|
|
319
|
+
i++;
|
|
320
|
+
if (i >= len) {
|
|
321
|
+
// TODO do nothing
|
|
322
|
+
} else {
|
|
323
|
+
this._analyseStatementMiddlewares[i](sub_ctx, nextFunc);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
this._analyseStatementMiddlewares[0](sub_ctx, nextFunc);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Move to the next line
|
|
332
|
+
this.incrementNextPosition();
|
|
333
|
+
|
|
334
|
+
if (covered) {
|
|
335
|
+
if (this._statementAnalyseStack.length === 0) {
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
if (this._statementAnalyseStack.length > 0) {
|
|
340
|
+
covered = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this._nextRunPosition = _nextRunPositionBackup;
|
|
346
|
+
|
|
347
|
+
// Check if the statement stack is empty
|
|
348
|
+
if (this._statementAnalyseStack.length > 0) {
|
|
349
|
+
throw `statement not closed, at file [${
|
|
350
|
+
this._statementAnalyseStack[
|
|
351
|
+
this._statementAnalyseStack.length - 1
|
|
352
|
+
][1][0]
|
|
353
|
+
}] line [${
|
|
354
|
+
this._statementAnalyseStack[
|
|
355
|
+
this._statementAnalyseStack.length - 1
|
|
356
|
+
][1][1] + 1
|
|
357
|
+
}]`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Reset _nextRunPosition to the backup value, and switch back to normal mode, and continue executing
|
|
361
|
+
// this._core.debug.logconsole.log("[HZEngine] Finished analyse statement mode ");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// eval
|
|
365
|
+
evalScope(code: string) {
|
|
366
|
+
try {
|
|
367
|
+
return new Function("sd", "gd", "hz", `${code}`)(
|
|
368
|
+
this._core.storage.sd,
|
|
369
|
+
this._core.storage.gd,
|
|
370
|
+
this._core
|
|
371
|
+
);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
this._core.debug.log(`Error in evalScope: ${e}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
evalExpression(code: string) {
|
|
377
|
+
this._core.debug.log(`evalExpression: ${code}`);
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
return new Function("sd", "gd", "hz", `return (${code})`)(
|
|
381
|
+
this._core.storage.sd,
|
|
382
|
+
this._core.storage.gd,
|
|
383
|
+
this._core
|
|
384
|
+
);
|
|
385
|
+
} catch (e) {
|
|
386
|
+
this._core.debug.log(`Error in evalExpression: ${e}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// parse string
|
|
391
|
+
parseString(str: string) {
|
|
392
|
+
let parsedInterpolated = parseInterpolatedStr(str);
|
|
393
|
+
let res: string = "";
|
|
394
|
+
for (let item of parsedInterpolated) {
|
|
395
|
+
if (item.isExpression) {
|
|
396
|
+
res += this.evalExpression(item.str);
|
|
397
|
+
} else {
|
|
398
|
+
res += item.str;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return res;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export namespace Script {
|
|
407
|
+
export type Middleware = (ctx: Context, next: () => void) => void;
|
|
408
|
+
export class Context {
|
|
409
|
+
constructor(
|
|
410
|
+
protected _core: HZEngineCore,
|
|
411
|
+
private _rawtext: string,
|
|
412
|
+
public readonly currentPath: string,
|
|
413
|
+
public readonly currentLineIndex: number,
|
|
414
|
+
private _statementStack: StatementStack
|
|
415
|
+
) {}
|
|
416
|
+
public get rawtext(): string {
|
|
417
|
+
return this._rawtext;
|
|
418
|
+
}
|
|
419
|
+
public set rawtext(rawtext: string) {
|
|
420
|
+
this._rawtext = rawtext;
|
|
421
|
+
this._rawtextChanged = true;
|
|
422
|
+
}
|
|
423
|
+
private _rawtextChanged = false;
|
|
424
|
+
private _slicedArgs: Context.SlicedArg[] | null = null;
|
|
425
|
+
get slicedArgs(): Context.SlicedArg[] {
|
|
426
|
+
if (!this._slicedArgs || this._rawtextChanged)
|
|
427
|
+
this._slicedArgs = splitStr2Objs(this.rawtext);
|
|
428
|
+
this._rawtextChanged = false;
|
|
429
|
+
return this._slicedArgs;
|
|
430
|
+
}
|
|
431
|
+
// 注意只有在修改在触发slicedArgs的时候才会更新rawtext
|
|
432
|
+
set slicedArgs(slicedArgs: Context.SlicedArg[]) {
|
|
433
|
+
this._slicedArgs = JSON.parse(JSON.stringify(slicedArgs)); // TODO 深拷贝 性能
|
|
434
|
+
this._rawtext = joinObjs2Str(slicedArgs);
|
|
435
|
+
// TODO this._rawtextChanged = true ???
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* 開始一個新的Statement,返回該Statement的數據
|
|
439
|
+
* Start a new statement and return the data of the new statement
|
|
440
|
+
* @param identifier the identifier of the statement
|
|
441
|
+
* @returns the data of the new statement
|
|
442
|
+
*/
|
|
443
|
+
startStatement(
|
|
444
|
+
identifier: string,
|
|
445
|
+
data?: Storage.Saveable<unknown>
|
|
446
|
+
): StatementData {
|
|
447
|
+
let statement_data = data ?? this.getStatementData();
|
|
448
|
+
let statementStackItem: StatementStackItem = [
|
|
449
|
+
identifier,
|
|
450
|
+
[this.currentPath, this.currentLineIndex],
|
|
451
|
+
statement_data,
|
|
452
|
+
];
|
|
453
|
+
this._statementStack.push(statementStackItem);
|
|
454
|
+
return statement_data;
|
|
455
|
+
}
|
|
456
|
+
endStatement(identifier: string) {
|
|
457
|
+
if (this._statementStack.length === 0)
|
|
458
|
+
throw `statement not open, at file [${this.currentPath}] line [${
|
|
459
|
+
this.currentLineIndex + 1
|
|
460
|
+
}]`;
|
|
461
|
+
if (
|
|
462
|
+
this._statementStack[this._statementStack.length - 1][0] !== identifier
|
|
463
|
+
)
|
|
464
|
+
throw `the last statement in the stack is not ${identifier}, at file [${
|
|
465
|
+
this.currentPath
|
|
466
|
+
}] line [${this.currentLineIndex + 1}]`;
|
|
467
|
+
return this._statementStack.pop()![2] as NonNullable<
|
|
468
|
+
Storage.Saveable<unknown>
|
|
469
|
+
>;
|
|
470
|
+
}
|
|
471
|
+
get statementStack() {
|
|
472
|
+
return this._statementStack;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// statement data will stored in core.storage.globalData.script.statement_data
|
|
476
|
+
// the key of the statement data is the line index of the start statement,
|
|
477
|
+
// for example: the key of the statement data of "menu ... end menu"
|
|
478
|
+
// is stored in the key of the line index of the "menu" statement
|
|
479
|
+
getStatementData(): NonNullable<Storage.Saveable<unknown>> {
|
|
480
|
+
let statement_data_in_file = this._core.storage.getSaveableData(
|
|
481
|
+
this._core.storage.globalData,
|
|
482
|
+
true,
|
|
483
|
+
"script",
|
|
484
|
+
"statement_data",
|
|
485
|
+
this.currentPath
|
|
486
|
+
) as Record<string, NonNullable<Storage.Saveable<unknown>>>;
|
|
487
|
+
if (!statement_data_in_file["" + this.currentLineIndex]) {
|
|
488
|
+
this._core.script.analyseStatement(this);
|
|
489
|
+
statement_data_in_file = this._core.storage.getSaveableData(
|
|
490
|
+
this._core.storage.globalData,
|
|
491
|
+
true,
|
|
492
|
+
"script",
|
|
493
|
+
"statement_data",
|
|
494
|
+
this.currentPath
|
|
495
|
+
) as Record<string, NonNullable<Storage.Saveable<unknown>>>;
|
|
496
|
+
if (!statement_data_in_file[this.currentLineIndex])
|
|
497
|
+
throw `analyse statement failed as statement data not found, at file [${
|
|
498
|
+
this.currentPath
|
|
499
|
+
}] line [${this.currentLineIndex + 1}]`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return statement_data_in_file[this.currentLineIndex]!;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
setStatementData(
|
|
506
|
+
statement_data: NonNullable<Storage.Saveable<unknown>>,
|
|
507
|
+
start_position: [path: string, index: number]
|
|
508
|
+
) {
|
|
509
|
+
let statement_data_in_file = this._core.storage.getSaveableData(
|
|
510
|
+
this._core.storage.globalData,
|
|
511
|
+
true,
|
|
512
|
+
"script",
|
|
513
|
+
"statement_data",
|
|
514
|
+
start_position[0]
|
|
515
|
+
) as Record<string, NonNullable<Storage.Saveable<unknown>>>;
|
|
516
|
+
statement_data_in_file["" + start_position[1]] = statement_data;
|
|
517
|
+
this._core.storage.saveGlobalData();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
export namespace Utils {
|
|
521
|
+
export const joinSlicedArgs: typeof joinObjs2Str = joinObjs2Str;
|
|
522
|
+
export const splitRawtext: typeof splitStr2Objs = splitStr2Objs;
|
|
523
|
+
export function splitCommas(rawtext: string): string[] {
|
|
524
|
+
let slicedArgs = splitStr2Objs(rawtext);
|
|
525
|
+
// console.log(`splitCommas rawtext: ${rawtext}, slicedArgs: ${JSON.stringify(slicedArgs)}`);
|
|
526
|
+
|
|
527
|
+
let res: string[] = [];
|
|
528
|
+
for (let i = 0; i < slicedArgs.length; i++) {
|
|
529
|
+
if (slicedArgs[i].isQuoted) res.push(`"${slicedArgs[i].str}"`);
|
|
530
|
+
else if (slicedArgs[i].isSquared) res.push(`[${slicedArgs[i].str}]`);
|
|
531
|
+
else if (slicedArgs[i].isRounded) res.push(`(${slicedArgs[i].str})`);
|
|
532
|
+
else {
|
|
533
|
+
slicedArgs[i].str.split(",").forEach((str) => {
|
|
534
|
+
str = str.trim();
|
|
535
|
+
if (str) res.push(str);
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// console.log(`splitCommas res: ${JSON.stringify(res)}`);
|
|
540
|
+
|
|
541
|
+
return res;
|
|
542
|
+
}
|
|
543
|
+
export function parseTuple(rawtext: string) {
|
|
544
|
+
if (
|
|
545
|
+
rawtext.length < 2 ||
|
|
546
|
+
rawtext[0] !== "(" ||
|
|
547
|
+
rawtext[rawtext.length - 1] !== ")"
|
|
548
|
+
) {
|
|
549
|
+
throw `invalid tuple: ${rawtext}`;
|
|
550
|
+
}
|
|
551
|
+
rawtext = rawtext.slice(1, rawtext.length - 1);
|
|
552
|
+
// console.log(`parseTuple rawtext: ${rawtext}`);
|
|
553
|
+
|
|
554
|
+
return parseHzsArgs(rawtext);
|
|
555
|
+
}
|
|
556
|
+
export function parseArray(rawtext: string) {
|
|
557
|
+
if (
|
|
558
|
+
rawtext.length < 2 ||
|
|
559
|
+
rawtext[0] !== "[" ||
|
|
560
|
+
rawtext[rawtext.length - 1] !== "]"
|
|
561
|
+
) {
|
|
562
|
+
throw `invalid array: ${rawtext}`;
|
|
563
|
+
}
|
|
564
|
+
rawtext = rawtext.slice(1, rawtext.length - 1);
|
|
565
|
+
return parseHzsArgs(rawtext);
|
|
566
|
+
}
|
|
567
|
+
export function parseHzsArgs(rawtext: string): TupleOrArr {
|
|
568
|
+
rawtext = rawtext.trim();
|
|
569
|
+
let arr = splitCommas(rawtext);
|
|
570
|
+
let res = arr.map((str) => {
|
|
571
|
+
if (str.startsWith("(")) return parseTuple(str);
|
|
572
|
+
else if (str.startsWith("[")) return parseArray(str);
|
|
573
|
+
else return str;
|
|
574
|
+
});
|
|
575
|
+
// console.log(`parseHzsArgs from: "${rawtext}" ; res: ${JSON.stringify(res)}`);
|
|
576
|
+
|
|
577
|
+
return res;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
type TupleOrArr = (string | TupleOrArr)[];
|
|
581
|
+
}
|
|
582
|
+
export type MiddlewareForAnalyseStatement = (
|
|
583
|
+
ctx: ContextForAnalyseStatement,
|
|
584
|
+
next: () => void
|
|
585
|
+
) => void;
|
|
586
|
+
export class ContextForAnalyseStatement extends Context {
|
|
587
|
+
startStatement(
|
|
588
|
+
identifier: string,
|
|
589
|
+
data: Storage.Saveable<unknown> = {}
|
|
590
|
+
): StatementData {
|
|
591
|
+
return super.startStatement(identifier, data ?? {});
|
|
592
|
+
}
|
|
593
|
+
endStatement(identifier: string) {
|
|
594
|
+
let statement_data = super.endStatement(identifier);
|
|
595
|
+
// save analysed statement data
|
|
596
|
+
let statement_data_in_file = this._core.storage.getSaveableData(
|
|
597
|
+
this._core.storage.globalData,
|
|
598
|
+
true,
|
|
599
|
+
"script",
|
|
600
|
+
"statement_data",
|
|
601
|
+
this.currentPath
|
|
602
|
+
) as Record<string, NonNullable<Storage.Saveable<unknown>>>;
|
|
603
|
+
statement_data_in_file["" + this.currentLineIndex] = statement_data;
|
|
604
|
+
this._core.storage.saveGlobalData();
|
|
605
|
+
return statement_data;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
export namespace Context {
|
|
609
|
+
export interface SlicedArg {
|
|
610
|
+
str: string;
|
|
611
|
+
isQuoted?: boolean; // 是否被雙引號包圍
|
|
612
|
+
isSquared?: boolean; // 是否被方括號包圍
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export type StatementStackItem = [
|
|
617
|
+
identifier: string,
|
|
618
|
+
start_position: [path: string, index: number],
|
|
619
|
+
statement_data: Storage.JSONValue,
|
|
620
|
+
];
|
|
621
|
+
export type StatementStack = StatementStackItem[];
|
|
622
|
+
export type StatementData = NonNullable<Storage.JSONValue>;
|
|
623
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HZEngineCore } from "../index.js";
|
|
2
|
+
|
|
3
|
+
// import * as hmFS from '@zos/fs'
|
|
4
|
+
let cache = new Map();
|
|
5
|
+
export function readline(_core: HZEngineCore, path: string, line_index: number): string | undefined {
|
|
6
|
+
if (cache.has(path)) {
|
|
7
|
+
return cache.get(path)[line_index];
|
|
8
|
+
}
|
|
9
|
+
// Get Command
|
|
10
|
+
let hzsContent = _core.platform.readFileSync({
|
|
11
|
+
path,
|
|
12
|
+
options: { encoding: "utf8" },
|
|
13
|
+
}) as string;
|
|
14
|
+
let file = hzsContent.split(/\r?\n|(?<!\n)\r/)
|
|
15
|
+
cache.set(path, file);
|
|
16
|
+
return file[line_index];
|
|
17
|
+
}
|