Starting with @snowyu’s proposal I infer some design principles:
- This language is designed to be easy to render using existing Markdown, YAML, and syntax highlighters.
- The language should break out to TypeScript that is evaluated in the scope of a global object that represents all of the play state.
- The Markdown script is a Markdown subset that uses inline backtick expressions to break out to an annotation language that carry semantics.
- The annotation language embeds the TypeScript grammar for evaluating expressions.
My iteration on this design adds some detail:
- We use a Markdown variant that forbids arbitrary HTML, adds semantics to list item notation, and uses backticks for annotations.
The annotation language is:
->name
is a goto->name()
is a gosub (goto subroutine)<-
is a return from gosub or end of play@name
is a label@name()
is a subroutine label( expression : TypeScriptExpression )
is an expression that will have output that is rendered inline.if expression : TypeScriptExpression
indicates that the following items are thethen
andelse
(if present) clauses.switch expression : TypeScriptExpression
indicates that the following items are cases of a switch block.
The grammar for the leading annotation under a switch block is:
expression : TypeScriptExpression
and must correspond to a case.- A final term without an annotation is implicitly the default case.
My overall impression is that this would be a neat language.
This language would be better for programmers than it would be for non-programmer authors, in comparison to Ink or Kni.
Embedding TypeScript has the advantage that you can reuse its grammar and transpiler tooling. It does however limit your options for overloading semantics, like the -1arrow
notation on an option which in Kni both expresses an effect and a constraint (arrow > 0).
Embedding TypeScript would also require a JavaScript engine to be available or embedded in any runtime. Kni compiles to a JSON VM language that could be executed, for example, by a Go virtual machine, which is a door I want to keep open for Kni for multiplayer stories possibly running arbitrary Kni scripts. For this, I’ve avoided coupling to a higher level language.
---
// the defaults front-matter config: YAML
type:
gold:
type: integer
arrow: integer
hits: number
health:
type: integer
min: 0
max: 100
my:
gold: 2
arrow: 0
health: 100
shop:
gold: 0
arrow: 100
---
# `@main` A Wonder Adventure Story for You
You enter the fletcher’s shop. The fletcher beckons, “There are arrows for sale
and a range out back you can try your skills and maybe win your fortune. For each
hit, you win more gold!”
## `@shop` A Mysterious Shop
You have `(pluralize(my.arrow, 'arrow'))` `(my.arrow ? 'and' : 'but')` `(pluralize(my.gold, 'gold'))`.
+ `(--my.gold, my.arrow += 3)`
[You b[B]uy 3 arrows for a gold piece. ]
+ `(++my.gold, my.arrow -= 4)`
[You s[S]ell 4 arrows for a gold piece. ]
+ [You walk through the door to [Visit] the archery range. ]
`->range`
+ [Leave the store. ]
You depart the store through the back door with `(pluralize(my.gold, 'gold'))`.
`if (my.hits)`
- All told you scored `(count(hits))`.
`<-`
>
## `@range` The Archery Range
You have `(pluralize(my.arrow, 'arrow'))`.
+ `(--my.arrow)`
[You s[S]hoot an arrow[.]]
- `switch (Math.random(4))`
- `1` and hit the target, winning `my.hits? 'another' : 'a'` gold piece!
`++my.hits`
- `4` and miss.
- `(my.arrow += 3)`
+ [You r[R]eturn to the archery shop. ]
`->shop`
>
```ts
// the embed script supported.
import { pluralize as pluralizeWord } from 'pluralize';
function pluralize(value: number, word: string) {
let result;
if (value <= 0) {
result = `none ${word}`;
} else if (value > 1) {
result = `${value}`
result += pluralizeWord(word)
} else {
result = `${value} ${word}`
}
return result;
}