User Guides

Rule Expression Format

Full reference for the Shogi expression syntax used in rules.

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 42 or 0.01
  • strings in single or double quotes, such as 'hello' or "hello"
  • booleans: true and false

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:

  1. !
  2. +
  3. ,

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:

  1. unary !
  2. * and /
  3. + 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.

Next guides