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:
@@ -133,14 +133,9 @@ pub fn validate_relationship_bonds(relationships: &[Relationship], collector: &m
|
||||
}
|
||||
}
|
||||
|
||||
// Validate self/other blocks if present
|
||||
// Validate participant fields
|
||||
for participant in &rel.participants {
|
||||
if let Some(ref self_fields) = participant.self_block {
|
||||
validate_trait_ranges(self_fields, collector);
|
||||
}
|
||||
if let Some(ref other_fields) = participant.other_block {
|
||||
validate_trait_ranges(other_fields, collector);
|
||||
}
|
||||
validate_trait_ranges(&participant.fields, collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +160,7 @@ pub fn validate_schedule_overlaps(schedule: &Schedule, collector: &mut ErrorColl
|
||||
collector.add(ResolveError::ScheduleOverlap {
|
||||
block1: format!(
|
||||
"{} ({}:{:02}-{}:{:02})",
|
||||
block1.activity,
|
||||
block1.name.as_ref().unwrap_or(&block1.activity),
|
||||
block1.start.hour,
|
||||
block1.start.minute,
|
||||
block1.end.hour,
|
||||
@@ -173,7 +168,7 @@ pub fn validate_schedule_overlaps(schedule: &Schedule, collector: &mut ErrorColl
|
||||
),
|
||||
block2: format!(
|
||||
"{} ({}:{:02}-{}:{:02})",
|
||||
block2.activity,
|
||||
block2.name.as_ref().unwrap_or(&block2.activity),
|
||||
block2.start.hour,
|
||||
block2.start.minute,
|
||||
block2.end.hour,
|
||||
@@ -242,7 +237,7 @@ fn validate_tree_node_actions(
|
||||
collector: &mut ErrorCollector,
|
||||
) {
|
||||
match node {
|
||||
| BehaviorNode::Sequence(children) | BehaviorNode::Selector(children) => {
|
||||
| BehaviorNode::Sequence { children, .. } | BehaviorNode::Selector { children, .. } => {
|
||||
for child in children {
|
||||
validate_tree_node_actions(child, action_registry, tree_name, collector);
|
||||
}
|
||||
@@ -258,7 +253,7 @@ fn validate_tree_node_actions(
|
||||
| BehaviorNode::Condition(_) => {
|
||||
// Conditions are validated separately via expression validation
|
||||
},
|
||||
| BehaviorNode::Decorator(_name, child) => {
|
||||
| BehaviorNode::Decorator { child, .. } => {
|
||||
validate_tree_node_actions(child, action_registry, tree_name, collector);
|
||||
},
|
||||
| BehaviorNode::SubTree(_path) => {
|
||||
@@ -267,6 +262,154 @@ fn validate_tree_node_actions(
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate character resource linking
|
||||
///
|
||||
/// Checks:
|
||||
/// 1. No duplicate behavior tree references
|
||||
/// 2. Priority values are valid (handled by type system)
|
||||
pub fn validate_character_resource_links(character: &Character, collector: &mut ErrorCollector) {
|
||||
// Check for duplicate behavior tree references
|
||||
if let Some(ref behavior_links) = character.uses_behaviors {
|
||||
let mut seen_trees: HashSet<String> = HashSet::new();
|
||||
|
||||
for link in behavior_links {
|
||||
let tree_name = link.tree.join("::");
|
||||
|
||||
if seen_trees.contains(&tree_name) {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Character '{}' has duplicate behavior tree reference: {}",
|
||||
character.name,
|
||||
tree_name
|
||||
),
|
||||
help: Some(format!(
|
||||
"The behavior tree '{}' is referenced multiple times in the uses behaviors list. Each behavior tree should only be referenced once. If you want different conditions or priorities, combine them into a single entry.",
|
||||
tree_name
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
seen_trees.insert(tree_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate schedule references
|
||||
if let Some(ref schedules) = character.uses_schedule {
|
||||
let mut seen_schedules: HashSet<String> = HashSet::new();
|
||||
|
||||
for schedule in schedules {
|
||||
if seen_schedules.contains(schedule) {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Character '{}' has duplicate schedule reference: {}",
|
||||
character.name,
|
||||
schedule
|
||||
),
|
||||
help: Some(format!(
|
||||
"The schedule '{}' is referenced multiple times. Each schedule should only be referenced once.",
|
||||
schedule
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
seen_schedules.insert(schedule.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate institution resource linking
|
||||
pub fn validate_institution_resource_links(
|
||||
institution: &Institution,
|
||||
collector: &mut ErrorCollector,
|
||||
) {
|
||||
// Check for duplicate behavior tree references
|
||||
if let Some(ref behavior_links) = institution.uses_behaviors {
|
||||
let mut seen_trees: HashSet<String> = HashSet::new();
|
||||
|
||||
for link in behavior_links {
|
||||
let tree_name = link.tree.join("::");
|
||||
|
||||
if seen_trees.contains(&tree_name) {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Institution '{}' has duplicate behavior tree reference: {}",
|
||||
institution.name,
|
||||
tree_name
|
||||
),
|
||||
help: Some(format!(
|
||||
"The behavior tree '{}' is referenced multiple times. Each behavior tree should only be referenced once.",
|
||||
tree_name
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
seen_trees.insert(tree_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate schedule references
|
||||
if let Some(ref schedules) = institution.uses_schedule {
|
||||
let mut seen_schedules: HashSet<String> = HashSet::new();
|
||||
|
||||
for schedule in schedules {
|
||||
if seen_schedules.contains(schedule) {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Institution '{}' has duplicate schedule reference: {}",
|
||||
institution.name, schedule
|
||||
),
|
||||
help: Some(format!(
|
||||
"The schedule '{}' is referenced multiple times.",
|
||||
schedule
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
seen_schedules.insert(schedule.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate schedule composition requirements
|
||||
///
|
||||
/// Checks:
|
||||
/// 1. All blocks in extended schedules have names
|
||||
/// 2. Override blocks reference valid block names (requires name resolution)
|
||||
pub fn validate_schedule_composition(schedule: &Schedule, collector: &mut ErrorCollector) {
|
||||
// If schedule extends another, all blocks must have names for override system
|
||||
if schedule.extends.is_some() {
|
||||
for block in &schedule.blocks {
|
||||
if block.name.is_none() && !block.activity.is_empty() {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Schedule '{}' extends another schedule but has unnamed blocks",
|
||||
schedule.name
|
||||
),
|
||||
help: Some(
|
||||
"When a schedule extends another, all blocks must have names to support the override system. Use 'block name { ... }' syntax instead of 'time -> time : activity { ... }'.".to_string()
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that new-style blocks have action references
|
||||
for block in &schedule.blocks {
|
||||
if block.name.is_some() && block.action.is_none() && block.activity.is_empty() {
|
||||
collector.add(ResolveError::ValidationError {
|
||||
message: format!(
|
||||
"Schedule '{}' block '{}' missing action reference",
|
||||
schedule.name,
|
||||
block.name.as_ref().unwrap()
|
||||
),
|
||||
help: Some(
|
||||
"Named blocks should specify a behavior using 'action: BehaviorName'. Example: 'block work { 9:00 -> 17:00, action: WorkBehavior }'".to_string()
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate an entire file
|
||||
///
|
||||
/// Collects all validation errors and returns them together instead of failing
|
||||
@@ -278,12 +421,17 @@ pub fn validate_file(file: &File, action_registry: &HashSet<String>) -> Result<(
|
||||
match decl {
|
||||
| Declaration::Character(c) => {
|
||||
validate_trait_ranges(&c.fields, &mut collector);
|
||||
validate_character_resource_links(c, &mut collector);
|
||||
},
|
||||
| Declaration::Institution(i) => {
|
||||
validate_institution_resource_links(i, &mut collector);
|
||||
},
|
||||
| Declaration::Relationship(r) => {
|
||||
validate_relationship_bonds(std::slice::from_ref(r), &mut collector);
|
||||
},
|
||||
| Declaration::Schedule(s) => {
|
||||
validate_schedule_overlaps(s, &mut collector);
|
||||
validate_schedule_composition(s, &mut collector);
|
||||
},
|
||||
| Declaration::LifeArc(la) => {
|
||||
validate_life_arc_transitions(la, &mut collector);
|
||||
@@ -472,10 +620,13 @@ mod tests {
|
||||
|
||||
let tree = Behavior {
|
||||
name: "Test".to_string(),
|
||||
root: BehaviorNode::Sequence(vec![
|
||||
BehaviorNode::Action("walk".to_string(), vec![]),
|
||||
BehaviorNode::Action("eat".to_string(), vec![]),
|
||||
]),
|
||||
root: BehaviorNode::Sequence {
|
||||
label: None,
|
||||
children: vec![
|
||||
BehaviorNode::Action("walk".to_string(), vec![]),
|
||||
BehaviorNode::Action("eat".to_string(), vec![]),
|
||||
],
|
||||
},
|
||||
span: Span::new(0, 100),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user