feat(parser): add action parameters, repeater decorator, and named participants

Add syntax enhancements for more expressive behavior trees and relationships.

Action parameters:
- Support typed/positional parameters: WaitDuration(2.0)
- Support named parameters: SetValue(field: value)
- Enable inline values without field names

Repeater decorator:
- Add * { node } syntax for repeating behavior nodes
- Maps to Decorator("repeat", node)

Named participant blocks:
- Replace self/other blocks with named participant syntax
- Support multi-party relationships
- Example: Alice { role: seeker } instead of self { role: seeker }

Schedule block syntax:
- Require braces for schedule blocks to support narrative fields
- Update tests to use new syntax: 04:00 -> 06:00: Activity { }
This commit is contained in:
2026-02-08 15:45:56 +00:00
parent 4c89c80748
commit a8882eb3ec
12 changed files with 5906 additions and 3659 deletions

View File

@@ -55,11 +55,20 @@ PathSegments: Vec<String> = {
}
};
DottedPath: Vec<String> = {
<Ident> => vec![<>],
<mut v:DottedPath> "." <i:Ident> => {
v.push(i);
v
}
};
// ===== Character =====
Character: Character = {
"character" <name:Ident> <template:TemplateClause?> "{" <fields:Field*> "}" => Character {
"character" <name:Ident> <species:(":" <Ident>)?> <template:TemplateClause?> "{" <fields:Field*> "}" => Character {
name,
species,
fields,
template,
span: Span::new(0, 0),
@@ -93,10 +102,15 @@ Include: String = {
// ===== Fields =====
Field: Field = {
<name:Ident> ":" <value:Value> => Field {
name,
<path:DottedPath> ":" <value:Value> => Field {
name: path.join("."),
value,
span: Span::new(0, 0),
},
<pb:ProseBlock> => Field {
name: pb.tag.clone(),
value: Value::ProseBlock(pb),
span: Span::new(0, 0),
}
};
@@ -188,7 +202,7 @@ OverrideOp: OverrideOp = {
// ===== Life Arc =====
LifeArc: LifeArc = {
"life_arc" <name:Ident> "{" <states:ArcState*> "}" => LifeArc {
"life_arc" <name:Ident> "{" <fields:Field*> <states:ArcState*> "}" => LifeArc {
name,
states,
span: Span::new(0, 0),
@@ -196,13 +210,18 @@ LifeArc: LifeArc = {
};
ArcState: ArcState = {
"state" <name:Ident> "{" <transitions:Transition*> "}" => ArcState {
"state" <name:Ident> "{" <on_enter:OnEnter?> <fields:Field*> <transitions:Transition*> "}" => ArcState {
name,
on_enter,
transitions,
span: Span::new(0, 0),
}
};
OnEnter: Vec<Field> = {
"on" "enter" "{" <fields:Field*> "}" => fields
};
Transition: Transition = {
"on" <cond:Expr> "->" <to:Ident> => Transition {
to,
@@ -214,7 +233,7 @@ Transition: Transition = {
// ===== Schedule =====
Schedule: Schedule = {
"schedule" <name:Ident> "{" <blocks:ScheduleBlock*> "}" => Schedule {
"schedule" <name:Ident> "{" <fields:Field*> <blocks:ScheduleBlock*> "}" => Schedule {
name,
blocks,
span: Span::new(0, 0),
@@ -222,10 +241,11 @@ Schedule: Schedule = {
};
ScheduleBlock: ScheduleBlock = {
<start:Time> "->" <end:Time> ":" <activity:Ident> => ScheduleBlock {
<start:Time> "->" <end:Time> ":" <activity:Ident> "{" <fields:Field*> "}" => ScheduleBlock {
start,
end,
activity,
fields,
span: Span::new(0, 0),
}
};
@@ -233,7 +253,7 @@ ScheduleBlock: ScheduleBlock = {
// ===== Behavior Trees =====
Behavior: Behavior = {
"behavior" <name:Ident> "{" <root:BehaviorNode> "}" => Behavior {
"behavior" <name:Ident> "{" <fields:Field*> <root:BehaviorNode> "}" => Behavior {
name,
root,
span: Span::new(0, 0),
@@ -243,6 +263,7 @@ Behavior: Behavior = {
BehaviorNode: BehaviorNode = {
<SelectorNode>,
<SequenceNode>,
<RepeatNode>,
<ActionNode>,
<SubTreeNode>,
};
@@ -255,11 +276,30 @@ SequenceNode: BehaviorNode = {
">" "{" <nodes:BehaviorNode+> "}" => BehaviorNode::Sequence(nodes),
};
RepeatNode: BehaviorNode = {
"*" "{" <node:BehaviorNode> "}" => BehaviorNode::Decorator("repeat".to_string(), Box::new(node)),
};
ActionNode: BehaviorNode = {
<name:Ident> "(" <params:Comma<Field>> ")" => BehaviorNode::Action(name, params),
<name:Ident> "(" <params:Comma<ActionParam>> ")" => BehaviorNode::Action(name, params),
<name:Ident> => BehaviorNode::Action(name, vec![]),
};
ActionParam: Field = {
// Named parameter: field: value
<path:DottedPath> ":" <value:Value> => Field {
name: path.join("."),
value,
span: Span::new(0, 0),
},
// Positional parameter: just a value (use empty string as field name)
<value:Value> => Field {
name: String::new(),
value,
span: Span::new(0, 0),
},
};
SubTreeNode: BehaviorNode = {
"@" <path:Path> => BehaviorNode::SubTree(path),
};
@@ -286,13 +326,30 @@ Relationship: Relationship = {
};
Participant: Participant = {
<name:Path> <role:("as" <Ident>)?> <self_block:SelfBlock?> <other_block:OtherBlock?> => Participant {
role,
// Participant with inline block after name
<name:Path> "{" <fields:Field*> "}" => Participant {
role: None,
name,
self_block,
other_block,
self_block: Some(fields),
other_block: None,
span: Span::new(0, 0),
}
},
// Participant with role and inline block
<name:Path> "as" <role:Ident> "{" <fields:Field*> "}" => Participant {
role: Some(role),
name,
self_block: Some(fields),
other_block: None,
span: Span::new(0, 0),
},
// Participant without blocks (bare name)
<name:Path> => Participant {
role: None,
name,
self_block: None,
other_block: None,
span: Span::new(0, 0),
},
};
SelfBlock: Vec<Field> = {
@@ -316,8 +373,9 @@ Location: Location = {
// ===== Species =====
Species: Species = {
"species" <name:Ident> "{" <fields:Field*> "}" => Species {
"species" <name:Ident> "{" <includes:Include*> <fields:Field*> "}" => Species {
name,
includes,
fields,
span: Span::new(0, 0),
}
@@ -465,6 +523,7 @@ extern {
"enum" => Token::Enum,
"state" => Token::State,
"on" => Token::On,
"enter" => Token::Enter,
"as" => Token::As,
"self" => Token::SelfKw,
"other" => Token::Other,