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:
- A list of rules that operate on:
- A set of named stacks, that contain:
- Facts, which are made of:
- 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:
- Initial state, if there is any, is set up
- Rules are checked from the top of list of rules
- As soon as a rule's causes match the current Nova state
- The facts that match those rules are removed from their stacks, and
- The consequence facts are added to -their- stacks
- 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.