Rule Expression Format
What this page covers
This page documents the full expression format supported by Shogi's expression parser.
For a first simple intro, read the Getting Started page instead.
Use this guide when you want to understand:
- the overall rule shape
- operator precedence
- variables and assignments
- how function arguments work
- what kinds of syntax are valid or invalid
For a list of built-in Shogi conditions and effects, see Available Effects.
Rule shapes
Shogi accepts both effect calls as well as assignments as top-level forms, with optional conditions:
condition -> effect
condition -> $variable = expression
Examples:
xp_points_cost(12)
is_dimension('minecraft:the_end') -> refuse('You cannot use this here')
$xp_cost = clamp($distance * 0.01, 0, 27)
The arrow form means "if the condition matches, run the effect". If the condition does not match, nothing happens.
Literals
Expressions can contain these literal values:
- numbers, such as
42or0.01 - strings in single or double quotes, such as
'hello'or"hello" - booleans:
trueandfalse
Examples:
42
'minecraft:the_nether'
true
String literals support escaping by prefixing the next character with \.
Variables
Variables start with $:
$distance
$player.level
$foo.bar
Variable names can use dotted paths. These are commonly used when a rule calculates a value from the current evaluation context.
Assignments use the same variable syntax on the left-hand side:
$result = 5
$xp_cost = $distance * 0.01
Function and effect calls
Most rule logic is written as effect calls:
is_player
can_see_sky
failure('Players only')
clamp($value, 0, 27)
Zero-argument calls
Zero-argument effects can usually be written without parentheses:
is_player
can_see_sky
dismount
The parenthesized form is also valid when the effect actually takes no arguments:
is_player()
Positional arguments
Effects that register positional parameters can be called like this:
is_dimension('minecraft:the_end')
clamp($distance * 0.01, 0, 27)
if(can_see_sky, 180, 220)
Arguments are matched by their registered parameter order. For example, if(...) uses:
if(condition, then, else)
Some effects support a variadic positional list, such as:
and(is_player, can_see_sky, has_item('minecraft:ender_pearl', 2))
any(is_dimension('minecraft:the_nether'), is_dimension('minecraft:the_end'))
aggregate(item_cost('minecraft:ender_pearl', 1), xp_points_cost(3))
Named arguments
Effects can also be called with named arguments:
if(condition = can_see_sky, then = 180, else = 220)
clamp(value = $distance * 0.01, min = 0, max = 27)
Named arguments may be written in any order:
if(condition = can_see_sky, else = 27, then = $distance * 0.01)
Do not mix named and positional arguments
One call must use exactly one style:
binary_op('+', 1, 2)
binary_op(op = '+', left = 1, right = 2)
This is invalid:
binary_op(op = '+', 1, 2)
Conditions
Conditions are the part before ->:
condition -> effect
Examples:
is_player -> true
can_see_sky -> xp_points_cost(3)
is_dimension('minecraft:the_end') -> refuse('Disabled here')
Negation
Use ! to negate a condition:
!is_player -> failure('Players only')
You can also negate a grouped condition:
!(is_player, can_see_sky) -> failure('Condition failed')
AND with +
Use + when all conditions must match:
is_player + can_see_sky -> true
OR with ,
Use , when any condition may match:
is_dimension('minecraft:the_nether'), is_dimension('minecraft:the_end') -> 256
Grouping with parentheses
Use parentheses to group condition logic explicitly:
is_player + (can_see_sky, is_dimension('minecraft:the_end')) -> true
(is_player, has_item('minecraft:ender_pearl', 2)) + can_see_sky -> true
Condition precedence
Condition operators are parsed in this order:
!+,
So this:
noop + noop, noop -> noop
is parsed like this:
(noop + noop), noop -> noop
If you want a different grouping, add parentheses.
Expressions and arithmetic
The right-hand side of a rule, and function arguments inside calls, can be general expressions.
Arithmetic operators:
*/+-
Examples:
$distance * 0.01
1 + 2 * 3
(1 + 2) * 3
$xp_cost = clamp($distance * 0.01, 0, 27)
Arithmetic precedence follows normal rules:
- unary
! *and/+and-
Parentheses override precedence.
! is also valid inside expressions, not only in top-level conditions:
$result = !true + 1
$result = !has_cooldown('inventory_button')
Identifier rules and namespaces
Effect names use identifiers such as:
is_player
shogi:is_player
waystones:is_owner
use('test:other_rule')
In most user-facing configs, you can omit the namespace and write the short name. The scope decides which namespaces are searched first.
For example, a scope that defaults to waystones and shogi can resolve both:
is_owner + is_global -> xp_points_cost(0)
cooldown_cost('inventory_button', '300s')
If multiple namespaces are configured, the first matching effect wins.
Whitespace
Whitespace is optional around operators and punctuation. These parse the same way:
noop + noop, noop -> noop
noop+noop,noop->noop
Use spaces anyway when possible, because they are much easier to read.
Common patterns
Simple conditional rule
is_dimension('minecraft:the_end') -> refuse('You cannot use this here')
Combined conditions
is_player + can_see_sky -> xp_points_cost(3)
Any-of condition
is_dimension('minecraft:the_nether'), is_dimension('minecraft:the_end') -> 256
Nested contextual call
offhand(is_item('minecraft:totem_of_undying')) -> refuse('Totems block this action')
Assignment with arithmetic
$xp_cost = clamp($distance * 0.01, 0, 27)
Conditional expression with named arguments
$xp_cost = if(condition = can_see_sky, else = 27, then = $distance * 0.01)
Common parse errors
These are common problems when writing expressions:
- mixed named and positional arguments in the same call
- duplicate named parameters such as
op = '+', op = '-' - too many positional arguments for the chosen effect
- unknown or invalid effect identifiers
- trailing tokens after a complete expression
- missing closing parentheses
- empty condition groups like
() -> noop
Examples of invalid syntax:
binary_op(op = '+', 1, 2)
binary_op(op = '+', op = '-', left = 1, right = 2)
1 2
() -> noop
noop + () -> noop
When to use JSON instead
Expressions are the shortest and most convenient format for most rules.
Switch to JSON when:
- a rule becomes too deeply nested to read comfortably
- you want the explicit object shape of each effect
- you are generating rules programmatically
See Advanced: Rules as JSON for the JSON form.