multilinear-parser 0.4.2

A parser for the multilinear story systems
Documentation

Multilinear Parser

Crates.io Docs.rs

A companion parser for the multilinear crate that converts a human-readable format into constrained parallel aspects.

Features

  • 📝 Markdown-inspired syntax
  • ↔️ State transition declarations (from > to)
  • 🧩 Logical condition combinations (& and |)
  • 🗂️ Header-based event grouping with hierarchical namespaces
  • 📁 Recursive directory parsing for multi-file projects
  • ⚙️ External aspect default configuration files
  • 🚦 Automatic aspect/value management

Syntax

The basic syntax looks like this:

# [event]
[aspect]: [required_value]
[aspect]: [current_value] > [new_value]

For naming events, aspects and values, only alphanumeric characters, spaces and - and _ are allowed. The names will be trimmed. Empty names are allowed and are considered as the default values. But you can also specify an additonal file to set custom default values.

Namespaces via Headers

Events can be grouped using headers (#), subheaders (##), etc. The header hierarchy creates a namespace for each event, which is useful for organizing complex stories:

# Chapter 1

## Scene A

### Event

...

This creates the namespace Chapter 1, Scene A for the Event.

Explanation

A simple event could be defined like this:

# Look around in livingroom

place: livingroom

Looking around in the livingroom is only possible if you are in the livingroom. This event doesn't change anything. Usually you want to change at least one aspect.

This could be done like this:

# Go from livingroom to kitchen

place: livingroom > kitchen

If you want to go from the livingroom to the kitchen, you have to be in the livingroom and afterwards you will be in the kitchen.

Technically every condition is a change of the aspect. The previous notation is only short for a change with the same start and end value.

So staying in the same place could also be written like this:

# Look around in livingroom

place: livingroom > livingroom

It's also allowed to require multiple conditions to be fulfilled:

# Change into pajamas

place: bedroom
clothes: casual > pajamas

You can only change into pajamas if you're in your bedroom and wear casual clothes. Afterwards you will still be in the bedroom, but wear pajamas.

So if there are multiple lines, all of these conditions have to be fulfilled. It's not allowed to use conditions of the same aspect multiple times.

But it's also possible to have multiple alternative conditions for the same event by adding an empty line between the conditions:

# Sleep

place: bedroom

place: livingroom

You can sleep in the bedroom (in your bed) and in the livingroom (on the couch).

In this case, the aspect for both alternatives has to be the same.

It's also possible to have multiple sets of conditions, which might be fulfilled. And they might also lead to different outcomes.

# Call mom

place: bedroom
mom place: livingroom > bedroom

place: kitchen
mom place: livingroom > kitchen

When you're at home, and mom is in the livingroom, you can call her to come to your current room.

In most cases it's useful to use the same aspects in all sets of conditions. But in some cases it's allowed that some aspects only appear in one condition.

Logical expressions

It's also allowed to use logical expressions using & and | in single lines. By default, & has a higher precedence than |, but it's allowed to do explicit grouping using parentheses.

Everything can also be written in a single line using logical expressions.

The previous examples would then look like this:

# Look around in livingroom

place: livingroom

# Go from livingroom to kitchen

place: livingroom > kitchen

# Change into pajamas

place: bedroom & clothes: casual > pajamas

# Sleep

place: bedroom | place: livingroom

# Call mom

place: bedroom & mom place: livingroom > bedroom | place: kitchen & mom place: livingroom > kitchen

But when using this system, some things could even be simplified.

For example when having a shared condition:

# Watch TV

place: bedroom
time: afternoon

place: livingroom
time: afternoon

place: dining room
time: afternoon

You are only allowed to watch TV in the afternoon. And there are multiple rooms at home, but only some of them have a TV.

When using logical expressions, this could be simplified to look like this:

# Watch TV

place: bedroom | place: livingroom | place: dining room
time: afternoon

Since this uses the same aspect multiple times for an expression, there's another operator ;, which acts like |, but belongs to the specific aspect.

So the previous example could be simplified even further:

# Watch TV

place: bedroom; livingroom; dining room
time: afternoon

Precedence rules

  1. ; - Aspect-specific OR
  2. & - Explicit AND
  3. | - Explicit OR
  4. Newlines - Implicit AND
  5. Empty lines - Implicit OR

Example showing precedence:

# Complex Event

a: 1 | b: 2 & c: 3
d: 4

a: 5

This could also be represented in a single line with explicit grouping using parentheses like this:

# Complex Event

((a: 1 | (b: 2 & c: 3)) & d: 4) | a: 5

Usage

Here you'll see some basic usage. For more complex usage, see the documentation.

Basic

This basic example shows how to parse single files:

use multilinear_parser::parse_multilinear;
use std::fs::File;

let file = File::open("story.mld").unwrap();
let parsed = parse_multilinear(file).unwrap();

Directories & Aspect Files

For larger projects, you can organize your story into directories and parse aspect defaults from separate files:

use multilinear_parser::parse_multilinear_extended;

// Parse a directory tree with custom aspect default values
let parsed = parse_multilinear_extended(
    "story/chapters/".as_ref(),
    Some("story/aspects.mla".as_ref())
).unwrap();

// Parsing only a single file without aspects is also possible using this method, but less efficient
let parsed = parse_multilinear_extended(
    "story.mld".as_ref(),
    None
).unwrap();

Full Example

This is how some more complex example might look like:

# At home

## In bedroom

### Get dressed

place: bedroom
clothes: pajamas > casual

### Get undressed

place: bedroom
clothes: casual > pajamas

### Go to livingroom

place: bedroom > livingroom
clothes: pajamas; casual

## In livingroom

### Talk sister in pajamas

place: livingroom
clothes: pajamas

### Talk mom in pajamas

place: livingroom
clothes: pajamas

### Talk sister casual

place: livingroom
clothes: casual

### Talk mom casual

place: livingroom
clothes: casual

### Go to bedroom

place: livingroom > bedroom

### Go outside

place: livingroom > outside
clothes: casual

### Try to go outside wearing pajamas

place: livingroom
clothes: pajamas
mom mood: happy > annoyed

# Outside

## Go home

place: outside > livingroom

## Travel to the city

place: outside > city

This even shows grouping using subheaders.

In this case, the events are grouped by place. But it would also make sense to group them by action type (like continuous actions and temporary events, or movement and local events).

And not using grouping at all is also an option.