-
Notifications
You must be signed in to change notification settings - Fork 2
old NDCA Specification
-
@
is used for directives -
#
is used for tags and state IDs -
()
groups an expression or calls a function -
[]
contains a comma-separated vector -
{}
contains a comma-separated list -
"abc"
and'abc'
are both strings -
//
begins a comment, which ends at the end of the line -
/*
begins a block comment, which ends at the next*/
All whitespace is semantically equivalent to a single space
.
The following names are reserved for predefined constants:
-
true
=1
-
false
=0
-
any
= a state matcher which matches any state -
N
=[0, 1]
("north") -
S
=[0, -1]
("south") -
E
=[1, 0]
("east") -
W
=[-1, 0]
("west") -
NE
=[1, 1]
("northeast") -
SE
=[1, -1]
("southeast") -
NW
=[-1, 1]
("northwest") -
SW
=[-1, -1]
("southwest") -
U
=[0, 0, 1]
("up") -
D
=[0, 0, -1]
("down")
NDCA is statically typed.
Types:
- number (e.g.
10
) - vector (e.g.
[1, 2, 3]
) - state (e.g.
#10
) - tag (e.g.
#tag_name
) - tag value (e.g.
#tag_name:10
or#tag_name:value_name
) - pattern (e.g.
napkin
) - list
- state matcher
- pattern matcher
All types support the following comparisons:
a == b
a != b
These always return 1
(true
) if a
and b
are equal and 0
(false
) if they are not. Two values can only be compared if they are the same type, or if one type can be automatically converted into the other.
Numbers are automatically converted into vectors when used where a vector is expected. The resulting vector has the original numeric value along all axis. For example, converting 3
to a vector in a 2D automaton results in [3, 3]
.
Tags are automatically converted to state matchers when used where a state matcher is expected.
All numbers are signed 32-bit integers.
Numbers support basic math operations:
- Addition:
a + b
- Subtraction:
a - b
- Multiplication:
a * b
- Division:
a / b
(rounds down) - Modulo:
a % b
(always positive) - Exponentiation:
a ** b
- Negation:
-a
- Bitshift:
a << b
,a >> b
- Bitwise AND:
a & b
- Bitwise OR:
a | b
- Bitwise XOR:
a ^ b
- Bitwise complement:
~a
Numbers can be compared:
a == b
a != b
a < b
a > b
a <= b
a >= b
There are three unary functions which operate on numbers:
- Logical NOT:
not(a)
- equivalent to
a == 0
- equivalent to
- Convert to boolean:
bool(a)
- equivalent to
a != 0
- equivalent to
- Absolute value:
abs(a)
To get the cell state with a given numeric ID, use the unary #
operator:
#10
-
#(a)
- use parentheses when value is stored in a variable
All vectors have the same number of dimensions as the automaton. Unspecified vector components are initialized to zero.
Vectors support all of the same math operations as numbers, which operate elementwise. For example, [1, 2, 3] + [4, 4, -1]
is [5, 6, 2]
.
Vectors support only two comparison operations:
a == b
a != b
Vectors support the same three unary functions as numbers:
- Logiical NOT:
not(a)
- equivalent to
a == 0
- equivalent to
- Convert to boolean:
bool(a)
- equivalent to
a != 0
- equivalent to
- Absolute value:
abs(a)
- operates elementwise; returns a vector
Each component of a vector is a signed 32-bit integer and can be accessed individually using the name of the axis:
- Vector component access:
my_vector.X
Vector "swizzling" is supported. For example my_vector.XZY
returns my_vector
but with the Y and Z components swapped.
States can be constructed from a number using the #
prefix. For example, #10
is the state with ID 10, and #(some_number)
is the state with ID stored in the variable some_number
. Note that when constructing a state from a number stored in a variable (e.g. #(some_variable)
) or as a result from an expression (e.g. #(2 + 3)
), parentheses are required.
States have tag attributes, which are numbers. To get the value of a tag, write the state followed by the tag. For example, state#tag_name
returns the value of #tag_name
on state
.
When used with is
or any of the set operators &
, |
, ^
, or ~
, a state automatically coerces to a state matcher that is true
for its own state and false
for all other states. For example, #10 | #15
matches only the state with ID 10 and the state with ID 15, and ~#10
matches any state with an ID other than 10.
Tags are identifiers with the #
prefix (e.g. #tag_name
). Tags are used in state definitions and as matchers.
A tag is a property of a state. For each tag (denoted #tag_name
), every state has a numeric value. Unless otherwise specified, this numeric value is 0
. When a state is defined, it can be given some other value.
Tag values are tags followed by :
and then either an identifier or a number (e.g. #tag_name:value_name
or #tag_name:10
). Tag values are used in state definitions and as matchers.
Each named tag value (denoted #tag_name:value_name
) is automatically assigned a nonzero number that it is equivalent to; for example, if #some_tag:some_value
is assigned the number 3
, then #some_tag:some_value
is equivalent to #some_tag:3
.
TODO
A list is defined using curly braces {}
, with commas between elements. A trailing comma is allowed. For example, {1, 2, 3}
is a list of the numbers 1, 2, and 3. A list must contain only one data type.
start..end
constructs a list of numbers ranging from start
to end
, including both endpoints; for example, 1..5
is equivalent to [1, 2, 3, 4, 5]
A list's length is given by some_list.len
.
A list can be indexed using a numeric index: some_list[10]
or some_list[some_index]
. The first element of a list has the index 0
, and the last element has the index list.len - 1
.
Lists can be concatenated using the ..
operator (e.g. list1 .. list2
).
TODO: list slicing using ranges? Pythonesque negative indices?
When a state, tag, or tag value is used with is
or any of the set operators &
, |
, ^
, or ~
, it is first converted to a state matcher according to the following rules.
-
#10
matches only the state with ID 10 -
#tag_name
matches any state with a nonzero tag value for#tag_name
-
#tag_name:value
matches any state with the tag value#tag_name:value
State matchers are primarily used in is
expressions: state is state_matcher
returns 1
(true
) if state_matcher
matches state
and 0
(false
) if it does not.
TODO
Variables can be created or modified using set var_name = expression
. Variables can hold any value.
There are several control flow constructs, modelled. <condition>
stands for an expression which evaluates to a number, vector, or list; it is considered falsey if it is zero/empty and truthy if it is nonzero/nonempty.
if <condition> then ... end
if <condition> then ... else ... end
if <condition1> then ... else if <condition2> then ... end
if <condition1> then ... else if <condition2> then ... else ... end
while <condition> do ... end
TODO: iterators/for loops and switch
statement
Directives must be top-level; i.e. they cannot appear nested inside of other directives or code blocks. Most directives take only a single value, but some have special syntax. Each directive can only appear at most once. @ndca
and @name
are required; all directives are optional. @author
, @designer
, and @year
all default to "Unknown"
; @description
and @url
default to an empty string; the default values for all other directives are shown below.
// Version of NDCA that this rule was written for
@ndca 0
// Name of automaton rule
@name "CA Name"
// Name of person who wrote this file
@author "Your Name"
// Name of person/people who first described this automaton
@designer "Name(s) of Designers"
// Year that this automaton was first described
@year "2020"
// Link to research paper, forum post, or wiki page describing this automaton
@url "https://www.example.com/"
// A multiline description of this automaton
@description "
A multiline description of this CA rule.
Blank lines are trimmed from the beginning and end of this description.
"
// Default pattern
// TODO: creator/description/year of default pattern?
@pattern r"!"
// Number of dimensions
@dim 2
// Neighborhood radius
@radius 1
// Cell states
@states { #dead, #alive }
// Transition function, terminated by keyword `end`
@transition
remain
end
If @author
is specified but not @designer
, @designer
will be the same as @author
.
Two directives, @states
and @transition
, do not take a simple value. Both are described below.
@states
takes a list of state definitions. Each state definition consists of several state properties, separated by whitespace. If a state has no properties, _
must be used to express this.
State properties:
- a state ID (e.g.
#10
), which forces the state ID - a tag (e.g.
#tag_name
), which is equivalent to#tag_name:1
- a tag value (e.g.
#tag_name:10
or#tag_value:value_name
), which gives the state the given value for that tag.
The transition function contains code that is executed to determine the next state of a given cell. Two special variables are accessible inside the transition function and cannot be modified:
-
this
is the state of the center cell -
napkin
is the patten of cells in the surrounding region
TODO: allow napkin mask (i.e. neighborhood) to be modified
Due to the decision-tree-like nature of many cellular automata, NDCA code may make quite heavy use of nesting. For this reason, NDCA files should be indented with 2 spaces per indent level.
Lines longer than 80 characters should be avoided, either by rewriting or by splitting one statement into multiple lines. Long lines are ok in a multiline string though.
The description should be wrapped to ~80 characters, and paragraphs should be separated by a blank line. In the future, basic markdown syntax may be supported.
If any state has multiple tags, then the members of @states
should be split so that each state is specified on its own line.