Syntactically, there are three categories of statements: control-flow statements which contain sub-statements, statements which contain rules, and line statements. Additionally, declarations may occur where statements are expected.
When a block may have multiple children, these must occur either in an indented block after a colon ‘:
’, or if there is only one child it may occur on the same line, after the colon.
Statements in MJr are said to “return true” or “return false”; this determines the program’s control flow. A statement typically “returns true” if it makes any changes to the current grid’s state.
A compilation unit is treated like a ‘sequence’ statement.
A markov
statement executes its children in order of priority, returning back to the first child each time any child statement “returns true”. The markov
statement itself “returns true” if any of its children did.
markov: Statement+
A sequence
statement executes each child repeatedly until it “returns false”, then moves onto the next child. The sequence
statement itself “returns true” if any of its children did.
sequence: Statement+
A @limit
modifier is followed by an expression, which must be a runtime constant of type int
, and then a sub-statement on the next line. The value of the expression determines the initial value of a “limit counter” which is initialised each time the parent block is entered, and counts down each time the modified statement “returns true”. Once the counter reaches zero, the limit statement “returns false” without executing its sub-statement.
A compilation error occurs if the sub-statement is another limit statement, a once
statement, or a kind of statement which always “returns false”.
@limit Expression Statement
A one
statement has a set of rewrite rules, and pseudorandomly chooses an applicable match of one of them. If an applicable match exists, that rewrite is applied to the grid and the statement “returns true”; otherwise it “returns false”.
one: Rule+
one {Arguments}: Rule+
As a shorthand syntax, the keyword once
may be written instead of one
; this is equivalent to a one
statement with a limit of 1. A once
statement has no arguments.
once: Rule+
An all
statement has a set of rewrite rules, and applies rewrites in parallel. If any applicable matches exist, then a maximal subset of them is pseudorandomly chosen such that output patterns do not overlap, the rules are applied on these matches, and the statement “returns true”; otherwise, it returns “false”.
all: Rule+
all {Arguments}: Rule+
A prl
statement is similar to an all
statement, except that overlapping output patterns are not prevented. If there are any applicable matches, then the rewrite rules are applied on every match in parallel, with any overlapping output patterns written in a pseudorandom order, and the statement “returns true”; otherwise, it “returns false”.
A prl
statement has no arguments.
prl: Rule+
A convolution
statement applies a set of 1x1 rewrite rules to all matches in parallel, where the rules may make use of ‘sum’ expressions. There is one required argument, kernel
, which determines the convolution kernel used by sum
expressions within the statement. It must be a compile-time constant str
equal to one of the following names:
Moore
: a Moore neighbourhood.VonNeumann
: a Von Neumann neighbourhood.Additionally, there is an optional argument, boundary
, which must be a compile-time constant 1x1 pattern. If present, cells outside of the grid are treated as if they take values from the subset of the alphabet defined by boundary
. A compilation error occurs if boundary
is specified for a periodic grid.
The statement “returns true” if there were any applicable matches, otherwise it “returns false”.
convolution {Arguments}: Rule+
A map
statement has a set of rewrite rules whose input patterns are matched in an “input grid” and output patterns are written to a separate “output grid”. The input grid is the current grid, and the output grid is specified as an argument. The input and output grids may have different scales, in which case the input and output patterns of each rewrite rule must be scaled in the same proportion, and the position of the match in the input grid is scaled to the output grid in the same proportion.
A map
statement always “returns false”, because it may make changes to outGrid
but not the current grid. However, after a map
statement executes, outGrid
becomes the current grid.
A compilation error occurs if outGrid
is the current grid.
map {outGrid=Expression}: Rule+
A convchain
statement runs the ConvChain algorithm to generate an image which is locally similar to a given sample pattern, by pseudorandomly replacing some grid cells. It has the following arguments:
sample
: the sample image. Must be a compile-time constant pattern.out
with at least two distinct alphabet symbols, and no wildcards.n
: the size of subpatterns used to determine “local similarity”. Must be a compile-time constant int
which is at least 2 and at most the width and height of sample
.periodic
: an optional bool
indicating whether sample
is a periodic image (default true
). Must be a compile-time constant.on
: an optional charset.in
determining which grid cells may be modified (default [.]
). Must be a compile-time constant.epsilon
: an optional float
which determines the weight of subpatterns which are not present in sample
(default 0.125
). Must be a compile-time constant, and positive.temperature
: an optional float
which controls the probability of changes which decrease local similarity (default 1.0
). Must be non-negative.anneal
: an optional float
which is subtracted from the temperature on each iteration (default 0.0
). Must be non-negative.The alphabet symbols present in sample
form the “output set”.
On the first iteration of a convchain
statement, a set of grid cells is initialised containing those cells which match the pattern on
. Each cell in this set which does not already have a symbol in the output set is replaced with pseudorandom symbol from the output set. If any replacements were made, then the statement completes and “returns true”, otherwise it continues executing as below.
After initialisation, on each iteration the grid cells which originally matched on
are pseudorandomly replaced with different symbols the output set, biased towards local similarity with sample
. The statement “returns true” if any replacements were made, and “returns false” otherwise.
convchain {Arguments}
A log
statement logs a the value of an expression to the standard output stream (i.e. the console or terminal), and always “returns false”. The expression’s type must be one of bool
, float
, fraction
, grid
, int
or str
.
log Expression
A pass
statement does nothing, and always “returns false”. It may be used as a placeholder in a block which is not yet written.
pass
path {Arguments}
A put
statement writes a pattern to the current grid at a given position, if it is not already present there. The position determines the top-left corner of where the pattern should be written. The pattern’s type must be an output pattern type.
A put
statement always “returns false”, even when writing the pattern changes the grid. This is because even when a put
statement is not idempotent, it is normally not intended to repeat.
put Expression at Expression
A put
statement may have a condition, which must be an expression of type bool
. If the condition is true, then the statement is executed as above; otherwise the pattern is not written.
put Expression at Expression if Expression
A use
statement changes the current grid to the value of an expression, which must be a compile-time constant of type grid
. The context change persists beyond the current block. The statement always “returns false”.
use Expression
If the expression is a grid expression, then the keyword use
may be omitted. This is a shorthand syntax, referred to as a “bare ‘use’ statement”.
grid [Alphabet]
grid {Arguments} [Alphabet]
In order to access the grid’s attributes (i.e. its width and height) or otherwise refer to the grid elsewhere, it is necessary to declare a variable holding a reference to the grid. This can be done with a declaration such as let g = grid ...
followed by the statement use g
, or with the following shorthand syntax, referred to as a use let
statement:
use let Name = Expression
The use let
statement is equivalent to the combination of a let
declaration and a use
statement; in particular, the variable is only in scope until the end of the current block, but the context change of the use
statement persists beyond the current block.
If a declaration occurs in a block of statements, then the declaration is in effect for the statements following it within that block.
Declaration Statement*
Alternatively, the declaration may be followed by the keyword in
, a colon ‘:
’ and a block of child statements, in which case the declaration is in effect for the statements in that block.
Declaration in: Statement+