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:
For a list of built-in Shogi conditions and effects, see Available Effects.
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.
Expressions can contain these literal values:
42 or 0.01'hello' or "hello"true and falseExamples:
42
'minecraft:the_nether'
true
String literals support escaping by prefixing the next character with \.
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
Most rule logic is written as effect calls:
is_player
can_see_sky
failure('Players only')
clamp($value, 0, 27)
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()
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))
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)
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 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')
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')
+Use + when all conditions must match:
is_player + can_see_sky -> true
,Use , when any condition may match:
is_dimension('minecraft:the_nether'), is_dimension('minecraft:the_end') -> 256
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 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.
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:
!* and /+ and -Parentheses override precedence.
! is also valid inside expressions, not only in top-level conditions:
$result = !true + 1
$result = !has_cooldown('inventory_button')
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 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.
is_dimension('minecraft:the_end') -> refuse('You cannot use this here')
is_player + can_see_sky -> xp_points_cost(3)
is_dimension('minecraft:the_nether'), is_dimension('minecraft:the_end') -> 256
offhand(is_item('minecraft:totem_of_undying')) -> refuse('Totems block this action')
$xp_cost = clamp($distance * 0.01, 0, 27)
$xp_cost = if(condition = can_see_sky, else = 27, then = $distance * 0.01)
These are common problems when writing expressions:
op = '+', op = '-'() -> noopExamples of invalid syntax:
binary_op(op = '+', 1, 2)
binary_op(op = '+', op = '-', left = 1, right = 2)
1 2
() -> noop
noop + () -> noop
Expressions are the shortest and most convenient format for most rules.
Switch to JSON when:
See Advanced: Rules as JSON for the JSON form.