Splitting from the MouseQBN topic to avoid derailing it too far…
For parsing simple expressions like this I think the shunting-yard algorithm hits a sweet spot of easy-to-implement but pretty powerful and flexible. Hand-rolling a recursive-descent parser isn’t bad either, but you have to worry about backtracking the parse, and avoiding left-recursion, and you’re hardcoding the precedence levels into your grammar, so it feels a little more finicky to me.
For lexing I usually compile down to a single regular expression with one capturing group per type of token: Moo is a good example of this. It has some error checking and a few bells and whistles but its source code is still only about 650 lines, so it’s not too hard to find the relevant bits? But let me know if you’d like a more minimal example of this approach: I could throw something together.
I have two favorite data structures for compiling expressions for later evaluation:
- S-expressions with arrays, e.g.
["<", "$fuel", ["*", "$distance", "$milesPerGallon"]]
- Some form of RPN “bytecode” for a stack-based virtual machine a lá Forth, e.g.
"$fuel $distance $milesPerGallon * <".split(" ")
Though of course there are further questions, like “do you want to restrict the kinds of allowable conditions to make it easier to graph or analyze the story volume?” and “if you’re restricting them, do you do it in the parser or in the generated data structure?”
I tend to do it only on request: it’s easy to attach to story events as needed, and it’s not too hard to optimize as necessary by indexing your lookups on common fields. Doing it after a data operation allows some cool things, but a naïve implementation can get slow because data operations are often more common than storylet selection (I think?), and implementing a data/condition dependency graph is extremely cool but not really worth it unless you’re building a whole tool/language around it IMO…
TinyQBN uses passage tags as the initial state: SugarCube really wants passages to be static so it keeps the variable data in an array or dictionary, though I don’t remember how much it exposes that.
As for “separating the storylet data from passage or other data,” one thing I’d really like to see in a dedicated storylet tool is for it to let you view and edit the storylet data both with the text of the individual storylet and as a collection of the metadata without content for a (possibly-dynamically-filtered) collection of storylets.