questmark 0.0.36 → 0.1.0
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/.github/workflows/docs.yml +56 -0
- package/bin/questmark +1 -1
- package/dist/browser/bundle.js +28365 -0
- package/dist/node/QuestVM.d.ts +1 -2
- package/dist/node/QuestVM.js +81 -83
- package/dist/node/QuestVM.js.map +1 -1
- package/dist/node/cli.js +33 -49
- package/dist/node/cli.js.map +1 -1
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +2 -14
- package/dist/node/index.js.map +1 -1
- package/dist/node/parseMarkdown.d.ts +18 -7
- package/dist/node/parseMarkdown.js +81 -77
- package/dist/node/parseMarkdown.js.map +1 -1
- package/docs/explanation/how-questmark-works.md +123 -0
- package/docs/explanation/why-questmark.md +72 -0
- package/docs/how-to/compile-to-bytecode.md +62 -0
- package/docs/how-to/play-a-document.md +57 -0
- package/docs/how-to/use-the-library.md +98 -0
- package/docs/index.md +61 -0
- package/docs/reference/api.md +135 -0
- package/docs/reference/cli.md +66 -0
- package/docs/reference/language.md +213 -0
- package/docs/tutorials/first-conversation.md +219 -0
- package/package.json +33 -31
- package/readme.md +55 -80
- package/tsconfig.json +8 -3
- package/tsconfig.webpack.json +9 -0
- package/webpack.config.cjs +49 -0
- package/dist/browser/QuestVM.d.ts +0 -13
- package/dist/browser/QuestVM.js +0 -89
- package/dist/browser/QuestVM.js.map +0 -1
- package/dist/browser/cli.d.ts +0 -2
- package/dist/browser/cli.js +0 -80
- package/dist/browser/cli.js.map +0 -1
- package/dist/browser/index.d.ts +0 -2
- package/dist/browser/index.js +0 -15
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/parseMarkdown.d.ts +0 -25
- package/dist/browser/parseMarkdown.js +0 -413
- package/dist/browser/parseMarkdown.js.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# How to play a Questmark document
|
|
2
|
+
|
|
3
|
+
This guide shows you how to run a Questmark document interactively.
|
|
4
|
+
|
|
5
|
+
## From the project directory
|
|
6
|
+
|
|
7
|
+
After cloning the repository and running `npm install`, play any local
|
|
8
|
+
document with:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm start -- --input path/to/document.md
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The document must have one of the extensions `.md`, `.qmd`, `.md.html`, or
|
|
15
|
+
`.qmd.html` (or be a compiled `.json` — see
|
|
16
|
+
[compile a document to bytecode](compile-to-bytecode.md)).
|
|
17
|
+
|
|
18
|
+
## From the published CLI
|
|
19
|
+
|
|
20
|
+
If you don't have a checkout of the repository, the CLI is published on npm:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx questmark --input path/to/document.md
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## From a URL
|
|
27
|
+
|
|
28
|
+
The input path can be an `http://` or `https://` URL, so you can play a
|
|
29
|
+
document that's hosted online:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx questmark --input https://ghcdn.rawgit.org/jorisvddonk/questmark/master/examples/self-describing.md
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## During a play session
|
|
36
|
+
|
|
37
|
+
- The interpreter prints the current state's text, then shows a menu of the
|
|
38
|
+
options currently available.
|
|
39
|
+
- Use the arrow keys to navigate and `Enter` to choose.
|
|
40
|
+
- Text that appears *after* an option line in the source is only shown once
|
|
41
|
+
that option has been chosen.
|
|
42
|
+
- Options can be hidden by preconditions, or appear only once when marked
|
|
43
|
+
`@once` — if an option is missing from the menu, that's the story telling
|
|
44
|
+
you it isn't available.
|
|
45
|
+
|
|
46
|
+
## Troubleshooting
|
|
47
|
+
|
|
48
|
+
- **"Missing input!"** — you didn't pass `--input`. Pass the path or URL of a
|
|
49
|
+
document.
|
|
50
|
+
- **The menu loops forever** — you chose an option that has neither a link nor
|
|
51
|
+
an `exit`/`goto` effect. With the default `loopback_to_options` behaviour the
|
|
52
|
+
interpreter returns to the menu. Change the behaviour in the document header
|
|
53
|
+
(see the [language reference](../reference/language.md)), or give the option
|
|
54
|
+
a link or an effect.
|
|
55
|
+
- **The interpreter quits immediately** — the document uses the `exit`
|
|
56
|
+
`no_link_behaviour`, or the state you entered runs an `exit` instruction.
|
|
57
|
+
This is often intended.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# How to embed Questmark in your own application
|
|
2
|
+
|
|
3
|
+
The library exposes two pieces: `parseMarkdown`, which compiles a Questmark
|
|
4
|
+
document into a Tzo VM state, and `QuestVM`, which interprets that state. This
|
|
5
|
+
guide wires them together in a small Node.js application.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install questmark
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Compile and run a document
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const { parseMarkdown, QuestVM } = require("questmark");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
|
|
19
|
+
const markdown = fs.readFileSync("conversation.md", "utf8");
|
|
20
|
+
const { qvmState } = parseMarkdown(markdown);
|
|
21
|
+
|
|
22
|
+
const vm = new QuestVM(
|
|
23
|
+
// emitFunction: called every time the document emits text.
|
|
24
|
+
(body) => process.stdout.write(String(body)),
|
|
25
|
+
|
|
26
|
+
// getResponseFunction: called whenever the document asks the player
|
|
27
|
+
// to choose. Return a Promise of the chosen option's id.
|
|
28
|
+
async (choices) => {
|
|
29
|
+
choices.forEach((c) => console.log(`${c.id}: ${c.title}`));
|
|
30
|
+
const id = Number(await askUser("Your choice: "));
|
|
31
|
+
return id;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// additionalFunctions: optional extra Tzo opcodes.
|
|
35
|
+
{}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
vm.loadVMState(qvmState);
|
|
39
|
+
vm.run();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## What the callbacks do
|
|
43
|
+
|
|
44
|
+
- **emitFunction** — invoked by the `emit` opcode with the text (or number) the
|
|
45
|
+
document wants to show. It can be called several times while rendering a
|
|
46
|
+
single state (for example `` You have `"cookies" getContext emit` cookies! ``).
|
|
47
|
+
Concatenate the calls to reconstruct the full text.
|
|
48
|
+
|
|
49
|
+
- **getResponseFunction** — invoked by the `getResponse` opcode with the list
|
|
50
|
+
of options currently available, as `{ title, id }` objects. The ids are
|
|
51
|
+
positional (0, 1, 2, ...). Return a `Promise` resolving to the chosen id;
|
|
52
|
+
the VM resumes execution from that option's effect.
|
|
53
|
+
|
|
54
|
+
- **additionalFunctions** — a map of extra opcode names to functions. Opcode
|
|
55
|
+
names beginning with `_` are ignored at compile time, so you can use
|
|
56
|
+
underscore-prefixed calls in documents as hooks for your own logic.
|
|
57
|
+
|
|
58
|
+
## Running in a browser
|
|
59
|
+
|
|
60
|
+
The webpack build produces a browser bundle at `dist/browser/bundle.js`. Load
|
|
61
|
+
it with a `<script>` tag and use the global `Questmark` object the same way:
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<script src="questmark/dist/browser/bundle.js"></script>
|
|
65
|
+
<script>
|
|
66
|
+
const { parseMarkdown, QuestVM } = Questmark;
|
|
67
|
+
// ...same API as above, wiring emitFunction to the DOM instead of stdout.
|
|
68
|
+
</script>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Handling errors
|
|
72
|
+
|
|
73
|
+
`QuestVM` extends `EventEmitter`. Runtime errors in the interpreter are emitted
|
|
74
|
+
on the `eventBus` under the `error` event:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
vm.eventBus.on("error", (err) => {
|
|
78
|
+
console.error("Interpreter error:", err);
|
|
79
|
+
vm.quit();
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The `eventBus` also emits `emit` and `response` events for every text emission
|
|
84
|
+
and option registration, which can be useful for logging or debug UIs.
|
|
85
|
+
|
|
86
|
+
## A note on preconditions
|
|
87
|
+
|
|
88
|
+
Context lookups with `getContext` throw if the key is not present, so always
|
|
89
|
+
initialize every variable you plan to read in the document's
|
|
90
|
+
`initial-context` (see the [language reference](../reference/language.md)).
|
|
91
|
+
|
|
92
|
+
## Further reading
|
|
93
|
+
|
|
94
|
+
- [API reference](../reference/api.md) — exact signatures and types.
|
|
95
|
+
- [Tutorial: write your first conversation](../tutorials/first-conversation.md) —
|
|
96
|
+
the basics of the language.
|
|
97
|
+
- The repository's `test/` directory contains more complete examples of
|
|
98
|
+
driving a `QuestVM` programmatically.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
|
|
4
|
+
hero:
|
|
5
|
+
name: Questmark
|
|
6
|
+
text: Dialogue trees and hypertext fiction in Markdown
|
|
7
|
+
tagline: A language, compiler, interpreter, and TypeScript library for conversation trees and interactive fiction — built on the Markdown everyone already knows.
|
|
8
|
+
actions:
|
|
9
|
+
- theme: brand
|
|
10
|
+
text: Start the tutorial
|
|
11
|
+
link: /tutorials/first-conversation
|
|
12
|
+
- theme: alt
|
|
13
|
+
text: Why Questmark?
|
|
14
|
+
link: /explanation/why-questmark
|
|
15
|
+
|
|
16
|
+
features:
|
|
17
|
+
- icon: 📝
|
|
18
|
+
title: Markdown-native
|
|
19
|
+
details: Every properly-structured Markdown document with headers and links is already a Questmark document. Prose stays readable; behaviour is added with small backticked code snippets.
|
|
20
|
+
- icon: 🧭
|
|
21
|
+
title: Conversation trees
|
|
22
|
+
details: States and options model dialogue graphs — like Star Control 2 — and quests, hypertext fiction, text adventures, and visual novels — like Space Rangers 2.
|
|
23
|
+
- icon: 🧠
|
|
24
|
+
title: Context and side-effects
|
|
25
|
+
details: A context bag tracks what the player has done, gating options with preconditions and driving branching narrative and in-world side effects.
|
|
26
|
+
- icon: ⚙️
|
|
27
|
+
title: Compiles to Tzo bytecode
|
|
28
|
+
details: Documents compile to a portable Tzo VMState that can be stored, shipped, and interpreted in a game, a CLI, or a browser.
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Documentation
|
|
32
|
+
|
|
33
|
+
Questmark's docs follow the [Diátaxis](https://diataxis.fr/) framework, which
|
|
34
|
+
splits documentation into four complementary modes of use:
|
|
35
|
+
|
|
36
|
+
| Mode | Audience | Purpose | Start here |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| [Tutorials](tutorials/first-conversation) | Learners | Learning-oriented lessons that take you by the hand | [Your first conversation](tutorials/first-conversation) |
|
|
39
|
+
| [How-to guides](how-to/play-a-document) | Doers | Goal-oriented steps for solving real problems | [Play a document](how-to/play-a-document) |
|
|
40
|
+
| [Reference](reference/language) | Everyone | Information-oriented descriptions of the machinery | [Language reference](reference/language) |
|
|
41
|
+
| [Explanation](explanation/why-questmark) | Everyone | Understanding-oriented background and context | [Why Questmark?](explanation/why-questmark) |
|
|
42
|
+
|
|
43
|
+
## Quick orientation
|
|
44
|
+
|
|
45
|
+
- **New to Questmark?** Start with the [tutorial](tutorials/first-conversation).
|
|
46
|
+
It takes about ten minutes and gets you writing and playing your first
|
|
47
|
+
conversation.
|
|
48
|
+
- **Trying to get something done?** The [how-to guides](how-to/play-a-document)
|
|
49
|
+
show you how to play a document, compile one to bytecode, and embed Questmark
|
|
50
|
+
in your own app.
|
|
51
|
+
- **Need a fact?** The [reference](reference/language) pages describe the
|
|
52
|
+
language, the CLI, and the library API precisely and completely.
|
|
53
|
+
- **Want to understand the design?** The [explanation](explanation/why-questmark)
|
|
54
|
+
pages cover why Questmark exists and how it works under the hood.
|
|
55
|
+
|
|
56
|
+
## Related projects
|
|
57
|
+
|
|
58
|
+
- [Tzo](https://github.com/jorisvddonk/tzo) — the stack-machine bytecode and VM
|
|
59
|
+
that Questmark compiles to.
|
|
60
|
+
- [questmark-webrenderer](https://github.com/jorisvddonk/questmark-webrenderer) —
|
|
61
|
+
render and play Questmark documents in the browser.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Questmark API reference
|
|
2
|
+
|
|
3
|
+
The library entry point (`src/index.ts`, built to `dist/node/`) exports the
|
|
4
|
+
compiler and the interpreter. All of the API below is available from the
|
|
5
|
+
package entry point (`import ... from "questmark"` / `require("questmark")`).
|
|
6
|
+
|
|
7
|
+
## `parseMarkdown(fileContents: string)`
|
|
8
|
+
|
|
9
|
+
Compiles a Questmark document into a Tzo VM state.
|
|
10
|
+
|
|
11
|
+
**Returns** `{ parsedMDFile, qvmState }`
|
|
12
|
+
|
|
13
|
+
- `parsedMDFile` — the parsed document as a unist tree, with position
|
|
14
|
+
information removed.
|
|
15
|
+
- `qvmState` — a `TzoVMState` (see below) ready to load into a `QuestVM`.
|
|
16
|
+
|
|
17
|
+
**Throws** on malformed option headers (for example an unknown
|
|
18
|
+
`no_link_behaviour`) or Tzo tokenizer errors.
|
|
19
|
+
|
|
20
|
+
## `QuestVM`
|
|
21
|
+
|
|
22
|
+
`class QuestVM extends VM` — an interpreter for compiled Questmark documents.
|
|
23
|
+
It extends Tzo's `VM` and adds the `emit`, `response`, and `getResponse`
|
|
24
|
+
opcodes that implement text output and choices.
|
|
25
|
+
|
|
26
|
+
### Constructor
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
new QuestVM(
|
|
30
|
+
emitFunction: (body: string | number) => void,
|
|
31
|
+
getResponseFunction: (choices: Choice[]) => Promise<number>,
|
|
32
|
+
additionalFunctions?: Functions
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `emitFunction` — called by the `emit` opcode for each piece of text output.
|
|
37
|
+
- `getResponseFunction` — called by the `getResponse` opcode whenever the
|
|
38
|
+
document asks the player to choose. Receives the currently available options
|
|
39
|
+
and must return a `Promise` resolving to the chosen option's `id`.
|
|
40
|
+
- `additionalFunctions` — a map of extra Tzo opcode names to functions, merged
|
|
41
|
+
into the function table.
|
|
42
|
+
|
|
43
|
+
### Events
|
|
44
|
+
|
|
45
|
+
`eventBus` is an `EventEmitter` with these events:
|
|
46
|
+
|
|
47
|
+
| Event | Payload | When |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `emit` | `string \| number` | Every text emission. |
|
|
50
|
+
| `response` | `{ response, pc }` | Every option registration. |
|
|
51
|
+
| `error` | `Error` | Runtime errors in the response flow. |
|
|
52
|
+
|
|
53
|
+
### Inherited methods (from Tzo `VM`)
|
|
54
|
+
|
|
55
|
+
| Method | Description |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `loadVMState(tzoVMState)` | Load a compiled VM state and prepare it for execution. |
|
|
58
|
+
| `run()` | Execute the program until the VM exits or suspends. |
|
|
59
|
+
| `quit()` | Mark the VM as exited. |
|
|
60
|
+
| `suspend()` | Pause execution (the `pause` opcode / `getResponse`). |
|
|
61
|
+
| `tick()` | Execute a single instruction. |
|
|
62
|
+
|
|
63
|
+
### Properties
|
|
64
|
+
|
|
65
|
+
`stack`, `context`, `programList`, `labelMap`, `programCounter`, `exit`,
|
|
66
|
+
`pause` — the interpreter state.
|
|
67
|
+
|
|
68
|
+
## `Choice`
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
interface Choice {
|
|
72
|
+
title: string;
|
|
73
|
+
id: number;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
An option offered to the player. `id` is the option's positional index within
|
|
78
|
+
the current state's choice list.
|
|
79
|
+
|
|
80
|
+
## `NoLinkBehaviour`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
enum NoLinkBehaviour {
|
|
84
|
+
EXIT = 0,
|
|
85
|
+
LOOPBACK_TO_OPTIONS = 1
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Behaviour when a chosen option has no link and no effect. Exported for host
|
|
90
|
+
applications; documents configure this via the header flag
|
|
91
|
+
`options.no_link_behaviour`.
|
|
92
|
+
|
|
93
|
+
## `TzoVMState`
|
|
94
|
+
|
|
95
|
+
The compiled representation produced by `parseMarkdown` and consumed by
|
|
96
|
+
`loadVMState`:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
interface TzoVMState {
|
|
100
|
+
stack: any[];
|
|
101
|
+
context: { [key: string]: string | number };
|
|
102
|
+
programList: Instruction[];
|
|
103
|
+
labelMap: { [label: string]: number };
|
|
104
|
+
programCounter: number;
|
|
105
|
+
exit: boolean;
|
|
106
|
+
pause: boolean;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## `Tokenizer`
|
|
111
|
+
|
|
112
|
+
Re-exported from Tzo. Parses a string of Tzo source code into an array of
|
|
113
|
+
instructions:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
new Tokenizer().parse("1 2 +") // -> [push-number 1, push-number 2, invoke +]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Full usage example
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const { parseMarkdown, QuestVM } = require("questmark");
|
|
123
|
+
const { qvmState } = parseMarkdown(md);
|
|
124
|
+
|
|
125
|
+
const vm = new QuestVM(
|
|
126
|
+
(text) => console.log(text),
|
|
127
|
+
async (choices) => {
|
|
128
|
+
const index = await pickFrom(choices);
|
|
129
|
+
return choices[index].id;
|
|
130
|
+
},
|
|
131
|
+
{}
|
|
132
|
+
);
|
|
133
|
+
vm.loadVMState(qvmState);
|
|
134
|
+
vm.run();
|
|
135
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Questmark CLI reference
|
|
2
|
+
|
|
3
|
+
The Questmark command line interface compiles and interprets Questmark
|
|
4
|
+
documents.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
questmark [options]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Run it from a checkout with `npm start -- [options]`, or from the published
|
|
13
|
+
package with `npx questmark [options]`.
|
|
14
|
+
|
|
15
|
+
## Options
|
|
16
|
+
|
|
17
|
+
| Option | Description |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `-V, --version` | Print the version number. |
|
|
20
|
+
| `-c, --clear` | Clear the console on each state. *(Declared but not yet implemented.)* |
|
|
21
|
+
| `--input <path>` | Load a source `.md`, `.qmd`, `.qmd.html`, `.md.html`, or `.json` file. `path` can be a local filesystem path or an `http(s)://` URL. |
|
|
22
|
+
| `--output <path>` | Emit the compiled VMState as JSON to `path`. |
|
|
23
|
+
| `--no-run` | Do not interpret the input; only parse and (optionally) emit output. |
|
|
24
|
+
| `-h, --help` | Display help. |
|
|
25
|
+
|
|
26
|
+
`--input` is required. Without it the CLI prints `Missing input!` and exits
|
|
27
|
+
with status 1.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
Play a local document:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
questmark --input examples/space_alien.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Compile a document to bytecode without running it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
questmark --input examples/space_alien.md --output vmstate.json --no-run
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Play a compiled document:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
questmark --input vmstate.json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Play a document hosted online:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
questmark --input https://example.com/document.md
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Exit codes
|
|
56
|
+
|
|
57
|
+
- `0` — success.
|
|
58
|
+
- `1` — missing `--input`, or a runtime/interpretation error.
|
|
59
|
+
|
|
60
|
+
## Behaviour
|
|
61
|
+
|
|
62
|
+
- `.json` inputs are treated as pre-compiled VM states and interpreted directly.
|
|
63
|
+
- `.md`, `.qmd`, `.md.html`, and `.qmd.html` inputs are compiled with
|
|
64
|
+
`parseMarkdown` before interpretation.
|
|
65
|
+
- Any other extension is an error.
|
|
66
|
+
- Inputs beginning with `http://` or `https://` are fetched before processing.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Questmark language reference
|
|
2
|
+
|
|
3
|
+
Questmark is a dialect of Markdown for dialogue trees and interactive fiction.
|
|
4
|
+
A document is a series of `#`-headed sections called **states**, connected by
|
|
5
|
+
links and code. The compiler turns the document into Tzo bytecode, which an
|
|
6
|
+
interpreter such as `QuestVM` executes.
|
|
7
|
+
|
|
8
|
+
## Document structure
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
# QUESTMARK-OPTIONS-HEADER
|
|
12
|
+
|
|
13
|
+
{ "questmark-spec": "1.0", "initial-state": "start" }
|
|
14
|
+
|
|
15
|
+
# start
|
|
16
|
+
|
|
17
|
+
Some text.
|
|
18
|
+
|
|
19
|
+
* An option
|
|
20
|
+
* Another option
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- The `QUESTMARK-OPTIONS-HEADER` section is optional. When present, its
|
|
24
|
+
indented code block must contain valid JSON (see below).
|
|
25
|
+
- Every other `# heading` defines a state.
|
|
26
|
+
- Text before the first list item is the state's text.
|
|
27
|
+
- List items are the state's **options**.
|
|
28
|
+
|
|
29
|
+
## Options header
|
|
30
|
+
|
|
31
|
+
The `QUESTMARK-OPTIONS-HEADER` section configures the document. Its fields:
|
|
32
|
+
|
|
33
|
+
| Field | Type | Meaning |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `questmark-spec` | string | Spec version. Currently `"1.0"`. |
|
|
36
|
+
| `initial-state` | string | Name of the state to start in. |
|
|
37
|
+
| `initial-context` | object | Initial values for the context bag. |
|
|
38
|
+
| `options` | object | Interpreter behaviour flags, below. |
|
|
39
|
+
|
|
40
|
+
### `options` flags
|
|
41
|
+
|
|
42
|
+
| Flag | Values | Default | Meaning |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| `no_link_behaviour` | `"loopback_to_options"`, `"exit"` | `"loopback_to_options"` | What happens when a chosen option has neither a link nor an `exit`/`goto` effect. |
|
|
45
|
+
| `whitespace` | `"retain"` | normal | Retain original source whitespace instead of the parsed text. |
|
|
46
|
+
| `emit_on` | `"end_of_line"` | `"classic"` | Only emit text when a line ends, instead of on every text node. |
|
|
47
|
+
| `strip_leading_newlines` | `true` | `false` | Strip leading newlines from emitted text. |
|
|
48
|
+
|
|
49
|
+
## States
|
|
50
|
+
|
|
51
|
+
A state is a `#`-headed section. Its name is the heading text. `#` characters
|
|
52
|
+
inside the heading are removed from the name.
|
|
53
|
+
|
|
54
|
+
A state contains:
|
|
55
|
+
|
|
56
|
+
- **Text** — paragraphs shown to the player on entry.
|
|
57
|
+
- **Options** — the list of things the player can do.
|
|
58
|
+
- **Code** — backticked Tzo code (see below), executed in place.
|
|
59
|
+
|
|
60
|
+
### State text and code
|
|
61
|
+
|
|
62
|
+
Plain paragraphs are emitted as text when the player enters the state.
|
|
63
|
+
Inline code in the text is executed as Tzo code at that point:
|
|
64
|
+
|
|
65
|
+
```markdown
|
|
66
|
+
You have `"cookies" getContext emit` cookies!
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This pushes the value of `cookies`, then emits it, so the player sees
|
|
70
|
+
`You have 3 cookies!`.
|
|
71
|
+
|
|
72
|
+
A code block with language `comment` is ignored entirely and never reaches the
|
|
73
|
+
player or the interpreter:
|
|
74
|
+
|
|
75
|
+
````markdown
|
|
76
|
+
```comment
|
|
77
|
+
This is a note for the human writers. It is never executed or shown.
|
|
78
|
+
```
|
|
79
|
+
````
|
|
80
|
+
|
|
81
|
+
## Options
|
|
82
|
+
|
|
83
|
+
Options are list items under a state's text. An option can have:
|
|
84
|
+
|
|
85
|
+
- a **text** (the label shown in the menu),
|
|
86
|
+
- a **link** (a `#state` target to jump to when chosen),
|
|
87
|
+
- **precondition** code (backticks before the text; the option is hidden when
|
|
88
|
+
the code leaves a non-positive value),
|
|
89
|
+
- a **directive** (backticks before the text starting with `@`),
|
|
90
|
+
- **effect** code (backticks after the text; executed when chosen),
|
|
91
|
+
- **post-option text** (paragraphs after the option line; emitted when chosen).
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
* A simple statement with no effect
|
|
97
|
+
* [A link to another state](#other)
|
|
98
|
+
* A statement that quits when chosen `exit`
|
|
99
|
+
* `"key" getContext` A conditional option (hidden unless "key" is positive)
|
|
100
|
+
* `@once` An option that can only be chosen once
|
|
101
|
+
* `@once` `"a" getContext` Once, and only when "a" is positive
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
When a player chooses an option, the interpreter:
|
|
105
|
+
|
|
106
|
+
1. runs the option's effect code,
|
|
107
|
+
2. emits the option's post-option text (interleaved with the effect),
|
|
108
|
+
3. follows the option's link (`goto`),
|
|
109
|
+
4. or, if there is no link and no effect, applies `no_link_behaviour`.
|
|
110
|
+
|
|
111
|
+
### Preconditions
|
|
112
|
+
|
|
113
|
+
Backticked code before an option's text is a precondition. The interpreter
|
|
114
|
+
executes it and offers the option only if the code leaves a value greater than
|
|
115
|
+
zero on the stack. Preconditions are how you build branching, story-aware menus
|
|
116
|
+
(see [context](#context)).
|
|
117
|
+
|
|
118
|
+
Note that `getContext` throws if the key is not present in the context, so
|
|
119
|
+
every key read in a precondition should be initialized in `initial-context`.
|
|
120
|
+
|
|
121
|
+
### The `@once` directive
|
|
122
|
+
|
|
123
|
+
An option marked `@once` is only offered once per play session. After the
|
|
124
|
+
player chooses it, it disappears from the menu. The interpreter implements this
|
|
125
|
+
with an internal context key (`__once__N`).
|
|
126
|
+
|
|
127
|
+
## Context
|
|
128
|
+
|
|
129
|
+
The **context** is a bag of named variables (numbers and strings) that tracks
|
|
130
|
+
what the player has done. It is initialized from `initial-context` and mutated
|
|
131
|
+
by effect code.
|
|
132
|
+
|
|
133
|
+
Common patterns:
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
`1 "door_open" setContext` ; store the number 1 under "door_open"
|
|
137
|
+
`"door_open" getContext` ; push the value of "door_open"
|
|
138
|
+
`"door_open" hasContext` ; push 1 if the key exists, else 0
|
|
139
|
+
`"door_open" delContext` ; remove the key
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use the `emit` opcode to show a context value in text, and `getContext` in a
|
|
143
|
+
precondition to gate options on it.
|
|
144
|
+
|
|
145
|
+
## Tzo code
|
|
146
|
+
|
|
147
|
+
Code inside backticks is Tzo bytecode written in reverse-Polish order: operands
|
|
148
|
+
first, operator last. Strings are double-quoted. Example:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
"NORMAL_HELLO_" 2 randInt 65 + charCode rconcat goto
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Opcodes
|
|
155
|
+
|
|
156
|
+
The standard opcode set (provided by Tzo and QuestVM):
|
|
157
|
+
|
|
158
|
+
| Opcode | Stack effect | Description |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| `emit` | `(value)` | Emit text/number to the player. |
|
|
161
|
+
| `exit` | | End the conversation. |
|
|
162
|
+
| `goto` | `(label \| pc)` | Jump to a label or program position. |
|
|
163
|
+
| `pause` | | Suspend the VM. |
|
|
164
|
+
| `getContext` | `(key) -> value` | Push the value of a context key (throws if absent). |
|
|
165
|
+
| `hasContext` | `(key) -> 0 \| 1` | Push whether the key exists. |
|
|
166
|
+
| `setContext` | `(key value)` | Store `value` under `key`. |
|
|
167
|
+
| `delContext` | `(key)` | Remove `key` from the context. |
|
|
168
|
+
| `eq` | `(a b) -> 0 \| 1` | Push `1` if `a == b`. |
|
|
169
|
+
| `gt` | `(a b) -> 0 \| 1` | Push `1` if `a > b`. |
|
|
170
|
+
| `lt` | `(a b) -> 0 \| 1` | Push `1` if `a < b`. |
|
|
171
|
+
| `and` | `(a b) -> 0 \| 1` | Logical and. |
|
|
172
|
+
| `or` | `(a b) -> 0 \| 1` | Logical or. |
|
|
173
|
+
| `not` | `(a) -> 0 \| 1` | Logical not. |
|
|
174
|
+
| `jgz` | `(a)` | Skip the next instruction if `a > 0`. |
|
|
175
|
+
| `jz` | `(a)` | Skip the next instruction if `a == 0`. |
|
|
176
|
+
| `+` / `plus` | `(a b) -> a+b` | Addition. |
|
|
177
|
+
| `-` / `min` | `(a b) -> a-b` | Subtraction. |
|
|
178
|
+
| `*` / `mul` | `(a b) -> a*b` | Multiplication. |
|
|
179
|
+
| `concat` | `(a b) -> "a"+"b"` | Concatenate (topmost value first). |
|
|
180
|
+
| `rconcat` | `(a b) -> "b"+"a"` | Concatenate (topmost value last). |
|
|
181
|
+
| `dup` | `(a) -> a a` | Duplicate the top of stack. |
|
|
182
|
+
| `pop` | `(a)` | Discard the top of stack. |
|
|
183
|
+
| `stacksize` | `() -> n` | Push the stack depth. |
|
|
184
|
+
| `randInt` | `(max) -> 0..max-1` | Push a random integer. |
|
|
185
|
+
| `charCode` | `(n) -> char` | Push the character for code point `n`. |
|
|
186
|
+
| `nop` | | Do nothing. |
|
|
187
|
+
| `{` / `}` | | Block delimiters used by `jgz`/`jz` to skip code. |
|
|
188
|
+
| `ppc` | `() -> pc` | Push the current program counter (used internally). |
|
|
189
|
+
|
|
190
|
+
The QuestVM adds the choice machinery, used by the compiler internally:
|
|
191
|
+
|
|
192
|
+
| Opcode | Description |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| `response` | Register the current option's effect position as a choice. |
|
|
195
|
+
| `getResponse` | Suspend the VM and ask the host for a choice. |
|
|
196
|
+
|
|
197
|
+
`optionEnabled`, `optionDisabled`, `disableOption`, and `enableOption` are
|
|
198
|
+
deprecated and should not be used.
|
|
199
|
+
|
|
200
|
+
Functions not registered in the VM are an error at runtime, unless their name
|
|
201
|
+
starts with `_`, in which case they are silently treated as a no-op (handy as
|
|
202
|
+
markers or hooks for host applications).
|
|
203
|
+
|
|
204
|
+
## Limitations and notes
|
|
205
|
+
|
|
206
|
+
- The spec is still informal, and the feature set is still evolving.
|
|
207
|
+
- `getContext` on an uninitialized key throws; initialize keys up front.
|
|
208
|
+
- An option that has neither a link nor an effect loops back to the menu by
|
|
209
|
+
default (`loopback_to_options`), which can appear to loop forever if the
|
|
210
|
+
option is always available. Set `no_link_behaviour` to `"exit"` or give the
|
|
211
|
+
option a link/effect if that's not what you want.
|
|
212
|
+
- Comments inside option text are not yet supported by the interpreter; a
|
|
213
|
+
backtick in an option label produces a warning.
|