Expression Language
The Storybook expression language enables conditions, queries, and logic throughout the system. Expressions appear in life arc transitions, behavior tree guards, decorator conditions, and relationship queries. This chapter provides a complete reference for expression syntax and semantics.
What are Expressions?
Expressions are logical statements that evaluate to true or false. They combine:
- Literals: Numbers, strings, booleans
- Identifiers: References to fields and entities
- Comparisons:
==,!=,<,<=,>,>= - Logical operators:
and,or,not - Field access:
self.field,other.field - Quantifiers:
forall,exists(for collections)
Syntax
<expression> ::= <literal>
| <identifier>
| <field-access>
| <comparison>
| <logical>
| <unary>
| <quantifier>
| "(" <expression> ")"
<literal> ::= <int> | <float> | <string> | <bool>
<identifier> ::= <simple-name>
| <qualified-path>
<field-access> ::= <expression> "." <identifier>
| "self" "." <identifier>
| "other" "." <identifier>
<comparison> ::= <expression> <comp-op> <expression>
<comp-op> ::= "==" | "!=" | "<" | "<=" | ">" | ">="
<logical> ::= <expression> "and" <expression>
| <expression> "or" <expression>
<unary> ::= "not" <expression>
| "-" <expression>
<quantifier> ::= ("forall" | "exists") <identifier> "in" <expression> ":" <expression>
Literals
Integer Literals
42
-7
0
1000
Float Literals
3.14
-0.5
0.0
100.25
String Literals
"Martha"
"Sourdough takes patience."
"active"
Strings are enclosed in double quotes. Escape sequences: \n, \t, \\, \".
Boolean Literals
true
false
Identifiers
Identifiers reference fields or entities.
Simple Identifiers
health
enemy_count
is_ready
Qualified Paths
Martha.skill_level
Character.emotional_state
Comparison Operators
Equality: ==
Tests if two values are equal.
name == "Martha"
count == 5
status == active
Type compatibility:
- Both operands must be the same type
- Works with: int, float, string, bool, enum values
Inequality: !=
Tests if two values are not equal.
name != "Gregory"
health != 0
ready != true
Less Than: <
Tests if left operand is less than right.
health < 20
age < 18
distance < 10.0
Valid types: int, float
Less Than or Equal: <=
health <= 50
count <= max_count
Greater Than: >
strength > 10
bond > 0.5
Greater Than or Equal: >=
age >= 21
score >= 100
Logical Operators
AND: and
Both operands must be true.
health < 50 and has_potion
is_ready and not is_busy
age >= 18 and age < 65
Evaluation: Short-circuit (if left is false, right is not evaluated)
OR: or
At least one operand must be true.
is_day or is_lit
health < 20 or surrounded
enemy_count == 0 or all_enemies_dead
Evaluation: Short-circuit (if left is true, right is not evaluated)
Operator Precedence
From highest to lowest:
- Parentheses:
(...) - Unary:
not,- - Comparisons:
==,!=,<,<=,>,>= - AND:
and - OR:
or
Examples:
not is_ready and is_awake
// Equivalent to: (not is_ready) and is_awake
health < 50 or is_poisoned and has_antidote
// Equivalent to: (health < 50) or (is_poisoned and has_antidote)
// Use parentheses for clarity:
(health < 50 or is_poisoned) and has_antidote
Unary Operators
NOT: not
Inverts a boolean value.
not is_ready
not (health < 20)
not enemy_nearby and safe
Negation: -
Negates a numeric value.
-health
-10
-(max_value - current_value)
Field Access
Direct Field Access
health
bond
emotional_state
References a field on the current entity.
Dot Access
Martha.skill_level
Character.emotional_state
enemy.health
Access fields on other entities.
Self Access
In relationships and certain contexts, self refers to the current participant:
self.bond
self.responsibility
self.trust
Use case: Relationship transitions, symmetric queries
Other Access
In relationships, other refers to other participants:
other.bond
other.aware_of_mentor
other.respect
Use case: Relationship queries with perspective
Example in Life Arcs
life_arc RelationshipState {
state new {
on self.bond > 0.7 and other.bond > 0.7 -> stable
}
state stable {
on self.bond < 0.3 -> troubled
on other.bond < 0.3 -> troubled
}
state troubled {
on self.bond < 0.1 or other.bond < 0.1 -> broken
}
state broken {}
}
Quantifiers
Quantifiers test conditions over collections.
ForAll: forall
Tests if a condition holds for all elements in a collection.
forall e in enemies: e.defeated
forall item in inventory: item.weight < 10
Syntax:
forall <variable> in <collection>: <predicate>
Semantics:
- Returns
trueif predicate is true for every element - Returns
truefor empty collections (vacuously true)
Examples:
// All enemies defeated?
forall enemy in enemies: enemy.health <= 0
// All party members ready?
forall member in party: member.is_ready
// All doors locked?
forall door in doors: door.is_locked
Exists: exists
Tests if a condition holds for at least one element.
exists e in enemies: e.is_hostile
exists item in inventory: item.is_healing_potion
Syntax:
exists <variable> in <collection>: <predicate>
Semantics:
- Returns
trueif predicate is true for any element - Returns
falsefor empty collections
Examples:
// Any enemy nearby?
exists enemy in enemies: enemy.distance < 10
// Any door unlocked?
exists door in doors: not door.is_locked
// Any ally wounded?
exists ally in allies: ally.health < ally.max_health * 0.5
Nested Quantifiers
Quantifiers can nest:
forall team in teams: exists player in team: player.is_leader
// Every team has at least one leader
exists room in dungeon: forall enemy in room.enemies: enemy.defeated
// At least one room has all enemies defeated
Usage in Context
Life Arc Transitions
life_arc CombatState {
state idle {
on enemy_count > 0 -> combat
}
state combat {
on health < 20 -> fleeing
on enemy_count == 0 -> victorious
}
state fleeing {
on distance_from_enemies > 100 -> safe
}
state victorious {
on celebration_complete -> idle
}
state safe {
on health >= 50 -> idle
}
}
Behavior Tree Conditions
behavior GuardedAction {
if(health > 50 and has_weapon) {
AggressiveAttack
}
}
behavior ConditionalChoice {
choose tactics {
then melee {
if(distance < 5 and weapon_type == "sword")
MeleeAttack
}
then ranged {
if(distance >= 5 and has_arrows)
RangedAttack
}
}
}
Behavior Tree Conditions
behavior SmartAI {
choose strategy {
then aggressive {
if(health > 70 and enemy_count < 3)
Attack
}
then defensive {
if(health < 30 or enemy_count >= 5)
Defend
}
then balanced {
if(health >= 30 and health <= 70)
TacticalManeuver
}
}
}
Type System
Type Compatibility
Comparisons require compatible types:
| Operator | Left Type | Right Type | Valid? |
|---|---|---|---|
==, != | int | int | ✓ |
==, != | float | float | ✓ |
==, != | string | string | ✓ |
==, != | bool | bool | ✓ |
==, != | enum | same enum | ✓ |
==, != | int | float | ✗ |
<, <=, >, >= | int | int | ✓ |
<, <=, >, >= | float | float | ✓ |
<, <=, >, >= | string | string | ✗ |
Implicit Coercion
None. Storybook has no implicit type coercion. All comparisons must be between compatible types.
Error:
count == "5" // Error: int vs string
health < true // Error: int vs bool
Correct:
count == 5
health < 50
Special Keyword: is
The is keyword provides syntactic sugar for equality with enum values:
// Instead of:
status == active
// You can write:
status is active
More examples:
name is "Martha"
skill_level is master
emotional_state is focused
This is purely syntactic—is and == are equivalent.
Complete Examples
Simple Conditions
health < 20
enemy_nearby
not is_ready
count > 5
Complex Conditions
(health < 20 and not has_potion) or surrounded
forall e in enemies: e.defeated
exists item in inventory: item.is_healing_potion and item.quantity > 0
Life Arc with Complex Conditions
life_arc CharacterMood {
state content {
on health < 30 or hunger > 80 -> distressed
on social_interaction > 0.8 -> happy
}
state distressed {
on health >= 50 and hunger < 30 -> content
on (health < 10 or hunger > 95) and help_available -> desperate
}
state happy {
on social_interaction < 0.3 -> content
on received_bad_news -> distressed
}
state desperate {
on help_received -> distressed
}
}
Behavior with Quantifiers
behavior SquadLeader {
choose leadership {
then regroup {
if(squad_has_wounded)
OrderRetreat
}
then advance {
if(squad_all_ready)
OrderAdvance
}
then hold_position {
if(not squad_all_ready)
OrderHold
}
}
}
Relationship Query
life_arc FriendshipQuality {
state new_friends {
on self.bond > 0.7 and other.bond > 0.7 -> strong_bond
on self.trust < 0.3 or other.trust < 0.3 -> shaky
}
state strong_bond {
on self.bond < 0.5 -> weakening
}
state weakening {
on self.bond < 0.2 or other.bond < 0.2 -> ended
on self.bond > 0.7 and other.bond > 0.7 -> strong_bond
}
state shaky {
on self.trust > 0.6 and other.trust > 0.6 -> new_friends
on self.trust < 0.1 or other.trust < 0.1 -> ended
}
state ended {}
}
Validation Rules
- Type consistency: Both sides of comparison must be compatible types
- Boolean context: Logical operators (
and,or,not) require boolean operands - Field existence: Referenced fields must exist on the entity
- Collection validity: Quantifiers require collection-typed expressions
- Variable scope: Quantifier variables only valid within their predicate
- No division by zero: Arithmetic operations must not divide by zero
- Enum validity: Enum comparisons must reference defined enum values
Best Practices
1. Use Parentheses for Clarity
Avoid:
health < 50 or is_poisoned and has_antidote
Prefer:
(health < 50 or is_poisoned) and has_antidote
2. Break Complex Conditions
Avoid:
on (health < 20 and not has_potion) or (surrounded and not has_escape) or (enemy_count > 10 and weapon_broken) -> desperate
Prefer:
state combat {
on health < 20 and not has_potion -> desperate
on surrounded and not has_escape -> desperate
on enemy_count > 10 and weapon_broken -> desperate
}
3. Name Complex Conditions
For repeated complex conditions, consider using intermediate fields:
Instead of:
on health < (max_health * 0.2) and enemy_count > 5 -> flee
Consider:
// In character definition:
critically_wounded: health < (max_health * 0.2)
outnumbered: enemy_count > 5
// In life arc:
on critically_wounded and outnumbered -> flee
4. Use is for Enums
Prefer:
status is active
emotional_state is focused
Over:
status == active
emotional_state == focused
5. Quantifiers for Collections
Avoid:
// Manual checks for each element
if enemy1.defeated and enemy2.defeated and enemy3.defeated
Prefer:
if forall enemy in enemies: enemy.defeated
Cross-References
- Life Arcs - Transition conditions
- Behavior Trees - Guard and condition nodes
- Decorators - Guard decorator
- Relationships - Self/other field access
- Value Types - Literal value types
Related Concepts
- Type safety: Strong typing prevents type errors at compile time
- Short-circuit evaluation: AND/OR operators optimize evaluation
- Quantifiers: Enable expressive collection queries
- Field access: Context-sensitive (
self,other) for relationships - Boolean algebra: Standard logical operators with expected semantics