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.
Files changed (103) hide show
  1. package/dist/async/index.js +162 -0
  2. package/dist/async/zeppos_timer.js +58 -0
  3. package/dist/audio/index.js +260 -0
  4. package/dist/config/index.js +57 -0
  5. package/dist/debug/index.js +8 -0
  6. package/dist/index.js +103 -0
  7. package/dist/platform/index.js +1 -0
  8. package/dist/plugins/basic_command/$.js +8 -0
  9. package/dist/plugins/basic_command/audio.js +40 -0
  10. package/dist/plugins/basic_command/basic.js +124 -0
  11. package/dist/plugins/basic_command/character.js +112 -0
  12. package/dist/plugins/basic_command/conditional.js +260 -0
  13. package/dist/plugins/basic_command/config.js +22 -0
  14. package/dist/plugins/basic_command/decorator.js +24 -0
  15. package/dist/plugins/basic_command/eval.js +67 -0
  16. package/dist/plugins/basic_command/img.js +249 -0
  17. package/dist/plugins/basic_command/index.js +22 -0
  18. package/dist/plugins/basic_command/menu.js +140 -0
  19. package/dist/plugins/global_gesture/index.js +25 -0
  20. package/dist/plugins/transform/animation.js +440 -0
  21. package/dist/plugins/transform/commands.js +38 -0
  22. package/dist/plugins/transform/example_profiles.js +32 -0
  23. package/dist/plugins/transform/hz_anime.js +211 -0
  24. package/dist/plugins/transform/index.js +93 -0
  25. package/dist/script/index.js +537 -0
  26. package/dist/script/readscript.js +15 -0
  27. package/dist/script/strtools.js +157 -0
  28. package/dist/storage/decorator.js +260 -0
  29. package/dist/storage/fs.js +96 -0
  30. package/dist/storage/index.js +442 -0
  31. package/dist/system/index.js +144 -0
  32. package/dist/ui/index.js +535 -0
  33. package/dist/utils/path.js +289 -0
  34. package/license.txt +202 -0
  35. package/package.json +26 -0
  36. package/src/async/index.ts +124 -0
  37. package/src/async/zeppos_timer.js +65 -0
  38. package/src/audio/index.ts +224 -0
  39. package/src/config/index.ts +80 -0
  40. package/src/debug/index.ts +11 -0
  41. package/src/index.ts +122 -0
  42. package/src/platform/index.ts +158 -0
  43. package/src/plugins/basic_command/$.ts +11 -0
  44. package/src/plugins/basic_command/audio.ts +53 -0
  45. package/src/plugins/basic_command/basic.ts +145 -0
  46. package/src/plugins/basic_command/character.ts +144 -0
  47. package/src/plugins/basic_command/conditional.ts +349 -0
  48. package/src/plugins/basic_command/config.ts +29 -0
  49. package/src/plugins/basic_command/decorator.ts +29 -0
  50. package/src/plugins/basic_command/eval.ts +88 -0
  51. package/src/plugins/basic_command/img.ts +317 -0
  52. package/src/plugins/basic_command/index.ts +24 -0
  53. package/src/plugins/basic_command/menu.ts +178 -0
  54. package/src/plugins/global_gesture/index.ts +29 -0
  55. package/src/plugins/transform/animation.ts +542 -0
  56. package/src/plugins/transform/commands.ts +53 -0
  57. package/src/plugins/transform/example_profiles.ts +36 -0
  58. package/src/plugins/transform/hz_anime.ts +214 -0
  59. package/src/plugins/transform/index.ts +141 -0
  60. package/src/plugins/transform/readme.md +1 -0
  61. package/src/script/index.ts +623 -0
  62. package/src/script/readscript.ts +17 -0
  63. package/src/script/strtools.ts +159 -0
  64. package/src/storage/decorator.ts +473 -0
  65. package/src/storage/fs.ts +104 -0
  66. package/src/storage/index.ts +541 -0
  67. package/src/system/index.ts +95 -0
  68. package/src/ui/index.ts +699 -0
  69. package/src/utils/path.js +338 -0
  70. package/tsconfig.json +111 -0
  71. package/types/async/index.d.ts +24 -0
  72. package/types/async/zeppos_timer.d.ts +14 -0
  73. package/types/audio/index.d.ts +64 -0
  74. package/types/config/index.d.ts +9 -0
  75. package/types/debug/index.d.ts +6 -0
  76. package/types/index.d.ts +41 -0
  77. package/types/platform/index.d.ts +134 -0
  78. package/types/plugins/basic_command/$.d.ts +2 -0
  79. package/types/plugins/basic_command/audio.d.ts +2 -0
  80. package/types/plugins/basic_command/basic.d.ts +3 -0
  81. package/types/plugins/basic_command/character.d.ts +2 -0
  82. package/types/plugins/basic_command/conditional.d.ts +2 -0
  83. package/types/plugins/basic_command/config.d.ts +2 -0
  84. package/types/plugins/basic_command/decorator.d.ts +2 -0
  85. package/types/plugins/basic_command/eval.d.ts +2 -0
  86. package/types/plugins/basic_command/img.d.ts +2 -0
  87. package/types/plugins/basic_command/index.d.ts +2 -0
  88. package/types/plugins/basic_command/menu.d.ts +2 -0
  89. package/types/plugins/global_gesture/index.d.ts +2 -0
  90. package/types/plugins/transform/animation.d.ts +131 -0
  91. package/types/plugins/transform/commands.d.ts +7 -0
  92. package/types/plugins/transform/example_profiles.d.ts +2 -0
  93. package/types/plugins/transform/hz_anime.d.ts +51 -0
  94. package/types/plugins/transform/index.d.ts +13 -0
  95. package/types/script/index.d.ts +123 -0
  96. package/types/script/readscript.d.ts +2 -0
  97. package/types/script/strtools.d.ts +31 -0
  98. package/types/storage/decorator.d.ts +41 -0
  99. package/types/storage/fs.d.ts +1 -0
  100. package/types/storage/index.d.ts +86 -0
  101. package/types/system/index.d.ts +35 -0
  102. package/types/ui/index.d.ts +167 -0
  103. 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
+ }