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,53 @@
|
|
|
1
|
+
import { HZEngineCore } from "../../index.js";
|
|
2
|
+
import Path from "../../utils/path.js";
|
|
3
|
+
|
|
4
|
+
export function audio_command(core: HZEngineCore) {
|
|
5
|
+
// play command: play <channel> "<path>"
|
|
6
|
+
core.script.use((ctx, next) => {
|
|
7
|
+
if (
|
|
8
|
+
ctx.rawtext.trim().split(" ")[0].toLowerCase() !== "play" &&
|
|
9
|
+
ctx.rawtext.trim().split(" ")[0].toLowerCase() !== "queue"
|
|
10
|
+
)
|
|
11
|
+
return next();
|
|
12
|
+
if (ctx.slicedArgs.length !== 3)
|
|
13
|
+
throw `${ctx.slicedArgs[0].str.toUpperCase()} Command: incorrect amount of args`;
|
|
14
|
+
if (!ctx.slicedArgs[2].isQuoted)
|
|
15
|
+
throw `the third arg of ${ctx.slicedArgs[0].str} command should be quoted as string path`;
|
|
16
|
+
|
|
17
|
+
let channel_name = ctx.slicedArgs[1].str;
|
|
18
|
+
let path = ctx.slicedArgs[2].str;
|
|
19
|
+
|
|
20
|
+
core.debug.log(`Play Command: play ${channel_name} ${path}`);
|
|
21
|
+
|
|
22
|
+
let channel = core.audio.channels[channel_name];
|
|
23
|
+
|
|
24
|
+
if (!channel) {
|
|
25
|
+
throw `audio channel ${channel_name} not found`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (ctx.slicedArgs[0].str !== "queue") {
|
|
29
|
+
channel.stop();
|
|
30
|
+
}
|
|
31
|
+
channel.push({ path: Path.join(core.storage.projectRoot!, "audio", path) });
|
|
32
|
+
channel.play();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// stop command: stop <channel>
|
|
36
|
+
core.script.use((ctx, next) => {
|
|
37
|
+
if (ctx.rawtext.trim().split(" ")[0].toLowerCase() !== "stop") {
|
|
38
|
+
return next();
|
|
39
|
+
}
|
|
40
|
+
if (ctx.slicedArgs.length !== 2) {
|
|
41
|
+
throw `Stop Command: incorrect amount of args`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let channel_name = ctx.slicedArgs[1].str;
|
|
45
|
+
|
|
46
|
+
let channel = core.audio.channels[channel_name];
|
|
47
|
+
|
|
48
|
+
if (!channel) {
|
|
49
|
+
throw `audio channel ${channel_name} not found`;
|
|
50
|
+
}
|
|
51
|
+
channel.stop();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { HZEngineCore, UI } from "../../index.js";
|
|
2
|
+
|
|
3
|
+
export function basic_commands(core: HZEngineCore) {
|
|
4
|
+
// jump command
|
|
5
|
+
core.script.use((ctx, next) => {
|
|
6
|
+
let strArr = ctx.rawtext.trim().split(/ +/);
|
|
7
|
+
if (strArr.length === 0 || strArr[0].toLowerCase() !== "jump")
|
|
8
|
+
return next();
|
|
9
|
+
if (strArr.length !== 2) throw "Jump Command: incorrect amount of args";
|
|
10
|
+
core.debug.log(`Jump Command: jump to label [${strArr[1]}]`);
|
|
11
|
+
core.script.jumpLabel(strArr[1]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// call command
|
|
15
|
+
core.script.use((ctx, next) => {
|
|
16
|
+
let strArr = ctx.rawtext.trim().split(/ +/);
|
|
17
|
+
if (strArr.length === 0 || strArr[0].toLowerCase() !== "call")
|
|
18
|
+
return next();
|
|
19
|
+
if (strArr.length !== 2) throw "Call Command: incorrect amount of args";
|
|
20
|
+
core.debug.log(`Call Command: call label [${strArr[1]}]`);
|
|
21
|
+
core.script.callLabel(strArr[1]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// return command
|
|
25
|
+
core.script.use((ctx, next) => {
|
|
26
|
+
let strArr = ctx.rawtext.trim().split(/ +/);
|
|
27
|
+
if (strArr.length === 0 || strArr[0].toLowerCase() !== "return")
|
|
28
|
+
return next();
|
|
29
|
+
if (strArr.length !== 1)
|
|
30
|
+
throw "Return Command: this command can not have args";
|
|
31
|
+
core.debug.log(`Return Command: return`);
|
|
32
|
+
core.script.return();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// (debug) echo command
|
|
36
|
+
core.script.use((ctx, next) => {
|
|
37
|
+
let str = ctx.rawtext.trim();
|
|
38
|
+
if (!str.startsWith("echo")) return next();
|
|
39
|
+
core.debug.log(`[ECHO] ${core.script.parseString(str.slice(4).trim())}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// say command
|
|
43
|
+
core.script.use((ctx, next) => {
|
|
44
|
+
if (!ctx.rawtext.trim().startsWith('"')) return next();
|
|
45
|
+
|
|
46
|
+
function parseSayCommandArgs() {
|
|
47
|
+
if (
|
|
48
|
+
ctx.slicedArgs.length > 0 &&
|
|
49
|
+
ctx.slicedArgs[ctx.slicedArgs.length - 1].str === "nowait"
|
|
50
|
+
) {
|
|
51
|
+
ctx.slicedArgs = ctx.slicedArgs.slice(0, ctx.slicedArgs.length - 1);
|
|
52
|
+
return {
|
|
53
|
+
wait: false,
|
|
54
|
+
};
|
|
55
|
+
} else {
|
|
56
|
+
return {
|
|
57
|
+
wait: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let parsed = parseSayCommandArgs();
|
|
63
|
+
|
|
64
|
+
if (ctx.slicedArgs.length > 2 || ctx.slicedArgs.length < 1) {
|
|
65
|
+
throw `Say Command: incorrect amount of args`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (ctx.slicedArgs.length === 1) {
|
|
69
|
+
core.debug.log(`[SAY] ${ctx.slicedArgs[0].str}`);
|
|
70
|
+
sayAction(core, "", ctx.slicedArgs[0].str, parsed.wait);
|
|
71
|
+
} else {
|
|
72
|
+
if (!ctx.slicedArgs[1].isQuoted)
|
|
73
|
+
throw `Say Command: second arg should be quoted`;
|
|
74
|
+
core.debug.log(`[SAY] ${ctx.slicedArgs[0].str}: ${ctx.slicedArgs[1].str}`);
|
|
75
|
+
sayAction(
|
|
76
|
+
core,
|
|
77
|
+
ctx.slicedArgs[0].str,
|
|
78
|
+
ctx.slicedArgs[1].str,
|
|
79
|
+
parsed.wait
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// if (parsed?.wait) core.system.pause();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// pause command
|
|
87
|
+
core.script.use((ctx, next) => {
|
|
88
|
+
if (ctx.rawtext.trim().split(" ")[0].toLowerCase() !== "pause")
|
|
89
|
+
return next();
|
|
90
|
+
if (ctx.slicedArgs.length > 2)
|
|
91
|
+
throw `Pause Command: incorrect amount of args`;
|
|
92
|
+
if (ctx.slicedArgs.length === 2) {
|
|
93
|
+
if (
|
|
94
|
+
ctx.slicedArgs[1].isQuoted ||
|
|
95
|
+
!isFinite(Number(ctx.slicedArgs[1].str))
|
|
96
|
+
)
|
|
97
|
+
throw `Pause Command: the second arg must be a number`;
|
|
98
|
+
core.system.pause(Number(ctx.slicedArgs[1].str) * 1000);
|
|
99
|
+
} else core.system.pause();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function sayAction(
|
|
104
|
+
core: HZEngineCore,
|
|
105
|
+
who: string,
|
|
106
|
+
what: string,
|
|
107
|
+
wait: boolean
|
|
108
|
+
) {
|
|
109
|
+
const say_view_tag = "hzengine.say";
|
|
110
|
+
const say_view_name = "say";
|
|
111
|
+
what = core.script.parseString(what);
|
|
112
|
+
let message = {
|
|
113
|
+
who,
|
|
114
|
+
what,
|
|
115
|
+
};
|
|
116
|
+
let router = core.ui.getRouter(say_view_tag);
|
|
117
|
+
if (!router) {
|
|
118
|
+
core.ui.addRouter(say_view_tag, "ct");
|
|
119
|
+
router = core.ui.getRouter(say_view_tag)!;
|
|
120
|
+
}
|
|
121
|
+
if (!router.length) {
|
|
122
|
+
// @ts-ignore
|
|
123
|
+
router.push<UI.Message>(say_view_name, message);
|
|
124
|
+
} else {
|
|
125
|
+
// @ts-ignore
|
|
126
|
+
router.update<UI.Message>(message);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (wait) {
|
|
130
|
+
|
|
131
|
+
if (core.config.getConfig("game.autoplay.enable")) {
|
|
132
|
+
let delay =
|
|
133
|
+
(core.config.getConfig("game.autoplay.extra_delay") as number) +
|
|
134
|
+
(core.config.getConfig("game.autoplay.ms_per_char") as number) *
|
|
135
|
+
what.length;
|
|
136
|
+
|
|
137
|
+
if (isNaN(delay) || !isFinite(delay) || delay < 0) {
|
|
138
|
+
throw `Say Action: AutoPlay Delay Error: ${delay} ms`;
|
|
139
|
+
}
|
|
140
|
+
core.system.pause(delay);
|
|
141
|
+
} else {
|
|
142
|
+
core.system.pause();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { HZEngineCore } from "../../index.js";
|
|
2
|
+
import { sayAction } from "./basic.js";
|
|
3
|
+
|
|
4
|
+
export function character_command(core: HZEngineCore) {
|
|
5
|
+
// character command
|
|
6
|
+
// Used to create a character, and save it to script field in archive storage,
|
|
7
|
+
// so that it can be used when the archive is loaded next time the game starts
|
|
8
|
+
// by loading the archive.
|
|
9
|
+
// syntax: character <short_name> <display_name>
|
|
10
|
+
// if the short name is conflict with an existing character, the script will throw an error
|
|
11
|
+
core.script.use((ctx, next) => {
|
|
12
|
+
if (ctx.rawtext.trim().split(" ")[0].toLowerCase() !== "character")
|
|
13
|
+
return next();
|
|
14
|
+
if (ctx.slicedArgs.length > 3)
|
|
15
|
+
throw `Character Command: incorrect amount of args`;
|
|
16
|
+
if (ctx.slicedArgs.length === 1)
|
|
17
|
+
throw `Character Command: incorrect amount of args`;
|
|
18
|
+
if (ctx.slicedArgs.length === 2)
|
|
19
|
+
throw `Character Command: display name not specified`;
|
|
20
|
+
if (ctx.slicedArgs[1].isQuoted)
|
|
21
|
+
throw `Character Command: short name should not be quoted`;
|
|
22
|
+
// Check if the short name is conflict with an existing character
|
|
23
|
+
let short_name = ctx.slicedArgs[1].str;
|
|
24
|
+
let characterMap = getCharacterMap();
|
|
25
|
+
if (characterMap[short_name] != null) {
|
|
26
|
+
if (
|
|
27
|
+
characterMap[short_name].declare_info.path === ctx.currentPath &&
|
|
28
|
+
characterMap[short_name].declare_info.index === ctx.currentLineIndex
|
|
29
|
+
) {
|
|
30
|
+
} else
|
|
31
|
+
throw `Character Command: short name [${short_name}] conflict, at file [${
|
|
32
|
+
characterMap[short_name].declare_info.path
|
|
33
|
+
}] line [${characterMap[short_name].declare_info.index + 1}] and `;
|
|
34
|
+
} else {
|
|
35
|
+
let display_name = ctx.slicedArgs[2].isQuoted
|
|
36
|
+
? core.script.parseString(ctx.slicedArgs[2].str)
|
|
37
|
+
: ctx.slicedArgs[2].str;
|
|
38
|
+
characterMap[short_name] = buildCharacterInfo(short_name, display_name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildCharacterInfo(
|
|
42
|
+
short_name: string,
|
|
43
|
+
display_name: string
|
|
44
|
+
): CharacterInfo {
|
|
45
|
+
return {
|
|
46
|
+
short_name,
|
|
47
|
+
display_name,
|
|
48
|
+
declare_info: {
|
|
49
|
+
path: ctx.currentPath,
|
|
50
|
+
index: ctx.currentLineIndex,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function getCharacterMap(): Record<string, CharacterInfo> {
|
|
57
|
+
// // Check core.storage.archiveData
|
|
58
|
+
// if (typeof core.storage.archiveData == null)
|
|
59
|
+
// throw `[HZEngine] ArchiveData is null`;
|
|
60
|
+
// if (typeof core.storage.archiveData != "object")
|
|
61
|
+
// throw `[HZEngine] ArchiveData is not a object(Record or Array)`;
|
|
62
|
+
// if (Array.isArray(core.storage.archiveData))
|
|
63
|
+
// throw `[HZEngine] ArchiveData is an Array`;
|
|
64
|
+
// // Check core.storage.archiveData.script
|
|
65
|
+
// if (core.storage.archiveData.script == null) {
|
|
66
|
+
// console.log(`[HZEngine] ArchiveData.script is null, create it`);
|
|
67
|
+
// core.storage.archiveData.script = {};
|
|
68
|
+
// }
|
|
69
|
+
// if (typeof core.storage.archiveData.script != "object")
|
|
70
|
+
// throw `[HZEngine] ArchiveData.script is not a object(Record or Array)`;
|
|
71
|
+
// if (Array.isArray(core.storage.archiveData.script))
|
|
72
|
+
// throw `[HZEngine] ArchiveData.script is an Array`;
|
|
73
|
+
// // Check core.storage.archiveData.script.characterMap
|
|
74
|
+
// if (core.storage.archiveData.script.characterMap == null) {
|
|
75
|
+
// console.log(
|
|
76
|
+
// `[HZEngine] ArchiveData.script.characterMap is null, create it`
|
|
77
|
+
// );
|
|
78
|
+
// core.storage.archiveData.script.characterMap = {};
|
|
79
|
+
// }
|
|
80
|
+
// if (typeof core.storage.archiveData.script.characterMap != "object")
|
|
81
|
+
// throw `[HZEngine] ArchiveData.script.characterMap is not a object(Record or Array)`;
|
|
82
|
+
// if (Array.isArray(core.storage.archiveData.script.characterMap))
|
|
83
|
+
// throw `[HZEngine] ArchiveData.script.characterMap is an Array`;
|
|
84
|
+
// return core.storage.archiveData.script.characterMap as any; // TODO
|
|
85
|
+
return core.storage.getSaveableData(
|
|
86
|
+
core.storage.archiveData,
|
|
87
|
+
true,
|
|
88
|
+
"script",
|
|
89
|
+
"characterMap"
|
|
90
|
+
) as unknown as Record<string, CharacterInfo>;
|
|
91
|
+
}
|
|
92
|
+
interface CharacterInfo {
|
|
93
|
+
short_name: string;
|
|
94
|
+
display_name: string;
|
|
95
|
+
// debug
|
|
96
|
+
declare_info: {
|
|
97
|
+
path: string;
|
|
98
|
+
index: number;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Gramma: %short_name% "{message}"
|
|
103
|
+
core.script.use((ctx, next) => {
|
|
104
|
+
if (ctx.slicedArgs[0].isQuoted) return next();
|
|
105
|
+
let characterMap = getCharacterMap();
|
|
106
|
+
let short_name = ctx.slicedArgs[0].str;
|
|
107
|
+
for (let key in characterMap) {
|
|
108
|
+
// console.log(`Character Say Command: key=${key} sn=${short_name}`);
|
|
109
|
+
|
|
110
|
+
function parseSayCommandArgs() {
|
|
111
|
+
if (
|
|
112
|
+
ctx.slicedArgs.length > 0 &&
|
|
113
|
+
ctx.slicedArgs[ctx.slicedArgs.length - 1].str === "nowait"
|
|
114
|
+
) {
|
|
115
|
+
ctx.slicedArgs = ctx.slicedArgs.slice(0, ctx.slicedArgs.length - 1);
|
|
116
|
+
return {
|
|
117
|
+
wait: false,
|
|
118
|
+
};
|
|
119
|
+
} else {
|
|
120
|
+
return {
|
|
121
|
+
wait: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let parsed = parseSayCommandArgs();
|
|
127
|
+
|
|
128
|
+
if (key === short_name) {
|
|
129
|
+
if (ctx.slicedArgs.length != 2)
|
|
130
|
+
throw `Character Say Command: incorrect amount of args`;
|
|
131
|
+
console.log(
|
|
132
|
+
"Character Say Command:",
|
|
133
|
+
short_name,
|
|
134
|
+
ctx.slicedArgs[1].str
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
sayAction(core, characterMap[key].display_name, ctx.slicedArgs[1].str, parsed.wait);
|
|
138
|
+
// if(parsed.wait) core.system.pause();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return next();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { HZEngineCore } from "../../index.js";
|
|
2
|
+
|
|
3
|
+
export function conditional(core: HZEngineCore) {
|
|
4
|
+
// if ... [elif] ... [else] ... end if
|
|
5
|
+
// syntax:
|
|
6
|
+
// if <expression>
|
|
7
|
+
// <code>
|
|
8
|
+
// [elif <expression>]
|
|
9
|
+
// <code>
|
|
10
|
+
// [else]
|
|
11
|
+
// <code>
|
|
12
|
+
// end if
|
|
13
|
+
|
|
14
|
+
// if statement start
|
|
15
|
+
core.script.use((ctx, next) => {
|
|
16
|
+
if (
|
|
17
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
18
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "if"
|
|
19
|
+
) {
|
|
20
|
+
return next();
|
|
21
|
+
}
|
|
22
|
+
let data = ctx.startStatement("if") as unknown as IfStatementData;
|
|
23
|
+
// console.log(`if data=${JSON.stringify(data)}`);
|
|
24
|
+
|
|
25
|
+
let if_expression_res: unknown = core.script.evalExpression(
|
|
26
|
+
data.if_expression
|
|
27
|
+
);
|
|
28
|
+
if (typeof if_expression_res !== "boolean") {
|
|
29
|
+
throw `If statement: if expression must return boolean, at file [${data.start_position[0]}] line [${data.start_position[1]}]`;
|
|
30
|
+
}
|
|
31
|
+
if (if_expression_res) {
|
|
32
|
+
// continue to execute next line
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// analyse elif
|
|
37
|
+
for (let i = 0; i < data.elif_list.length; i++) {
|
|
38
|
+
let item = data.elif_list[i];
|
|
39
|
+
let elif_expression_res = core.script.evalExpression(item.expression);
|
|
40
|
+
if (typeof elif_expression_res !== "boolean") {
|
|
41
|
+
throw `If statement: elif expression must return boolean, at file [${item.position[0]}] line [${item.position[1]}]`;
|
|
42
|
+
}
|
|
43
|
+
if (elif_expression_res) {
|
|
44
|
+
// jump to next line of elif statement
|
|
45
|
+
core.script.jump(item.position[0], item.position[1] + 1);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// analyse else
|
|
51
|
+
if (data.else_position) {
|
|
52
|
+
core.script.jump(data.else_position[0], data.else_position[1] + 1);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// the expressions are all false
|
|
57
|
+
// jump to the end of if statement
|
|
58
|
+
core.script.jump(data.end_position[0], data.end_position[1]);
|
|
59
|
+
});
|
|
60
|
+
// elif statement
|
|
61
|
+
// when come across next elif statement, jump to "end if" statement to stop execute.
|
|
62
|
+
core.script.use((ctx, next) => {
|
|
63
|
+
if (
|
|
64
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
65
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "elif"
|
|
66
|
+
) {
|
|
67
|
+
return next();
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
ctx.statementStack.length === 0 ||
|
|
71
|
+
ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
|
|
72
|
+
) {
|
|
73
|
+
throw `If statement: elif statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
74
|
+
}
|
|
75
|
+
let data = ctx.statementStack[
|
|
76
|
+
ctx.statementStack.length - 1
|
|
77
|
+
][2] as unknown as IfStatementData;
|
|
78
|
+
// check if this elif statement belong to the closest if statement
|
|
79
|
+
for (let i = 0; i < data.elif_list.length; i++) {
|
|
80
|
+
let item = data.elif_list[i];
|
|
81
|
+
if (
|
|
82
|
+
item.position[0] === ctx.currentPath &&
|
|
83
|
+
item.position[1] === ctx.currentLineIndex
|
|
84
|
+
) {
|
|
85
|
+
// jump to the "end if" statement
|
|
86
|
+
core.script.jump(data.end_position[0], data.end_position[1]);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// the elif statement is not belong to the closest if statement, so throw error
|
|
91
|
+
throw `If statement: elif statement not belong to the closest if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
92
|
+
});
|
|
93
|
+
// else statement
|
|
94
|
+
core.script.use((ctx, next) => {
|
|
95
|
+
if (
|
|
96
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
97
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "else"
|
|
98
|
+
) {
|
|
99
|
+
return next();
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
ctx.statementStack.length === 0 ||
|
|
103
|
+
ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
|
|
104
|
+
) {
|
|
105
|
+
throw `If statement: else statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
106
|
+
}
|
|
107
|
+
let data = ctx.statementStack[
|
|
108
|
+
ctx.statementStack.length - 1
|
|
109
|
+
][2] as unknown as IfStatementData;
|
|
110
|
+
// check if this else statement belong to the closest if statement
|
|
111
|
+
if (data.else_position) {
|
|
112
|
+
if (
|
|
113
|
+
data.else_position[0] === ctx.currentPath &&
|
|
114
|
+
data.else_position[1] === ctx.currentLineIndex
|
|
115
|
+
) {
|
|
116
|
+
// jump to the "end if" statement
|
|
117
|
+
core.script.jump(data.end_position[0], data.end_position[1]);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// the else statement is not belong to the closest if statement, so throw error
|
|
122
|
+
throw `If statement: else statement not belong to the closest if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
123
|
+
});
|
|
124
|
+
// end if statement
|
|
125
|
+
core.script.use((ctx, next) => {
|
|
126
|
+
if (ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end if") {
|
|
127
|
+
return next();
|
|
128
|
+
}
|
|
129
|
+
if (
|
|
130
|
+
ctx.statementStack.length === 0 ||
|
|
131
|
+
ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
|
|
132
|
+
) {
|
|
133
|
+
throw `If statement: end if statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
134
|
+
}
|
|
135
|
+
ctx.endStatement("if");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// analyse statement
|
|
139
|
+
// if statement
|
|
140
|
+
core.script.useAnalyseStatement((ctx, next) => {
|
|
141
|
+
if (
|
|
142
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
143
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "if"
|
|
144
|
+
) {
|
|
145
|
+
return next();
|
|
146
|
+
}
|
|
147
|
+
if (ctx.slicedArgs.length < 2) {
|
|
148
|
+
throw `If statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
149
|
+
}
|
|
150
|
+
ctx.startStatement("if", {
|
|
151
|
+
start_position: [ctx.currentPath, ctx.currentLineIndex],
|
|
152
|
+
if_expression: ctx.rawtext.trim().slice(2).trim(),
|
|
153
|
+
elif_list: [],
|
|
154
|
+
else_position: null,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
// elif statement
|
|
158
|
+
core.script.useAnalyseStatement((ctx, next) => {
|
|
159
|
+
if (
|
|
160
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
161
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "elif"
|
|
162
|
+
) {
|
|
163
|
+
return next();
|
|
164
|
+
}
|
|
165
|
+
if (ctx.slicedArgs.length < 2) {
|
|
166
|
+
throw `If statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
167
|
+
}
|
|
168
|
+
// check if the closest statement is if statement
|
|
169
|
+
if (
|
|
170
|
+
ctx.statementStack.length === 0 ||
|
|
171
|
+
ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
|
|
172
|
+
) {
|
|
173
|
+
throw `If statement: elif statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
174
|
+
}
|
|
175
|
+
let data = ctx.statementStack[
|
|
176
|
+
ctx.statementStack.length - 1
|
|
177
|
+
][2] as unknown as IfStatementData;
|
|
178
|
+
data.elif_list.push({
|
|
179
|
+
position: [ctx.currentPath, ctx.currentLineIndex],
|
|
180
|
+
expression: ctx.rawtext.trim().slice(4).trim(),
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
// else statement
|
|
184
|
+
core.script.useAnalyseStatement((ctx, next) => {
|
|
185
|
+
if (
|
|
186
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
187
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "else"
|
|
188
|
+
) {
|
|
189
|
+
return next();
|
|
190
|
+
}
|
|
191
|
+
// check if the closest statement is if statement
|
|
192
|
+
if (
|
|
193
|
+
ctx.statementStack.length === 0 ||
|
|
194
|
+
ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
|
|
195
|
+
) {
|
|
196
|
+
throw `If statement: else statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
197
|
+
}
|
|
198
|
+
let data = ctx.statementStack[
|
|
199
|
+
ctx.statementStack.length - 1
|
|
200
|
+
][2] as unknown as IfStatementData;
|
|
201
|
+
data.else_position = [ctx.currentPath, ctx.currentLineIndex];
|
|
202
|
+
});
|
|
203
|
+
// end if statement
|
|
204
|
+
core.script.useAnalyseStatement((ctx, next) => {
|
|
205
|
+
if (ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end if") {
|
|
206
|
+
return next();
|
|
207
|
+
}
|
|
208
|
+
let data = ctx.endStatement("if") as unknown as IfStatementData;
|
|
209
|
+
data.end_position = [ctx.currentPath, ctx.currentLineIndex];
|
|
210
|
+
ctx.setStatementData(data, [...data.start_position]);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// while ... end while
|
|
214
|
+
|
|
215
|
+
// while statement start
|
|
216
|
+
core.script.use((ctx, next) => {
|
|
217
|
+
if (
|
|
218
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
219
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "while"
|
|
220
|
+
) {
|
|
221
|
+
return next();
|
|
222
|
+
}
|
|
223
|
+
let data = ctx.startStatement("while") as unknown as WhileStatementData;
|
|
224
|
+
// test if expression return boolean
|
|
225
|
+
let expression = data.while_expression;
|
|
226
|
+
let expression_res = core.script.evalExpression(expression);
|
|
227
|
+
if (typeof expression_res !== "boolean") {
|
|
228
|
+
throw `While statement: while expression must return boolean, at file [${data.start_position[0]}] line [${data.start_position[1]}]`;
|
|
229
|
+
}
|
|
230
|
+
if (!expression_res) {
|
|
231
|
+
ctx.endStatement("while");
|
|
232
|
+
core.script.jump(...data.end_position, false);
|
|
233
|
+
core.script.incrementNextPosition();
|
|
234
|
+
} else {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// while statement end
|
|
240
|
+
core.script.use((ctx, next) => {
|
|
241
|
+
if (
|
|
242
|
+
ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end while"
|
|
243
|
+
) {
|
|
244
|
+
return next();
|
|
245
|
+
}
|
|
246
|
+
// check if the closest statement is while statement
|
|
247
|
+
if (
|
|
248
|
+
ctx.statementStack.length === 0 ||
|
|
249
|
+
ctx.statementStack[ctx.statementStack.length - 1][0] !== "while"
|
|
250
|
+
) {
|
|
251
|
+
throw `While statement: end while statement must come after while statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
252
|
+
}
|
|
253
|
+
// jump to while statement start line (loop)
|
|
254
|
+
let data = ctx.statementStack[
|
|
255
|
+
ctx.statementStack.length - 1
|
|
256
|
+
][2] as unknown as WhileStatementData;
|
|
257
|
+
ctx.endStatement("while");
|
|
258
|
+
core.script.jump(...data.start_position, false);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// analyse while statement start
|
|
262
|
+
core.script.useAnalyseStatement((ctx, next) => {
|
|
263
|
+
if (
|
|
264
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
265
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "while"
|
|
266
|
+
) {
|
|
267
|
+
return next();
|
|
268
|
+
}
|
|
269
|
+
let while_expression = ctx.rawtext.trim().slice(5).trim();
|
|
270
|
+
if (while_expression === "") {
|
|
271
|
+
throw `While statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
272
|
+
}
|
|
273
|
+
ctx.startStatement("while", {
|
|
274
|
+
start_position: [ctx.currentPath, ctx.currentLineIndex],
|
|
275
|
+
while_expression,
|
|
276
|
+
end_position: [ctx.currentPath, ctx.currentLineIndex],
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// analyse while statement end
|
|
281
|
+
core.script.useAnalyseStatement((ctx, next) => {
|
|
282
|
+
if (
|
|
283
|
+
ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end while"
|
|
284
|
+
) {
|
|
285
|
+
return next();
|
|
286
|
+
}
|
|
287
|
+
let data = ctx.endStatement("while") as unknown as WhileStatementData;
|
|
288
|
+
data.end_position = [ctx.currentPath, ctx.currentLineIndex];
|
|
289
|
+
ctx.setStatementData(data, [...data.start_position]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// do ... end do until
|
|
293
|
+
|
|
294
|
+
// break command
|
|
295
|
+
core.script.use((ctx, next) => {
|
|
296
|
+
if (
|
|
297
|
+
ctx.slicedArgs[0].isQuoted ||
|
|
298
|
+
ctx.slicedArgs.length !== 1 ||
|
|
299
|
+
ctx.slicedArgs[0].str.toLowerCase() !== "break"
|
|
300
|
+
) {
|
|
301
|
+
return next();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// find the closest breakable statement
|
|
305
|
+
// (currently only while statement is breakable)
|
|
306
|
+
|
|
307
|
+
//(debug) output stack
|
|
308
|
+
// console.log("Stack="+JSON.stringify(ctx.statementStack));
|
|
309
|
+
|
|
310
|
+
let resIndex = -1
|
|
311
|
+
for (let i = ctx.statementStack.length - 1; i >= 0; i--) {
|
|
312
|
+
if (ctx.statementStack[i][0] === "while") {
|
|
313
|
+
resIndex = i;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (resIndex === -1) {
|
|
318
|
+
throw `Break command: break statement must come after while statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let data = ctx.statementStack[resIndex][2] as unknown as WhileStatementData;
|
|
322
|
+
core.debug.log(`Break command: break from ${ctx.statementStack[resIndex][0]} statement at file [${data.start_position[0]}] line [${data.start_position[1] + 1}]`);
|
|
323
|
+
core.script.jump(
|
|
324
|
+
...data.end_position,
|
|
325
|
+
false
|
|
326
|
+
);
|
|
327
|
+
core.script.incrementNextPosition();
|
|
328
|
+
|
|
329
|
+
// clear statement stack from breakable statement
|
|
330
|
+
ctx.statementStack.splice(resIndex + 1, ctx.statementStack.length - resIndex - 1);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
interface IfStatementData {
|
|
334
|
+
start_position: [path: string, index: number];
|
|
335
|
+
if_expression: string;
|
|
336
|
+
elif_list: ElifListItem[];
|
|
337
|
+
else_position: [path: string, index: number] | null;
|
|
338
|
+
end_position: [path: string, index: number];
|
|
339
|
+
}
|
|
340
|
+
interface ElifListItem {
|
|
341
|
+
position: [path: string, index: number];
|
|
342
|
+
expression: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
interface WhileStatementData {
|
|
346
|
+
start_position: [path: string, index: number];
|
|
347
|
+
while_expression: string;
|
|
348
|
+
end_position: [path: string, index: number];
|
|
349
|
+
}
|