Skip to content

Instantly share code, notes, and snippets.

@kriskowal
Forked from snowyu/kni-markdown-style.md
Last active December 12, 2018 01:54
Show Gist options
  • Save kriskowal/f048154af848f378c2bfa369794daa2d to your computer and use it in GitHub Desktop.
Save kriskowal/f048154af848f378c2bfa369794daa2d to your computer and use it in GitHub Desktop.
The new style markdown KNI

Starting with @snowyu’s proposal I infer some design principles:

  1. This language is designed to be easy to render using existing Markdown, YAML, and syntax highlighters.
  2. The language should break out to TypeScript that is evaluated in the scope of a global object that represents all of the play state.
  3. The Markdown script is a Markdown subset that uses inline backtick expressions to break out to an annotation language that carry semantics.
  4. The annotation language embeds the TypeScript grammar for evaluating expressions.

My iteration on this design adds some detail:

  1. 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 the then and else (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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment