Skip to content

Templates

ionous edited this page Jan 8, 2021 · 2 revisions

Templates

Scripts use templated text as shorthands for sequences of commands. The templates are compiled into commands -- the runtime never sees the original template text.

Syntax

Templated text begins and ends with curly-braces.

"{example}"

Templates can refer to predefined commands or to objects by their names. An object name is preceded by a dot (.) and, if the names would normally contain spaces, each set of spaces must be replaced by a single underscore.

For example, not "messy bedroom", but rather: "{.messy_bedroom}"

Sayable objects

Object names can be printed quickly and easily.

ex. c.Cmd("say", "You see the {.lantern}.")
prints: "You see the cast iron lantern."

Whitespace control

Whitespace before or after brackets can be removed with trim (tilde) operators.

"abc   {~ }" is the same as "abc{}"
"{ ~}   abc" is the same as "{}abc"

Calling functions

a command separator ('!,':','?') is needed to execute a function call. ( note: there is no difference between command separators. it's purely whatever looks good. )

{pluralize: 'lamp'}
{upper_the? .soldiers} 
{13|print_num!}

multiple parameters are space separated.

pipe ('|') makes the output of the bits on the left a parameter of the bits on the right. For instance:

{print_name: object|buffer:|pluralize:}

would pluralize the result of buffering the printing of the object's name.

alternatively, sub-expressions can pass the results of functions to functions:

{print_num: {5+5}}

Object access

Uppercase names search for the specified object globally, lowercase names search upwards through scope.

{A}

refers to the one and only object named "A", while:

{a.b.c}

might refer to some function parameter named "a".

Object property access

{a.b.c}

If-else

{if ...}a{end}
{if ...}a{else}b{end}
{if ...}a{elsif ...}b{end}

{unless ...}{end}
{unless ...}{otherwise}b{end}
{unless ...}{otherwiseIf ...}b{end}


{if 7 >= 8}

Cycles, etc.

{cycle}a{or}b{or}c{end}
{once}a{or}b{or}c{end}
{shuffle}a{or}b{or}c{end}

Math, etc.

{5 + .A.num * .B.num % 7}

Logic, etc.

{.a and (.b or {isNot: .c})}

Builtins

Not supported yet. These are commands which do not require "go" to activate them.

ex. c.Cmd("say", "You can only just make out {a whatever}.")

might print:

"You can only just make out a lamp-post.", or
"You can only just make out Trevor."

Depending on the object in question.

Uppercase vs Lowercase names

A capitalized name in a template always looks first for an object of that name. For example, if there was an object named "Score", c.Cmd("set text", "story", "status right", "{Score}") would attempt to find it. Using a lowercase "{score}" would cause the template to look first for a property inside of story called score.

Background

Even without templates, iffy allows simple string shortcuts. For instance, to get the property "prop" from an object "example", a script can specify: c.Cmd("get", "example", "prop"). The string "example" gets turned into core.Object{"example"} automatically.

With those shortcuts, casing doesn't matter. "Example" and "example" both work. Templates, however, incorporate the scope of the object in which they are being evaluated, which introduces the possibility of naming conflicts.

Capitalization, therefore, is used to change the manner in which names get evaluated.

Templates as properties

Templates can be used anywhere an "eval" ( TextEval, NumberEval, etc. ) is used. That is to say, they can be used to compute the values of most commands, and they can be attached to object properties ( so long as the property was declared as an eval. )

Why custom templates?

Since there are many good off-the-shelf template systems capable of formatting text or html, why write a custom solution?

Most interactive fiction stories seem to make do with rather limited templates: no loops, no filters, no blocks, no variables. Instead, these stories rely on selection statements such as "cycle", "shuffle", etc. which vary the output of the template over time.

And yet, most off the shelf systems are heavily focused on loops and blocks, and most do not provide built-in commands for things like cycling. Moreover, most systems do not support adding new multi-part tokens in a standard way, so custom processing would be needed above and beyond the initial integration.

Basically, the needs of interactive fiction and the needs of text/html templates are a mismatch.

Additionally:

  1. A custom solution allows the runtime to use one style of execution: iffy commands. ( Although, arguably, the ast of a compiled template could be "recompiled" to build iffy commands. )

  2. Most template solutions require the full set of objects and their values up front. eg. in a map. The set of possible objects and their values for iffy is its whole world. Although the first version of iffy ( aka sashimi ) did store its objects in a map, it would require fundamental changes to this version to do so. ( Recompiling templates to iffy commands could mitigate this. )

  3. Templates often are optimized for execution speed over parsing speed. Since iffy has its own execution engine, we mostly care about parsing speed.

Why curly braces?

Iffys templates use {} for their bounds. Most template systems use multiple characters, and many use some sort of character preceding the brackets. These things make good sense for templates which consist of large blocks of text.

Iffy's templates tend to be at most a few lines, and most often just one line. Having templates flow with text rather than standout seems to work better.

Some examples:

  • inform [...]
  • ruby, coffeescript: #{...}
  • go, mustache, handlebars: {{ }}
  • C#, python: $"... {}"
  • perl, php, javascript, etc: ${}; and, many seem to allow either $var or ${expression}

Square brackets are reserved for future use with globs ( see below. )

Glob matcher

In the future, square brackets will indicate phrase matching much like Inform7.

ex. say "[ 5 in words ]"

From inform:

(something - number) in words
(something - time) in words
if (c - condition)

Phrases as per the above will be split into a "glob list". Each element in the list contains a word, and an optional a list of continuing words or a terminal symbol. Each list is sorted by element word. There is a special element ( the glob ) with the word "*", which matches any series of word. The glob can have type filters which are applied a phrase has been matched.

Matching involves splitting a phrase into tokens ( whitespace separated ), and scanning through the glob list, recursively descending with each matched word. ( To match a glob, eat a token, test the remaining tokens, and continue on until a match has been found. ) The results of matching are the set of gobbled tokens for each glob, and the terminal symbol of the token series.

The results are then adapted to the existing express token structure.