questmark 0.0.35 → 0.0.36

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.
@@ -1,350 +1,413 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.parseMarkdown = exports.NoLinkBehaviour = void 0;
7
- const mdast_util_from_markdown_1 = __importDefault(require("mdast-util-from-markdown"));
8
- const unist_util_find_after_1 = __importDefault(require("unist-util-find-after"));
9
- const unist_util_remove_position_1 = __importDefault(require("unist-util-remove-position"));
10
- const unist_util_find_all_after_1 = __importDefault(require("unist-util-find-all-after"));
11
- const unist_util_find_all_between_1 = __importDefault(require("unist-util-find-all-between"));
12
- const unist_util_visit_parents_1 = __importDefault(require("unist-util-visit-parents"));
13
- const unist_util_visit_1 = __importDefault(require("unist-util-visit"));
14
- const unist_builder_1 = __importDefault(require("unist-builder"));
15
- require("array-flat-polyfill");
16
- const tzo_1 = require("tzo");
17
- var NoLinkBehaviour;
18
- (function (NoLinkBehaviour) {
19
- NoLinkBehaviour[NoLinkBehaviour["EXIT"] = 0] = "EXIT";
20
- NoLinkBehaviour[NoLinkBehaviour["LOOPBACK_TO_OPTIONS"] = 1] = "LOOPBACK_TO_OPTIONS"; // DEFAULT: Loopback to the options section if no link is found for an option and an option does not explicitly call an `exit` or `goto` effect.
21
- })(NoLinkBehaviour = exports.NoLinkBehaviour || (exports.NoLinkBehaviour = {}));
22
- function parseMarkdown(file_contents) {
23
- const tree = mdast_util_from_markdown_1.default(file_contents);
24
- const tokenizer = new tzo_1.Tokenizer();
25
- const root = unist_builder_1.default('questmarkDocument', { options: {} }, []);
26
- let last_once_directive_nr = 0;
27
- let noLinkBehaviour = NoLinkBehaviour.LOOPBACK_TO_OPTIONS;
28
- let retainWhitespace = false;
29
- unist_util_visit_parents_1.default(tree, 'heading', (x, ancestors) => {
30
- if (ancestors.length > 1) {
31
- return;
32
- }
33
- // capture name
34
- let name = "";
35
- unist_util_visit_parents_1.default(x, 'text', t => {
36
- name = `${name} ${t.value}`;
37
- });
38
- name = name.trim();
39
- // capture children belonging to this heading group
40
- const nextHeading = unist_util_find_after_1.default(tree, x, 'heading');
41
- const children = nextHeading !== null ? unist_util_find_all_between_1.default(tree, x, nextHeading) : unist_util_find_all_after_1.default(tree, x);
42
- if (name === "QUESTMARK-OPTIONS-HEADER") {
43
- root.options = getQuestmarkOptions(children);
44
- }
45
- else {
46
- root.children.push(createStateNode(name, children));
47
- }
48
- });
49
- function getQuestmarkOptions(children) {
50
- let code = '';
51
- unist_util_visit_1.default(unist_builder_1.default('bla', children), 'code', n => {
52
- code = `${code} ${n.value}`;
53
- });
54
- return JSON.parse(code.trim());
55
- }
56
- function createStateNode(name, children) {
57
- // create new state node
58
- const stateNode = unist_builder_1.default('state', { name, options: [] }, []);
59
- let found = false;
60
- let stateChildren = [];
61
- let remainingChildren = [];
62
- // phase 0: split children into stateChildren and remaining children.
63
- children.forEach(child => {
64
- if (child.type === 'list' && child.ordered === false) {
65
- found = true;
66
- }
67
- if (found) {
68
- remainingChildren.push(child);
69
- }
70
- else {
71
- stateChildren.push(child);
72
- }
73
- });
74
- stateNode.children = stateChildren;
75
- // phase 1: replace list nodes with their children, then flatten the list
76
- remainingChildren = remainingChildren.map(child => {
77
- if (child.type === 'list' && child.ordered === false) {
78
- return child.children;
79
- }
80
- return child;
81
- }).flat(1);
82
- // phase 2: reparent non-listItem nodes to the last-seen listItem.
83
- let lastSeenListItem = null;
84
- remainingChildren = remainingChildren.map(child => {
85
- if (child.type === 'listItem') {
86
- lastSeenListItem = child;
87
- return child;
88
- }
89
- else {
90
- lastSeenListItem.children.push(child);
91
- }
92
- }).filter(x => x !== undefined);
93
- // phase 3: build the options from the remainingChildren (now all listItems)
94
- const options = remainingChildren.map((li) => {
95
- // li's `link` / `text` children are the main links / options, but only the first item is considered (the others are effect nodes)
96
- // li's `inlineCode` children are either precondition or effect code blocks
97
- const optionNode = unist_builder_1.default("option", { preconditionChildren: null, effectChildren: null, text: null, link: null, directives: null });
98
- let effectNodes = [];
99
- let preconditionNodes = [];
100
- let text = [];
101
- let link = [];
102
- let directives = [];
103
- unist_util_visit_1.default(li, n => {
104
- if (text.length > 0 && n.type !== "paragraph") {
105
- if (n.type === "text" && n.value && n.value.trim().length === 0) {
106
- // whitespace only node!
107
- }
108
- else {
109
- effectNodes.push(n);
110
- }
111
- }
112
- if (n.type === "text") {
113
- if (n.value && n.value.trim().length > 0 && text.length === 0) {
114
- text.push(n.value);
115
- }
116
- }
117
- if (n.type === "link") {
118
- if (n.url && link.length === 0) {
119
- link.push(n.url);
120
- }
121
- }
122
- if (text.length === 0 && n.type === "inlineCode") {
123
- if (n.value.startsWith("@")) {
124
- // this contains directives!
125
- n.value.split(/\s+/).forEach(directive => directives.push(directive));
126
- }
127
- else {
128
- // this contains precondition nodes..
129
- preconditionNodes.push(n);
130
- }
131
- }
132
- });
133
- optionNode.preconditionChildren = preconditionNodes;
134
- optionNode.effectChildren = effectNodes;
135
- optionNode.directives = directives;
136
- optionNode.text = text.join(" ").trim();
137
- optionNode.link = link.join(" ");
138
- if (optionNode.link.length === 0) {
139
- optionNode.link = null;
140
- }
141
- if (optionNode.text.length === 0) {
142
- optionNode.text = null;
143
- }
144
- if (optionNode.directives.length === 0) {
145
- optionNode.directives = null;
146
- }
147
- if (optionNode.preconditionChildren.length === 0) {
148
- optionNode.preconditionChildren = null;
149
- }
150
- if (optionNode.effectChildren.length === 0) {
151
- optionNode.effectChildren = null;
152
- }
153
- return optionNode;
154
- });
155
- stateNode.options = options;
156
- return stateNode;
157
- }
158
- const qvmState = unist_builder_1.default("questmarkVMState", {
159
- stack: [],
160
- context: {},
161
- programList: [],
162
- labelMap: {},
163
- programCounter: 0,
164
- exit: false,
165
- pause: false
166
- }, []);
167
- const q = (a) => {
168
- qvmState.programList.push(a);
169
- };
170
- const visitorFunc = (node, index, parent) => {
171
- let fixText = (n) => {
172
- let text = "";
173
- if (retainWhitespace) {
174
- text = file_contents.substring(n.position.start.offset, n.position.end.offset);
175
- }
176
- else {
177
- text = n.value;
178
- }
179
- if (parent.type === "paragraph" && parent.children.indexOf(n) === parent.children.length - 1) {
180
- text = text + '\n\n';
181
- }
182
- text = text.replaceAll('\\*', '*').replaceAll('\\[', '[').replaceAll('\\]', ']').replaceAll('\\#', '#').replaceAll('\\`', '`').replaceAll('\\(', '(').replaceAll('\\)', ')');
183
- if (parent.type === "emphasis" || parent.type === "strong") {
184
- text = `*${text}*`;
185
- }
186
- return text;
187
- };
188
- if (parent === undefined && node.type !== "questmarkDocument") {
189
- return;
190
- }
191
- switch (node.type) {
192
- case "questmarkDocument":
193
- if (node.options) {
194
- if (node.options["initial-context"]) {
195
- Object.entries(node.options["initial-context"]).forEach(entry => {
196
- if (typeof entry[1] === "string") {
197
- q(tzo_1.pushString(entry[1]));
198
- }
199
- else if (typeof entry[1] === "number") {
200
- q(tzo_1.pushNumber(entry[1]));
201
- }
202
- q(tzo_1.pushString(entry[0]));
203
- q(tzo_1.invokeFunction("setContext"));
204
- });
205
- }
206
- if (node.options["initial-state"]) {
207
- q(tzo_1.pushString(node.options["initial-state"]));
208
- q(tzo_1.invokeFunction("goto"));
209
- }
210
- if (node.options["options"]) {
211
- if (node.options["options"]["no_link_behaviour"]) {
212
- if (node.options["options"]["no_link_behaviour"] === "exit") {
213
- noLinkBehaviour = NoLinkBehaviour.EXIT;
214
- }
215
- else if (node.options["options"]["no_link_behaviour"] === "loopback_to_options") {
216
- noLinkBehaviour = NoLinkBehaviour.LOOPBACK_TO_OPTIONS;
217
- }
218
- else {
219
- throw new Error(`Unknown 'options.no_link_behaviour': ${node.options["options"]["no_link_behaviour"]}`);
220
- }
221
- }
222
- if (node.options["options"]["whitespace"]) {
223
- if (node.options["options"]["whitespace"] === "retain") {
224
- retainWhitespace = true;
225
- }
226
- }
227
- }
228
- }
229
- break;
230
- case "state":
231
- const nodeLabel = node.name;
232
- const nodeOptionsLabel = node.name + "__options";
233
- qvmState.labelMap[nodeLabel] = qvmState.programList.length;
234
- unist_util_visit_1.default(node, visitorFunc); // parse children immediately, before we parse the node's options
235
- qvmState.labelMap[nodeOptionsLabel] = qvmState.programList.length; // add a label immediately after the node's options to be able to shortcut jump to in certain cases
236
- node.options.forEach(option => {
237
- let has_precondition = false;
238
- let has_once_directive = false;
239
- let once_directive_name = null;
240
- if (option.directives !== null && option.directives.includes('@once')) {
241
- has_once_directive = true;
242
- once_directive_name = `__once__${last_once_directive_nr}`;
243
- last_once_directive_nr += 1;
244
- }
245
- if (has_once_directive) {
246
- q(tzo_1.pushString(once_directive_name));
247
- q(tzo_1.invokeFunction("hasContext"));
248
- q(tzo_1.invokeFunction("jz"));
249
- q(tzo_1.invokeFunction("{")); // once directive block start
250
- }
251
- if (option.preconditionChildren !== null) {
252
- has_precondition = true;
253
- option.preconditionChildren.forEach(child => {
254
- if (child.type === "inlineCode") {
255
- tokenizer.parse(child.value).forEach(i => q(i));
256
- }
257
- else {
258
- throw new Error("preconditionChildren contain non-inlineCode elements. Aborting!");
259
- }
260
- });
261
- }
262
- if (has_precondition) {
263
- q(tzo_1.invokeFunction("jgz"));
264
- q(tzo_1.invokeFunction("{")); // precondition block start
265
- }
266
- if (option.text !== null) {
267
- if (option.text.includes("`")) {
268
- // TODO: support inlineCode in options. Can do via `concat`.
269
- console.warn("WARN: Option text contains a backtick. Inline code elements in option blocks is currently not supported!");
270
- }
271
- q(tzo_1.pushString(option.text));
272
- }
273
- q(tzo_1.invokeFunction("ppc")); // push address of effect body to stack
274
- q(tzo_1.pushNumber(4));
275
- q(tzo_1.invokeFunction("+"));
276
- q(tzo_1.invokeFunction("{")); // effect body start
277
- if (has_once_directive) {
278
- // set the once directive context variable immediatley
279
- q(tzo_1.pushNumber(1));
280
- q(tzo_1.pushString(once_directive_name));
281
- q(tzo_1.invokeFunction("setContext"));
282
- }
283
- if (option.effectChildren !== null) {
284
- option.effectChildren.forEach(child => {
285
- if (child.type === "inlineCode") {
286
- tokenizer.parse(child.value).forEach(i => q(i));
287
- }
288
- else if (child.type === "code") {
289
- if (child.lang === "comment") {
290
- return;
291
- }
292
- tokenizer.parse(child.value).forEach(i => q(i));
293
- }
294
- else {
295
- q(tzo_1.pushString(fixText(child)));
296
- q(tzo_1.invokeFunction("emit"));
297
- }
298
- });
299
- }
300
- if (option.link !== null) {
301
- q(tzo_1.pushString(option.link.substr(1)));
302
- q(tzo_1.invokeFunction("goto"));
303
- }
304
- if (noLinkBehaviour === NoLinkBehaviour.LOOPBACK_TO_OPTIONS) {
305
- q(tzo_1.pushString(nodeOptionsLabel)); // ensure that options with no link or "goto" effect loop back to the options
306
- q(tzo_1.invokeFunction("goto"));
307
- }
308
- else if (noLinkBehaviour === NoLinkBehaviour.EXIT) {
309
- q(tzo_1.invokeFunction("exit")); // ensure that options with no effect or link cause the VM to exit
310
- }
311
- q(tzo_1.invokeFunction("}")); // effect body end
312
- q(tzo_1.invokeFunction("response"));
313
- if (has_once_directive) {
314
- // when there was a once directive, ensure we close its block
315
- q(tzo_1.invokeFunction("}")); // once directive block end
316
- }
317
- if (has_precondition) {
318
- // when there was a precondition, ensure we close the precondition block
319
- q(tzo_1.invokeFunction("}")); // precondition block end
320
- }
321
- });
322
- q(tzo_1.invokeFunction("getResponse")); // getResponse is expected to pause the VM and push the relevant programlist index onto the stack
323
- q(tzo_1.invokeFunction("goto")); // goto whatever was pushed onto the stack by getResponse
324
- return "skip";
325
- // children have been parsed earlier
326
- break;
327
- case "inlineCode":
328
- case "code":
329
- if (node.lang === "comment") {
330
- break;
331
- }
332
- tokenizer.parse(node.value).forEach(i => q(i));
333
- break;
334
- case "text":
335
- q(tzo_1.pushString(fixText(node)));
336
- q(tzo_1.invokeFunction("emit"));
337
- break;
338
- default:
339
- break;
340
- }
341
- };
342
- unist_util_visit_1.default(root, visitorFunc);
343
- q(tzo_1.invokeFunction("exit")); // ensure we always exit at the end
344
- return {
345
- parsedMDFile: unist_util_remove_position_1.default(root, true),
346
- qvmState
347
- };
348
- }
349
- exports.parseMarkdown = parseMarkdown;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseMarkdown = exports.NoLinkBehaviour = void 0;
7
+ const mdast_util_from_markdown_1 = __importDefault(require("mdast-util-from-markdown"));
8
+ const unist_util_find_after_1 = __importDefault(require("unist-util-find-after"));
9
+ const unist_util_remove_position_1 = __importDefault(require("unist-util-remove-position"));
10
+ const unist_util_find_all_after_1 = __importDefault(require("unist-util-find-all-after"));
11
+ const unist_util_find_all_between_1 = __importDefault(require("unist-util-find-all-between"));
12
+ const unist_util_visit_parents_1 = __importDefault(require("unist-util-visit-parents"));
13
+ const unist_util_visit_1 = __importDefault(require("unist-util-visit"));
14
+ const unist_builder_1 = __importDefault(require("unist-builder"));
15
+ require("array-flat-polyfill");
16
+ const tzo_1 = require("tzo");
17
+ var NoLinkBehaviour;
18
+ (function (NoLinkBehaviour) {
19
+ NoLinkBehaviour[NoLinkBehaviour["EXIT"] = 0] = "EXIT";
20
+ NoLinkBehaviour[NoLinkBehaviour["LOOPBACK_TO_OPTIONS"] = 1] = "LOOPBACK_TO_OPTIONS"; // DEFAULT: Loopback to the options section if no link is found for an option and an option does not explicitly call an `exit` or `goto` effect.
21
+ })(NoLinkBehaviour = exports.NoLinkBehaviour || (exports.NoLinkBehaviour = {}));
22
+ var EmitOn;
23
+ (function (EmitOn) {
24
+ EmitOn[EmitOn["CLASSIC"] = 0] = "CLASSIC";
25
+ EmitOn[EmitOn["END_OF_LINE"] = 1] = "END_OF_LINE";
26
+ })(EmitOn || (EmitOn = {}));
27
+ function parseMarkdown(file_contents) {
28
+ const tree = mdast_util_from_markdown_1.default(file_contents);
29
+ const tokenizer = new tzo_1.Tokenizer();
30
+ const root = unist_builder_1.default('questmarkDocument', { options: {} }, []);
31
+ let last_once_directive_nr = 0;
32
+ let noLinkBehaviour = NoLinkBehaviour.LOOPBACK_TO_OPTIONS;
33
+ let retainWhitespace = false;
34
+ let emitOn = EmitOn.CLASSIC;
35
+ let missed_emits = 0;
36
+ let strip_leading_newlines = false;
37
+ unist_util_visit_parents_1.default(tree, 'heading', (x, ancestors) => {
38
+ if (ancestors.length > 1) {
39
+ return;
40
+ }
41
+ // capture name
42
+ let name = "";
43
+ unist_util_visit_parents_1.default(x, 'text', t => {
44
+ name = `${name} ${t.value}`;
45
+ });
46
+ name = name.trim();
47
+ // capture children belonging to this heading group
48
+ const nextHeading = unist_util_find_after_1.default(tree, x, 'heading');
49
+ const children = nextHeading !== null ? unist_util_find_all_between_1.default(tree, x, nextHeading) : unist_util_find_all_after_1.default(tree, x);
50
+ if (name === "QUESTMARK-OPTIONS-HEADER") {
51
+ root.options = getQuestmarkOptions(children);
52
+ }
53
+ else {
54
+ root.children.push(createStateNode(name, children));
55
+ }
56
+ });
57
+ function getQuestmarkOptions(children) {
58
+ let code = '';
59
+ unist_util_visit_1.default(unist_builder_1.default('bla', children), 'code', n => {
60
+ code = `${code} ${n.value}`;
61
+ });
62
+ return JSON.parse(code.trim());
63
+ }
64
+ function createStateNode(name, children) {
65
+ // create new state node
66
+ const stateNode = unist_builder_1.default('state', { name, options: [] }, []);
67
+ let found = false;
68
+ let stateChildren = [];
69
+ let remainingChildren = [];
70
+ // phase 0: split children into stateChildren and remaining children.
71
+ children.forEach(child => {
72
+ if (child.type === 'list' && child.ordered === false) {
73
+ found = true;
74
+ }
75
+ if (found) {
76
+ remainingChildren.push(child);
77
+ }
78
+ else {
79
+ stateChildren.push(child);
80
+ }
81
+ });
82
+ stateNode.children = stateChildren;
83
+ // phase 1: replace list nodes with their children, then flatten the list
84
+ remainingChildren = remainingChildren.map(child => {
85
+ if (child.type === 'list' && child.ordered === false) {
86
+ return child.children;
87
+ }
88
+ return child;
89
+ }).flat(1);
90
+ // phase 2: reparent non-listItem nodes to the last-seen listItem.
91
+ let lastSeenListItem = null;
92
+ remainingChildren = remainingChildren.map(child => {
93
+ if (child.type === 'listItem') {
94
+ lastSeenListItem = child;
95
+ return child;
96
+ }
97
+ else {
98
+ lastSeenListItem.children.push(child);
99
+ }
100
+ }).filter(x => x !== undefined);
101
+ // phase 3: build the options from the remainingChildren (now all listItems)
102
+ const options = remainingChildren.map((li) => {
103
+ // li's `link` / `text` children are the main links / options, but only the first item is considered (the others are effect nodes)
104
+ // li's `inlineCode` children are either precondition or effect code blocks
105
+ const optionNode = unist_builder_1.default("option", { preconditionChildren: null, effectChildren: null, text: null, link: null, directives: null });
106
+ let effectNodes = [];
107
+ let preconditionNodes = [];
108
+ let text = [];
109
+ let link = [];
110
+ let directives = [];
111
+ unist_util_visit_1.default(li, n => {
112
+ if (text.length > 0 && n.type !== "paragraph") {
113
+ if (n.type === "text" && n.value && n.value.trim().length === 0) {
114
+ // whitespace only node!
115
+ }
116
+ else {
117
+ effectNodes.push(n);
118
+ }
119
+ }
120
+ if (n.type === "text") {
121
+ if (n.value && n.value.trim().length > 0 && text.length === 0) {
122
+ text.push(n.value);
123
+ }
124
+ }
125
+ if (n.type === "link") {
126
+ if (n.url && link.length === 0) {
127
+ link.push(n.url);
128
+ }
129
+ }
130
+ if (text.length === 0 && n.type === "inlineCode") {
131
+ if (n.value.startsWith("@")) {
132
+ // this contains directives!
133
+ n.value.split(/\s+/).forEach(directive => directives.push(directive));
134
+ }
135
+ else {
136
+ // this contains precondition nodes..
137
+ preconditionNodes.push(n);
138
+ }
139
+ }
140
+ });
141
+ optionNode.preconditionChildren = preconditionNodes;
142
+ optionNode.effectChildren = effectNodes;
143
+ optionNode.directives = directives;
144
+ optionNode.text = text.join(" ").trim();
145
+ optionNode.link = link.join(" ");
146
+ if (optionNode.link.length === 0) {
147
+ optionNode.link = null;
148
+ }
149
+ if (optionNode.text.length === 0) {
150
+ optionNode.text = null;
151
+ }
152
+ if (optionNode.directives.length === 0) {
153
+ optionNode.directives = null;
154
+ }
155
+ if (optionNode.preconditionChildren.length === 0) {
156
+ optionNode.preconditionChildren = null;
157
+ }
158
+ if (optionNode.effectChildren.length === 0) {
159
+ optionNode.effectChildren = null;
160
+ }
161
+ return optionNode;
162
+ });
163
+ stateNode.options = options;
164
+ return stateNode;
165
+ }
166
+ const qvmState = unist_builder_1.default("questmarkVMState", {
167
+ stack: [],
168
+ context: {},
169
+ programList: [],
170
+ labelMap: {},
171
+ programCounter: 0,
172
+ exit: false,
173
+ pause: false
174
+ }, []);
175
+ const q = (a) => {
176
+ qvmState.programList.push(a);
177
+ };
178
+ const visitorFunc = (node, index, parent) => {
179
+ let getText = (n) => {
180
+ let text = "";
181
+ if (retainWhitespace) {
182
+ text = file_contents.substring(n.position.start.offset, n.position.end.offset);
183
+ }
184
+ else {
185
+ text = n.value;
186
+ }
187
+ return text;
188
+ };
189
+ let fixText = (n) => {
190
+ let text = getText(n);
191
+ if (parent.type === "paragraph" && parent.children.indexOf(n) === parent.children.length - 1) {
192
+ text = text + '\n\n';
193
+ }
194
+ text = text.replaceAll('\\*', '*').replaceAll('\\[', '[').replaceAll('\\]', ']').replaceAll('\\#', '#').replaceAll('\\`', '`').replaceAll('\\(', '(').replaceAll('\\)', ')');
195
+ if (parent.type === "emphasis" || parent.type === "strong") {
196
+ text = `*${text}*`;
197
+ }
198
+ if (strip_leading_newlines) {
199
+ text = text.replace(/^[\r\n]+/, "");
200
+ }
201
+ return text;
202
+ };
203
+ if (parent === undefined && node.type !== "questmarkDocument") {
204
+ return;
205
+ }
206
+ switch (node.type) {
207
+ case "questmarkDocument":
208
+ if (node.options) {
209
+ if (node.options["initial-context"]) {
210
+ Object.entries(node.options["initial-context"]).forEach(entry => {
211
+ if (typeof entry[1] === "string") {
212
+ q(tzo_1.pushString(entry[1]));
213
+ }
214
+ else if (typeof entry[1] === "number") {
215
+ q(tzo_1.pushNumber(entry[1]));
216
+ }
217
+ q(tzo_1.pushString(entry[0]));
218
+ q(tzo_1.invokeFunction("setContext"));
219
+ });
220
+ }
221
+ if (node.options["initial-state"]) {
222
+ q(tzo_1.pushString(node.options["initial-state"]));
223
+ q(tzo_1.invokeFunction("goto"));
224
+ }
225
+ if (node.options["options"]) {
226
+ if (node.options["options"]["no_link_behaviour"]) {
227
+ if (node.options["options"]["no_link_behaviour"] === "exit") {
228
+ noLinkBehaviour = NoLinkBehaviour.EXIT;
229
+ }
230
+ else if (node.options["options"]["no_link_behaviour"] === "loopback_to_options") {
231
+ noLinkBehaviour = NoLinkBehaviour.LOOPBACK_TO_OPTIONS;
232
+ }
233
+ else {
234
+ throw new Error(`Unknown 'options.no_link_behaviour': ${node.options["options"]["no_link_behaviour"]}`);
235
+ }
236
+ }
237
+ if (node.options["options"]["whitespace"]) {
238
+ if (node.options["options"]["whitespace"] === "retain") {
239
+ retainWhitespace = true;
240
+ }
241
+ }
242
+ if (node.options["options"]["emit_on"]) {
243
+ if (node.options["options"]["emit_on"] === "end_of_line") {
244
+ emitOn = EmitOn.END_OF_LINE;
245
+ }
246
+ }
247
+ if (node.options["options"]["strip_leading_newlines"]) {
248
+ if (node.options["options"]["strip_leading_newlines"] === true) {
249
+ strip_leading_newlines = true;
250
+ }
251
+ }
252
+ }
253
+ }
254
+ break;
255
+ case "state":
256
+ const nodeLabel = node.name;
257
+ const nodeOptionsLabel = node.name + "__options";
258
+ qvmState.labelMap[nodeLabel] = qvmState.programList.length;
259
+ unist_util_visit_1.default(node, visitorFunc); // parse children immediately, before we parse the node's options
260
+ // if there are any missing emits, perform them now
261
+ if (missed_emits > 0) {
262
+ while (missed_emits > 1) {
263
+ q(tzo_1.invokeFunction("rconcat"));
264
+ missed_emits -= 1;
265
+ }
266
+ missed_emits = 0;
267
+ q(tzo_1.invokeFunction("emit")); // need to still emit!
268
+ }
269
+ qvmState.labelMap[nodeOptionsLabel] = qvmState.programList.length; // add a label immediately after the node's options to be able to shortcut jump to in certain cases
270
+ node.options.forEach(option => {
271
+ let has_precondition = false;
272
+ let has_once_directive = false;
273
+ let once_directive_name = null;
274
+ if (option.directives !== null && option.directives.includes('@once')) {
275
+ has_once_directive = true;
276
+ once_directive_name = `__once__${last_once_directive_nr}`;
277
+ last_once_directive_nr += 1;
278
+ }
279
+ if (has_once_directive) {
280
+ q(tzo_1.pushString(once_directive_name));
281
+ q(tzo_1.invokeFunction("hasContext"));
282
+ q(tzo_1.invokeFunction("jz"));
283
+ q(tzo_1.invokeFunction("{")); // once directive block start
284
+ }
285
+ if (option.preconditionChildren !== null) {
286
+ has_precondition = true;
287
+ option.preconditionChildren.forEach(child => {
288
+ if (child.type === "inlineCode") {
289
+ tokenizer.parse(child.value).forEach(i => q(i));
290
+ }
291
+ else {
292
+ throw new Error("preconditionChildren contain non-inlineCode elements. Aborting!");
293
+ }
294
+ });
295
+ }
296
+ if (has_precondition) {
297
+ q(tzo_1.invokeFunction("jgz"));
298
+ q(tzo_1.invokeFunction("{")); // precondition block start
299
+ }
300
+ if (option.text !== null) {
301
+ if (option.text.includes("`")) {
302
+ // TODO: support inlineCode in options. Can do via `concat`.
303
+ console.warn("WARN: Option text contains a backtick. Inline code elements in option blocks is currently not supported!");
304
+ }
305
+ q(tzo_1.pushString(option.text));
306
+ }
307
+ q(tzo_1.invokeFunction("ppc")); // push address of effect body to stack
308
+ q(tzo_1.pushNumber(4));
309
+ q(tzo_1.invokeFunction("+"));
310
+ q(tzo_1.invokeFunction("{")); // effect body start
311
+ if (has_once_directive) {
312
+ // set the once directive context variable immediatley
313
+ q(tzo_1.pushNumber(1));
314
+ q(tzo_1.pushString(once_directive_name));
315
+ q(tzo_1.invokeFunction("setContext"));
316
+ }
317
+ if (option.effectChildren !== null) {
318
+ option.effectChildren.forEach(child => {
319
+ if (child.type === "inlineCode") {
320
+ tokenizer.parse(child.value).forEach(i => q(i));
321
+ }
322
+ else if (child.type === "code") {
323
+ if (child.lang === "comment") {
324
+ return;
325
+ }
326
+ tokenizer.parse(child.value).forEach(i => q(i));
327
+ }
328
+ else {
329
+ let t = getText(child);
330
+ q(tzo_1.pushString(fixText(child), t));
331
+ if (emitOn === EmitOn.CLASSIC || (emitOn === EmitOn.END_OF_LINE && t.replace(/[\t\f\v ]+$/, "").endsWith('\n'))) {
332
+ while (missed_emits > 0) {
333
+ q(tzo_1.invokeFunction("rconcat"));
334
+ missed_emits -= 1;
335
+ }
336
+ q(tzo_1.invokeFunction("emit"));
337
+ }
338
+ else {
339
+ missed_emits += 1;
340
+ }
341
+ }
342
+ });
343
+ }
344
+ // if there are any missing emits, perform them now before we close off this option effect body
345
+ if (missed_emits > 0) {
346
+ while (missed_emits > 1) {
347
+ q(tzo_1.invokeFunction("rconcat"));
348
+ missed_emits -= 1;
349
+ }
350
+ missed_emits = 0;
351
+ q(tzo_1.invokeFunction("emit")); // need to still emit!
352
+ }
353
+ if (option.link !== null) {
354
+ q(tzo_1.pushString(option.link.substr(1)));
355
+ q(tzo_1.invokeFunction("goto"));
356
+ }
357
+ if (noLinkBehaviour === NoLinkBehaviour.LOOPBACK_TO_OPTIONS) {
358
+ q(tzo_1.pushString(nodeOptionsLabel)); // ensure that options with no link or "goto" effect loop back to the options
359
+ q(tzo_1.invokeFunction("goto"));
360
+ }
361
+ else if (noLinkBehaviour === NoLinkBehaviour.EXIT) {
362
+ q(tzo_1.invokeFunction("exit")); // ensure that options with no effect or link cause the VM to exit
363
+ }
364
+ q(tzo_1.invokeFunction("}")); // effect body end
365
+ q(tzo_1.invokeFunction("response"));
366
+ if (has_once_directive) {
367
+ // when there was a once directive, ensure we close its block
368
+ q(tzo_1.invokeFunction("}")); // once directive block end
369
+ }
370
+ if (has_precondition) {
371
+ // when there was a precondition, ensure we close the precondition block
372
+ q(tzo_1.invokeFunction("}")); // precondition block end
373
+ }
374
+ });
375
+ q(tzo_1.invokeFunction("getResponse")); // getResponse is expected to pause the VM and push the relevant programlist index onto the stack
376
+ q(tzo_1.invokeFunction("goto")); // goto whatever was pushed onto the stack by getResponse
377
+ return "skip";
378
+ // children have been parsed earlier
379
+ break;
380
+ case "inlineCode":
381
+ case "code":
382
+ if (node.lang === "comment") {
383
+ break;
384
+ }
385
+ tokenizer.parse(node.value).forEach(i => q(i));
386
+ break;
387
+ case "text":
388
+ let t = getText(node);
389
+ q(tzo_1.pushString(fixText(node), t));
390
+ if (emitOn === EmitOn.CLASSIC || (emitOn === EmitOn.END_OF_LINE && t.replace(/[\t\f\v ]+$/, "").endsWith('\n'))) {
391
+ while (missed_emits > 0) {
392
+ q(tzo_1.invokeFunction("rconcat"));
393
+ missed_emits -= 1;
394
+ }
395
+ q(tzo_1.invokeFunction("emit"));
396
+ }
397
+ else {
398
+ missed_emits += 1;
399
+ }
400
+ break;
401
+ default:
402
+ break;
403
+ }
404
+ };
405
+ unist_util_visit_1.default(root, visitorFunc);
406
+ q(tzo_1.invokeFunction("exit")); // ensure we always exit at the end
407
+ return {
408
+ parsedMDFile: unist_util_remove_position_1.default(root, true),
409
+ qvmState
410
+ };
411
+ }
412
+ exports.parseMarkdown = parseMarkdown;
350
413
  //# sourceMappingURL=parseMarkdown.js.map