release: Storybook v0.2.0 - Major syntax and features update

BREAKING CHANGES:
- Relationship syntax now requires blocks for all participants
- Removed self/other perspective blocks from relationships
- Replaced 'guard' keyword with 'if' for behavior tree decorators

Language Features:
- Add tree-sitter grammar with improved if/condition disambiguation
- Add comprehensive tutorial and reference documentation
- Add SBIR v0.2.0 binary format specification
- Add resource linking system for behaviors and schedules
- Add year-long schedule patterns (day, season, recurrence)
- Add behavior tree enhancements (named nodes, decorators)

Documentation:
- Complete tutorial series (9 chapters) with baker family examples
- Complete reference documentation for all language features
- SBIR v0.2.0 specification with binary format details
- Added locations and institutions documentation

Examples:
- Convert all examples to baker family scenario
- Add comprehensive working examples

Tooling:
- Zed extension with LSP integration
- Tree-sitter grammar for syntax highlighting
- Build scripts and development tools

Version Updates:
- Main package: 0.1.0 → 0.2.0
- Tree-sitter grammar: 0.1.0 → 0.2.0
- Zed extension: 0.1.0 → 0.2.0
- Storybook editor: 0.1.0 → 0.2.0
This commit is contained in:
2026-02-13 21:52:03 +00:00
parent 80332971b8
commit 16deb5d237
290 changed files with 90316 additions and 5827 deletions

View File

