Skip to content

Lists and generators

tilttek/generator

A catalog is read from decoded YAML or JSON documents. A document holds lists and generators maps by name, or a single list, or a single generator:

lists:
  name.elf.first: [Aelis, Lórien]
generators:
  name.elf:
    templates: "{{ pick('name.elf.first') }}"
list: weapon
entries: [sword, bow]
generator: weapon.name
templates: "{{ pick('weapon')|capitalize }}"

FileLoader reads .yaml, .yml and .json files, given one by one or by directory, searched in depth, in the order of their paths. Reader reads documents already decoded, by the name of their source. A document of the wrong shape is an InvalidDefinition that says where: inn.yaml > generators > inn > fields > staff: …. Two lists or two generators of the same name are an error, wherever they are.

Names

A name is made of letters, digits, _ and -, in parts joined by dots: name.fantasy.elf.female.first. The dots only order the names for people; the library does not read them. Reader::isName() says whether a text is a name.

Lists and entries

A list is a sequence of at least one entry. An entry is a text, or a map:

lists:
  weapon:
    - dagger
    - { text: épée, weight: 3, gender: f, price: 12, tags: { kind: blade, era: [old, middle] } }
KeyMeaning
textWhat the entry prints. Required in a map.
weightA whole number of at least 1, 1 by default: an entry of weight 3 comes three times as often.
tagsA map of tags, each with a value or a sequence of values, on which pick(…, where: …) filters.
any other keyA field: a scalar or a sequence of scalars, read in a template as entry.price.

A list may also be written as a map of its entries and its markov chain; see Markov lists.

What other generators write as ranges of indexes — "the first six adjectives only go with the first four nouns" — is written here with tags. A filter on a tag that no entry of the list carries is an error, not an empty draw.

The grammar filters read the fields gender (m, f or n), number (p for a word that is always plural), plural, and, for adjectives, m, f, mp and fp; a field may also be given as a tag of the same name.

Markov lists

A list with a markov chain does not give its entries back: a pick invents a new word from them, letter by letter. The chain learns, for each run of letters as long as its order, which letters follow and how often, counted by the weight of the entries; a word is written from the start of a word to its end, in lower case, and capitalised when every entry is.

lists:
  name.elf:
    markov: { order: 2, min_length: 4, max_length: 9 }
    entries:
      - { text: Aelis, tags: { gender: f } }
      - { text: Lórien, tags: { gender: f } }
      - { text: Thalion, tags: { gender: m } }
KeyMeaning
orderHow many letters choose the next one, from 1 to 6, 2 by default: a higher order copies more of the entries.
min_lengthThe shortest word, in letters, 3 by default.
max_lengthThe longest word, from the shortest to 64, 16 by default.
allow_entriesWhether an entry may come out as it is, false by default.

markov: true takes the defaults. pick('name.elf', where: {gender: 'f'}) invents from the entries whose tags match, and the word it invents carries the tags the pick asked for, so that |article and |agree read its gender. pick('name.elf', 3) invents three different words, and not: excludes words, compared without regard to case. Each attempt at a word counts as a pick: a pick tries MarkovChain::ATTEMPTS (50) times for each word it asks for, and a single pick that invents nothing new fails with ExhaustedList. A list of few or very short entries invents few words: lower the order, or allow the entries.

Generators

A generator has exactly one body, and may have a language, variables and a state:

KeyMeaning
templatesA template, or a sequence of templates, each a text or {template: …, weight: …}: one is chosen by weight for each result.
filtersWith templates only: filters without arguments applied in order to the whole text, such as upper_first, capitalize or elide.
fieldsA structured result: a map of fields, each a template or {generate: name, count: 3}.
mapA map of cells or a graph of nodes; see Maps.
varsTemplates by variable name, rendered once per result, in order, before the body; each sees the earlier ones.
localeThe language of the grammar filters, such as fr or en_CA; the generator's default otherwise.
stateValues carried from one result to the next: a table with a memory; see Tables with a memory.

The fields of a structured result are filled in order, and each sees the earlier ones as variables:

generators:
  inn:
    vars:
      keeper: "{{ generate('person.name') }}"
    fields:
      name: "The {{ pick('inn.animal')|capitalize }}"
      keeper: "{{ keeper }}"
      description: "{{ name }}, kept by {{ keeper }}"
      staff: { generate: person.name, count: 3 }
      menu: { generate: inn.dish, count: 4 }

A field filled by generate keeps the other generator's result as it is — a text, a structured result, a map — and a count gives a list of different results.

Neutral examples

The examples/ directory of the package holds a few generators that load with FileLoader and that the tests draw: syllabic names and names invented by a Markov chain, a yes-or-no oracle whose answers follow a chaos factor, an inn in French, a village on a square grid, a dungeon of rooms, and three tables with a memory — the weather, a journey and the exploration of a map of hexagons. They show the syntax; the lists of an application are its own. The tables/ directory holds random tables ready to draw; see Random tables.