704 lines
13 KiB
Markdown
704 lines
13 KiB
Markdown
# 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
|
|
|
|
```bnf
|
|
<expression> ::= <literal>
|
|
| <identifier>
|
|
| <field-access>
|
|
| <comparison>
|
|
| <logical>
|
|
| <unary>
|
|
| <quantifier>
|
|
| "(" <expression> ")"
|
|
|
|
<literal> ::= <number> | <decimal> | <text> | <boolean>
|
|
|
|
<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
|
|
|
|
```storybook
|
|
42
|
|
-7
|
|
0
|
|
1000
|
|
```
|
|
|
|
### Float Literals
|
|
|
|
```storybook
|
|
3.14
|
|
-0.5
|
|
0.0
|
|
100.25
|
|
```
|
|
|
|
### String Literals
|
|
|
|
```storybook
|
|
"Martha"
|
|
"Sourdough takes patience."
|
|
"active"
|
|
```
|
|
|
|
Strings are enclosed in double quotes. Escape sequences: `\n`, `\t`, `\\`, `\"`.
|
|
|
|
### Boolean Literals
|
|
|
|
```storybook
|
|
true
|
|
false
|
|
```
|
|
|
|
## Identifiers
|
|
|
|
Identifiers reference fields or entities.
|
|
|
|
### Simple Identifiers
|
|
|
|
```storybook
|
|
health
|
|
enemy_count
|
|
is_ready
|
|
```
|
|
|
|
### Qualified Paths
|
|
|
|
```storybook
|
|
Martha.skill_level
|
|
Character.emotional_state
|
|
```
|
|
|
|
## Comparison Operators
|
|
|
|
### Equality: `==`
|
|
|
|
Tests if two values are equal.
|
|
|
|
```storybook
|
|
name == "Martha"
|
|
count == 5
|
|
status == active
|
|
```
|
|
|
|
**Type compatibility:**
|
|
- Both operands must be the same type
|
|
- Works with: number, decimal, text, boolean, enum values
|
|
|
|
### Inequality: `!=`
|
|
|
|
Tests if two values are not equal.
|
|
|
|
```storybook
|
|
name != "Gregory"
|
|
health != 0
|
|
ready != true
|
|
```
|
|
|
|
### Less Than: `<`
|
|
|
|
Tests if left operand is less than right.
|
|
|
|
```storybook
|
|
health < 20
|
|
age < 18
|
|
distance < 10.0
|
|
```
|
|
|
|
**Valid types:** number, decimal
|
|
|
|
### Less Than or Equal: `<=`
|
|
|
|
```storybook
|
|
health <= 50
|
|
count <= max_count
|
|
```
|
|
|
|
### Greater Than: `>`
|
|
|
|
```storybook
|
|
strength > 10
|
|
bond > 0.5
|
|
```
|
|
|
|
### Greater Than or Equal: `>=`
|
|
|
|
```storybook
|
|
age >= 21
|
|
score >= 100
|
|
```
|
|
|
|
## Logical Operators
|
|
|
|
### AND: `and`
|
|
|
|
Both operands must be true.
|
|
|
|
```storybook
|
|
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.
|
|
|
|
```storybook
|
|
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:
|
|
|
|
1. **Parentheses**: `(...)`
|
|
2. **Unary**: `not`, `-`
|
|
3. **Comparisons**: `==`, `!=`, `<`, `<=`, `>`, `>=`
|
|
4. **AND**: `and`
|
|
5. **OR**: `or`
|
|
|
|
**Examples:**
|
|
```storybook
|
|
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.
|
|
|
|
```storybook
|
|
not is_ready
|
|
not (health < 20)
|
|
not enemy_nearby and safe
|
|
```
|
|
|
|
### Negation: `-`
|
|
|
|
Negates a numeric value.
|
|
|
|
```storybook
|
|
-health
|
|
-10
|
|
-(max_value - current_value)
|
|
```
|
|
|
|
## Field Access
|
|
|
|
### Direct Field Access
|
|
|
|
```storybook
|
|
health
|
|
bond
|
|
emotional_state
|
|
```
|
|
|
|
References a field on the current entity.
|
|
|
|
### Dot Access
|
|
|
|
```storybook
|
|
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:
|
|
|
|
```storybook
|
|
self.bond
|
|
self.responsibility
|
|
self.trust
|
|
```
|
|
|
|
**Use case:** Relationship transitions, symmetric queries
|
|
|
|
### Other Access
|
|
|
|
In relationships, `other` refers to other participants:
|
|
|
|
```storybook
|
|
other.bond
|
|
other.aware_of_mentor
|
|
other.respect
|
|
```
|
|
|
|
**Use case:** Relationship queries with perspective
|
|
|
|
### Example in Life Arcs
|
|
|
|
```storybook
|
|
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.
|
|
|
|
```storybook
|
|
forall e in enemies: e.defeated
|
|
forall item in inventory: item.weight < 10
|
|
```
|
|
|
|
**Syntax:**
|
|
```bnf
|
|
forall <variable> in <collection>: <predicate>
|
|
```
|
|
|
|
**Semantics:**
|
|
- Returns `true` if predicate is true for every element
|
|
- Returns `true` for empty collections (vacuously true)
|
|
|
|
**Examples:**
|
|
```storybook
|
|
// 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.
|
|
|
|
```storybook
|
|
exists e in enemies: e.is_hostile
|
|
exists item in inventory: item.is_healing_potion
|
|
```
|
|
|
|
**Syntax:**
|
|
```bnf
|
|
exists <variable> in <collection>: <predicate>
|
|
```
|
|
|
|
**Semantics:**
|
|
- Returns `true` if predicate is true for any element
|
|
- Returns `false` for empty collections
|
|
|
|
**Examples:**
|
|
```storybook
|
|
// 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:
|
|
|
|
```storybook
|
|
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
|
|
|
|
```storybook
|
|
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
|
|
|
|
```storybook
|
|
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
|
|
|
|
```storybook
|
|
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? |
|
|
|----------|-----------|------------|--------|
|
|
| `==`, `!=` | number | number | ✓ |
|
|
| `==`, `!=` | decimal | decimal | ✓ |
|
|
| `==`, `!=` | text | text | ✓ |
|
|
| `==`, `!=` | boolean | boolean | ✓ |
|
|
| `==`, `!=` | enum | same enum | ✓ |
|
|
| `==`, `!=` | number | decimal | ✗ |
|
|
| `<`, `<=`, `>`, `>=` | number | number | ✓ |
|
|
| `<`, `<=`, `>`, `>=` | decimal | decimal | ✓ |
|
|
| `<`, `<=`, `>`, `>=` | text | text | ✗ |
|
|
|
|
### Implicit Coercion
|
|
|
|
**None.** Storybook has no implicit type coercion. All comparisons must be between compatible types.
|
|
|
|
**Error:**
|
|
```storybook
|
|
count == "5" // Error: number vs text
|
|
health < true // Error: number vs boolean
|
|
```
|
|
|
|
**Correct:**
|
|
```storybook
|
|
count == 5
|
|
health < 50
|
|
```
|
|
|
|
## Special Keyword: `is`
|
|
|
|
The `is` keyword provides syntactic sugar for equality with enum values:
|
|
|
|
```storybook
|
|
// Instead of:
|
|
status == active
|
|
|
|
// You can write:
|
|
status is active
|
|
```
|
|
|
|
**More examples:**
|
|
```storybook
|
|
name is "Martha"
|
|
skill_level is master
|
|
emotional_state is focused
|
|
```
|
|
|
|
This is purely syntactic—`is` and `==` are equivalent.
|
|
|
|
## Complete Examples
|
|
|
|
### Simple Conditions
|
|
|
|
```storybook
|
|
health < 20
|
|
enemy_nearby
|
|
not is_ready
|
|
count > 5
|
|
```
|
|
|
|
### Complex Conditions
|
|
|
|
```storybook
|
|
(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
|
|
|
|
```storybook
|
|
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
|
|
|
|
```storybook
|
|
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
|
|
|
|
```storybook
|
|
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
|
|
|
|
1. **Type consistency**: Both sides of comparison must be compatible types
|
|
2. **Boolean context**: Logical operators (`and`, `or`, `not`) require boolean operands
|
|
3. **Field existence**: Referenced fields must exist on the entity
|
|
4. **Collection validity**: Quantifiers require collection-typed expressions
|
|
5. **Variable scope**: Quantifier variables only valid within their predicate
|
|
6. **No division by zero**: Arithmetic operations must not divide by zero
|
|
7. **Enum validity**: Enum comparisons must reference defined enum values
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Parentheses for Clarity
|
|
|
|
**Avoid:**
|
|
```storybook
|
|
health < 50 or is_poisoned and has_antidote
|
|
```
|
|
|
|
**Prefer:**
|
|
```storybook
|
|
(health < 50 or is_poisoned) and has_antidote
|
|
```
|
|
|
|
### 2. Break Complex Conditions
|
|
|
|
**Avoid:**
|
|
```storybook
|
|
on (health < 20 and not has_potion) or (surrounded and not has_escape) or (enemy_count > 10 and weapon_broken) -> desperate
|
|
```
|
|
|
|
**Prefer:**
|
|
```storybook
|
|
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:**
|
|
```storybook
|
|
on health < (max_health * 0.2) and enemy_count > 5 -> flee
|
|
```
|
|
|
|
**Consider:**
|
|
```storybook
|
|
// 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:**
|
|
```storybook
|
|
status is active
|
|
emotional_state is focused
|
|
```
|
|
|
|
**Over:**
|
|
```storybook
|
|
status == active
|
|
emotional_state == focused
|
|
```
|
|
|
|
### 5. Quantifiers for Collections
|
|
|
|
**Avoid:**
|
|
```storybook
|
|
// Manual checks for each element
|
|
if enemy1.defeated and enemy2.defeated and enemy3.defeated
|
|
```
|
|
|
|
**Prefer:**
|
|
```storybook
|
|
if forall enemy in enemies: enemy.defeated
|
|
```
|
|
|
|
## Cross-References
|
|
|
|
- [Life Arcs](./13-life-arcs.md) - Transition conditions
|
|
- [Behavior Trees](./11-behavior-trees.md) - Guard and condition nodes
|
|
- [Decorators](./12-decorators.md) - Guard decorator
|
|
- [Relationships](./15-relationships.md) - Self/other field access
|
|
- [Value Types](./18-value-types.md) - 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
|