@@ -66,15 +66,44 @@ DottedPath: Vec<String> = {
// ===== Character =====
Character: Character = {
"character" <name:Ident> <species:(":" <Ident>)?> <template:TemplateClause?> "{" <fields:Field*> "}" => Character {
name,
species,
fields,
template,
span: Span::new(0, 0),
"character" <name:Ident> <species:(":" <Ident>)?> <template:TemplateClause?> "{" <body:CharacterBody> "}" => {
Character {
name,
species,
fields: body.0,
template,
uses_behaviors: body.1,
uses_schedule: body.2,
span: Span::new(0, 0),
}
}
};
// Character body can contain fields and uses clauses in any order
CharacterBody: (Vec<Field>, Option<Vec<BehaviorLink>>, Option<Vec<String>>) = {
<items:CharacterBodyItem*> => {
let mut fields = Vec::new();
let mut uses_behaviors = None;
let mut uses_schedule = None;
for item in items {
match item {
CharacterBodyItem::Field(f) => fields.push(f),
CharacterBodyItem::UsesBehaviors(b) => uses_behaviors = Some(b),
CharacterBodyItem::UsesSchedule(s) => uses_schedule = Some(s),
}
}
(fields, uses_behaviors, uses_schedule)
}
};
CharacterBodyItem: CharacterBodyItem = {
<Field> => CharacterBodyItem::Field(<>),
<UsesBehaviorsClause> => CharacterBodyItem::UsesBehaviors(<>),
<UsesScheduleClause> => CharacterBodyItem::UsesSchedule(<>),
};
TemplateClause: Vec<String> = {
"from" <t:Ident> <rest:("," <Ident>)*> => {
let mut templates = vec![t];
@@ -83,18 +112,116 @@ TemplateClause: Vec<String> = {
}
};
// ===== Template =====
// uses behaviors: [...]
UsesBehaviorsClause: Vec<BehaviorLink> = {
"uses" "behaviors" ":" "[" <links:Comma<BehaviorLinkItem>> "]" => links,
};
Template: Template = {
"template" <name:Ident> <strict:"strict"?> "{" <includes:Include*> <fields:Field*> "}" => Template {
name,
fields,
strict: strict.is_some(),
includes,
span: Span::new(0, 0),
// Individual behavior link: { tree: BehaviorName, priority: high, when: condition }
BehaviorLinkItem: BehaviorLink = {
"{" <fields:BehaviorLinkField+> "}" => {
let mut tree = None;
let mut condition = None;
let mut priority = Priority::Normal;
for field in fields {
match field {
BehaviorLinkField::Tree(t) => tree = Some(t),
BehaviorLinkField::Condition(c) => condition = Some(c),
BehaviorLinkField::Priority(p) => priority = p,
}
}
BehaviorLink {
tree: tree.expect("behavior link must have 'tree' field"),
condition,
priority,
span: Span::new(0, 0),
}
}
};
// Fields within a behavior link
BehaviorLinkField: BehaviorLinkField = {
"tree" ":" <path:Path> ","? => BehaviorLinkField::Tree(path),
"when" ":" <expr:Expr> ","? => BehaviorLinkField::Condition(expr),
"priority" ":" <p:PriorityLevel> ","? => BehaviorLinkField::Priority(p),
};
PriorityLevel: Priority = {
<s:Ident> => match s.as_str() {
"low" => Priority::Low,
"normal" => Priority::Normal,
"high" => Priority::High,
"critical" => Priority::Critical,
_ => Priority::Normal, // Default to normal for invalid values
},
};
// uses schedule: ScheduleName or uses schedules: [Name1, Name2]
UsesScheduleClause: Vec<String> = {
"uses" "schedule" ":" <name:Ident> => vec![name],
"uses" "schedules" ":" "[" <names:Comma<Ident>> "]" => names,
};
// ===== Template =====
Template: Template = {
"template" <name:Ident> <strict:"strict"?> "{" <body:TemplateBodyItem*> "}" => {
let mut fields = Vec::new();
let mut includes = Vec::new();
let mut uses_behaviors = None;
let mut uses_schedule = None;
for item in body {
match item {
TemplateBodyItem::Field(f) => fields.push(f),
TemplateBodyItem::Include(inc) => includes.push(inc),
TemplateBodyItem::UsesBehaviors(b) => uses_behaviors = Some(b),
TemplateBodyItem::UsesSchedule(s) => uses_schedule = Some(s),
}
}
Template {
name,
fields,
strict: strict.is_some(),
includes,
uses_behaviors,
uses_schedule,
span: Span::new(0, 0),
}
}
};
// Template body items (fields, includes, uses behaviors, uses schedule)
TemplateBodyItem: TemplateBodyItem = {
<Field> => TemplateBodyItem::Field(<>),
"include" <name:Ident> => TemplateBodyItem::Include(name),
<TemplateUsesBehaviorsClause> => TemplateBodyItem::UsesBehaviors(<>),
<TemplateUsesScheduleClause> => TemplateBodyItem::UsesSchedule(<>),
};
// Template-level behavior links (simple list, no priorities/conditions)
TemplateUsesBehaviorsClause: Vec<BehaviorLink> = {
"uses" "behaviors" ":" <first:Ident> <rest:("," <Ident>)*> => {
let mut names = vec![first];
names.extend(rest);
names.into_iter().map(|name| BehaviorLink {
tree: vec![name],
condition: None,
priority: Priority::Normal,
span: Span::new(0, 0),
}).collect()
},
};
// Template-level schedule links
TemplateUsesScheduleClause: Vec<String> = {
"uses" "schedule" ":" <name:Ident> => vec![name],
};
// Template/Species include clause
Include: String = {
"include" <name:Ident> => name
};
@@ -181,6 +308,11 @@ Duration: Duration = {
}
};
// Duration string for decorator timeouts/cooldowns (e.g., "5s", "30m", "2h", "1d")
BehaviorDurationLit: String = {
<s:DurationLit> => s
};
ProseBlock: ProseBlock = {
ProseBlockToken
};
@@ -233,20 +365,140 @@ Transition: Transition = {
// ===== Schedule =====
Schedule: Schedule = {
"schedule" <name:Ident> "{" <fields:Field*> <blocks:ScheduleBlock*> "}" => Schedule {
// Simple schedule: schedule Name { ... }
"schedule" <name:Ident> "{" <body:ScheduleBody> "}" => Schedule {
name,
blocks,
extends: None,
fields: body.0,
blocks: body.1,
recurrences: body.2,
span: Span::new(0, 0),
},
// Extending schedule: schedule Name extends Base { ... }
"schedule" <name:Ident> "extends" <base:Ident> "{" <body:ScheduleBody> "}" => Schedule {
name,
extends: Some(base),
fields: body.0,
blocks: body.1,
recurrences: body.2,
span: Span::new(0, 0),
}
};
// Schedule body can contain fields (prose blocks), blocks, and recurrence patterns
ScheduleBody: (Vec<Field>, Vec<ScheduleBlock>, Vec<RecurrencePattern>) = {
<items:ScheduleBodyItem*> => {
let mut fields = Vec::new();
let mut blocks = Vec::new();
let mut recurrences = Vec::new();
for item in items {
match item {
ScheduleBodyItem::Field(f) => fields.push(f),
ScheduleBodyItem::Block(b) => blocks.push(b),
ScheduleBodyItem::Recurrence(r) => recurrences.push(r),
}
}
(fields, blocks, recurrences)
}
};
ScheduleBodyItem: ScheduleBodyItem = {
<Field> => ScheduleBodyItem::Field(<>),
<ScheduleBlock> => ScheduleBodyItem::Block(<>),
<RecurrencePattern> => ScheduleBodyItem::Recurrence(<>),
};
ScheduleBlock: ScheduleBlock = {
// Legacy syntax: time -> time : activity { fields }
<start:Time> "->" <end:Time> ":" <activity:Ident> "{" <fields:Field*> "}" => ScheduleBlock {
name: None,
is_override: false,
start,
end,
activity,
action: None,
temporal_constraint: None,
fields,
span: Span::new(0, 0),
},
// Named block: block name { time, action, fields }
"block" <name:Ident> "{" <content:BlockContent> "}" => ScheduleBlock {
name: Some(name),
is_override: false,
start: content.0,
end: content.1,
activity: String::new(), // Empty for new syntax
action: content.2,
temporal_constraint: None,
fields: content.3,
span: Span::new(0, 0),
},
// Override block: override name { time, action, fields }
"override" <name:Ident> "{" <content:BlockContent> "}" => ScheduleBlock {
name: Some(name),
is_override: true,
start: content.0,
end: content.1,
activity: String::new(), // Empty for new syntax
action: content.2,
temporal_constraint: None,
fields: content.3,
span: Span::new(0, 0),
}
};
// Block content: time range, optional action, and fields
BlockContent: (Time, Time, Option<Vec<String>>, Vec<Field>) = {
<items:BlockContentItem+> => {
let mut start = None;
let mut end = None;
let mut action = None;
let mut fields = Vec::new();
for item in items {
match item {
BlockContentItem::TimeRange(s, e) => {
start = Some(s);
end = Some(e);
}
BlockContentItem::Field(f) => {
if f.name == "action" {
// Extract action as qualified path from identifier value
if let Value::Identifier(path) = &f.value {
action = Some(path.clone());
}
} else {
fields.push(f);
}
}
}
}
(
start.expect("block must have time range"),
end.expect("block must have time range"),
action,
fields
)
}
};
BlockContentItem: BlockContentItem = {
<start:Time> "->" <end:Time> ","? => BlockContentItem::TimeRange(start, end),
<Field> => BlockContentItem::Field(<>),
};
// Recurrence pattern: recurrence Name on DayOfWeek { blocks }
RecurrencePattern: RecurrencePattern = {
"recurrence" <name:Ident> "on" <day:Ident> "{" <blocks:ScheduleBlock+> "}" => RecurrencePattern {
name,
constraint: TemporalConstraint::DayOfWeek(day),
blocks,
span: Span::new(0, 0),
}
};
@@ -263,23 +515,116 @@ Behavior: Behavior = {
BehaviorNode: BehaviorNode = {
<SelectorNode>,
<SequenceNode>,
<RepeatNode>,
<ConditionNode>,
<DecoratorNode>,
<ActionNode>,
<SubTreeNode>,
};
// Selector node: choose { ... } or choose label { ... }
SelectorNode: BehaviorNode = {
"?" "{" <nodes:BehaviorNode+> "}" => BehaviorNode::Selector(nodes),
"choose" <label:Ident?> "{" <children:BehaviorNode+> "}" => BehaviorNode::Selector {
label,
children,
},
};
// Sequence node: then { ... } or then label { ... }
SequenceNode: BehaviorNode = {
">" "{" <nodes:BehaviorNode+> "}" => BehaviorNode::Sequence(nodes),
"then" <label:Ident?> "{" <children:BehaviorNode+> "}" => BehaviorNode::Sequence {
label,
children,
},
};
RepeatNode: BehaviorNode = {
"*" "{" <node:BehaviorNode> "}" => BehaviorNode::Decorator("repeat".to_string(), Box::new(node)),
// Condition node: if(expr) or when(expr)
// if(expr) { child } is the decorator form (replaces old "guard" keyword)
ConditionNode: BehaviorNode = {
"if" "(" <condition:Expr> ")" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::If(condition),
child: Box::new(child),
},
"if" "(" <condition:Expr> ")" => BehaviorNode::Condition(condition),
"when" "(" <condition:Expr> ")" => BehaviorNode::Condition(condition),
};
// Decorator node: keyword [params] { child }
DecoratorNode: BehaviorNode = {
<DecoratorRepeat>,
<DecoratorRepeatN>,
<DecoratorRepeatRange>,
<DecoratorInvert>,
<DecoratorRetry>,
<DecoratorTimeout>,
<DecoratorCooldown>,
<DecoratorSucceedAlways>,
<DecoratorFailAlways>,
};
DecoratorRepeat: BehaviorNode = {
"repeat" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::Repeat,
child: Box::new(child),
},
};
DecoratorRepeatN: BehaviorNode = {
"repeat" "(" <n:IntLit> ")" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::RepeatN(n as u32),
child: Box::new(child),
},
};
DecoratorRepeatRange: BehaviorNode = {
"repeat" "(" <min:IntLit> ".." <max:IntLit> ")" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::RepeatRange(min as u32, max as u32),
child: Box::new(child),
},
};
DecoratorInvert: BehaviorNode = {
"invert" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::Invert,
child: Box::new(child),
},
};
DecoratorRetry: BehaviorNode = {
"retry" "(" <n:IntLit> ")" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::Retry(n as u32),
child: Box::new(child),
},
};
DecoratorTimeout: BehaviorNode = {
"timeout" "(" <duration:BehaviorDurationLit> ")" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::Timeout(duration),
child: Box::new(child),
},
};
DecoratorCooldown: BehaviorNode = {
"cooldown" "(" <duration:BehaviorDurationLit> ")" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::Cooldown(duration),
child: Box::new(child),
},
};
DecoratorSucceedAlways: BehaviorNode = {
"succeed_always" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::SucceedAlways,
child: Box::new(child),
},
};
DecoratorFailAlways: BehaviorNode = {
"fail_always" "{" <child:BehaviorNode> "}" => BehaviorNode::Decorator {
decorator_type: DecoratorType::FailAlways,
child: Box::new(child),
},
};
// Action node: action_name or action_name(params)
ActionNode: BehaviorNode = {
<name:Ident> "(" <params:Comma<ActionParam>> ")" => BehaviorNode::Action(name, params),
<name:Ident> => BehaviorNode::Action(name, vec![]),
@@ -300,20 +645,50 @@ ActionParam: Field = {
},
};
// Subtree node: include path::to::subtree
SubTreeNode: BehaviorNode = {
"@" <path:Path> => BehaviorNode::SubTree(path),
"include" <path:Path> => BehaviorNode::SubTree(path),
};
// ===== Institution =====
Institution: Institution = {
"institution" <name:Ident> "{" <fields:Field*> "}" => Institution {
name,
fields,
span: Span::new(0, 0),
"institution" <name:Ident> "{" <body:InstitutionBody> "}" => {
Institution {
name,
fields: body.0,
uses_behaviors: body.1,
uses_schedule: body.2,
span: Span::new(0, 0),
}
}
};
// Institution body can contain fields and uses clauses in any order
InstitutionBody: (Vec<Field>, Option<Vec<BehaviorLink>>, Option<Vec<String>>) = {
<items:InstitutionBodyItem*> => {
let mut fields = Vec::new();
let mut uses_behaviors = None;
let mut uses_schedule = None;
for item in items {
match item {
InstitutionBodyItem::Field(f) => fields.push(f),
InstitutionBodyItem::UsesBehaviors(b) => uses_behaviors = Some(b),
InstitutionBodyItem::UsesSchedule(s) => uses_schedule = Some(s),
}
}
(fields, uses_behaviors, uses_schedule)
}
};
InstitutionBodyItem: InstitutionBodyItem = {
<Field> => InstitutionBodyItem::Field(<>),
<UsesBehaviorsClause> => InstitutionBodyItem::UsesBehaviors(<>),
<UsesScheduleClause> => InstitutionBodyItem::UsesSchedule(<>),
};
// ===== Relationship =====
Relationship: Relationship = {
@@ -326,40 +701,22 @@ Relationship: Relationship = {
};
Participant: Participant = {
// Participant with inline block after name
<name:Path> "{" <fields:Field*> "}" => Participant {
role: None,
name,
self_block: Some(fields),
other_block: None,
span: Span::new(0, 0),
},
// Participant with role and inline block
// Participant with role and block (block required)
<name:Path> "as" <role:Ident> "{" <fields:Field*> "}" => Participant {
name,
role: Some(role),
name,
self_block: Some(fields),
other_block: None,
fields,
span: Span::new(0, 0),
},
// Participant without blocks (bare name)
<name:Path> => Participant {
// Participant without role (block still required)
<name:Path> "{" <fields:Field*> "}" => Participant {
name,
role: None,
name,
self_block: None,
other_block: None,
fields,
span: Span::new(0, 0),
},
};
SelfBlock: Vec<Field> = {
"self" "{" <fields:Field*> "}" => fields
};
OtherBlock: Vec<Field> = {
"other" "{" <fields:Field*> "}" => fields
};
// ===== Location =====
Location: Location = {
@@ -540,9 +897,32 @@ extern {
"include" => Token::Include,
"from" => Token::From,
"is" => Token::Is,
"uses" => Token::Uses,
"behaviors" => Token::Behaviors,
"schedules" => Token::Schedules,
"tree" => Token::Tree,
"priority" => Token::Priority,
"extends" => Token::Extends,
"override" => Token::Override,
"recurrence" => Token::Recurrence,
"season" => Token::Season,
"block" => Token::Block,
"true" => Token::True,
"false" => Token::False,
// Behavior tree keywords
"choose" => Token::Choose,
"then" => Token::Then,
"if" => Token::If,
"when" => Token::When,
"repeat" => Token::Repeat,
"invert" => Token::Invert,
"retry" => Token::Retry,
"timeout" => Token::Timeout,
"cooldown" => Token::Cooldown,
"succeed_always" => Token::SucceedAlways,
"fail_always" => Token::FailAlways,
// Literals
Ident => Token::Ident(<String>),
IntLit => Token::IntLit(<i64>),