Nova, mechanically

Nova

Nova is a new programming langauge/tool, meant to be simple, expressive, and good at the kind of logic that tabletop games need.

Mechnically

Nova has 4 core ideas:

  1. A list of rules that operate on:
  2. A set of named stacks, that contain:
  3. Facts, which are made of:
  4. Sequences of symbols

We'll break these down, starting with symbols and working up the list.

Symbols

A symbol in Nova is a single piece of data. Usually it's a name for something, sometimes it's a number or a reference to something else. The only operation that is required of symbols is that you have a way to check if two symbols are equal. Additionally, it's a good idea for there to be a printable representation of a symbol that you can easily understand.

Each one of these words is a symbol

If you -do- want to have spaces in a symbol's name, you can write that this way in Myte, starting with a [, and ending with a ]:

[This is all one symbol]

Facts and stacks

Facts in nova are a sequence of symbols. If you're familiar with functional programming or databases, you'll recognize them as tuples.

Facts in Nova are always part of a named stack. For example:

||
:card: Ace of Diamonds
:card: 3 of Spades

This puts two facts on the card stack, Ace of Diamonds and 3 of Spades.

A flexible syntax for delimiters

Nova's default implementations (Myte, for now), have a two-delimiter syntax that's a little funky.

The way it works is that the first character in the file that isn't whitespace is taken as the rule delimiter. This is the token that switches from the causes to the effects and back again.

After every time there's a cause/effect switch, the -next- non-whitespace character is temporarily the stack name delimiter, until the next cause/effect switch.

For example:

| :the side of: causes | ~the side of~ effects

Both : and ~ are used as stack name delimiters. |, as the first non-whitespace character in the file, is used as the cause/effect delimiter

Rules

Nova rules are run in a specific way:

  1. Initial state, if there is any, is set up
  2. Rules are checked from the top of list of rules
  3. As soon as a rule's causes match the current Nova state
  4. The facts that match those rules are removed from their stacks, and
  5. The consequence facts are added to -their- stacks
  6. This repeats until none of the rules match

To give a bit of an example, consider the code below:

|:b: c| :c: d
|:a: b| :b: c
|:c: d| :: done

|| :a: b

Here, we have 4 rules. The first two are fairly straightforward find/replace rules. The third one effectively terminates the program. This isn't because :: done is a special fact to Nova, but because no other rules match :: done.

The fourth one -is- a special case. When a rule has no causes, it's treated as part of the initial state of the Nova program. Every rule that's specificied this way is bundled up into one big set of stacks and values of initial state.

So, to play this out:

1) We start with the state || :a: b. The first rule that matches this state is the second rule |:a: b| :b: c, so we end up with a new state || :b: c, and go back to the top of the rule list.

2) With the state || :b: c, the first rule is the first one to match. So, we run it and end up with the state || :c: d.

3) Now, the first rule to match the current state is rule 3. The consequences of that gives a state of :: done.

4) Finally, check all of the rules one more time, to make sure none of the rules match. When no rules match, Nova stops.

Conclusion

This is a very simple demonstration of how a Nova system works. If you want to learn more, check out Seeing through Nova's eyes, the next article in this series.