use crate::syntax::ast::*; use crate::syntax::lexer::Token; grammar; // ===== Top-level ===== pub File: File = { => File { declarations } }; Declaration: Declaration = { => Declaration::Use(u), => Declaration::Character(c), => Declaration::Template(t), => Declaration::LifeArc(l), => Declaration::Schedule(s), => Declaration::Behavior(b), => Declaration::Institution(i), => Declaration::Relationship(r), => Declaration::Location(loc), => Declaration::Species(sp), => Declaration::Concept(concept), => Declaration::SubConcept(sub), => Declaration::ConceptComparison(comp), }; // ===== Use declarations ===== UseDecl: UseDecl = { "use" ";" => UseDecl { path, kind: UseKind::Single, span: Span::new(start, end), }, "use" "::" "{" > "}" ";" => UseDecl { path: base, kind: UseKind::Grouped(items), span: Span::new(start, end), }, "use" "::" "*" ";" => UseDecl { path, kind: UseKind::Wildcard, span: Span::new(start, end), }, }; Path: Vec = { }; PathSegments: Vec = { => vec![<>], "::" => { v.push(i); v } }; DottedPath: Vec = { => vec![<>], "." => { v.push(i); v } }; // ===== Character ===== Character: Character = { "character" )?> "{" "}" => { Character { name, species, fields: body.0, template, uses_behaviors: body.1, uses_schedule: body.2, span: Span::new(start, end), } } }; // Character body can contain fields and uses clauses in any order CharacterBody: (Vec, Option>, Option>) = { => { 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 = { => CharacterBodyItem::Field(<>), => CharacterBodyItem::UsesBehaviors(<>), => CharacterBodyItem::UsesSchedule(<>), }; TemplateClause: Vec = { "from" )*> => { let mut templates = vec![t]; templates.extend(rest); templates } }; // uses behaviors: [...] UsesBehaviorsClause: Vec = { "uses" "behaviors" ":" "[" > "]" => links, }; // Individual behavior link: { tree: BehaviorName, priority: high, when: condition } BehaviorLinkItem: BehaviorLink = { "{" "}" => { 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(start, end), } } }; // Fields within a behavior link BehaviorLinkField: BehaviorLinkField = { "tree" ":" ","? => BehaviorLinkField::Tree(path), "when" ":" ","? => BehaviorLinkField::Condition(expr), "priority" ":" ","? => BehaviorLinkField::Priority(p), }; PriorityLevel: Priority = { => 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 = { "uses" "schedule" ":" => vec![name], "uses" "schedules" ":" "[" > "]" => names, }; // ===== Template ===== Template: Template = { "template" )?> "{" "}" => { 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, species_base, fields, strict: strict.is_some(), includes, uses_behaviors, uses_schedule, span: Span::new(start, end), } } }; // Template body items (fields, includes, uses behaviors, uses schedule) TemplateBodyItem: TemplateBodyItem = { => TemplateBodyItem::Field(<>), "include" => TemplateBodyItem::Include(name), => TemplateBodyItem::UsesBehaviors(<>), => TemplateBodyItem::UsesSchedule(<>), }; // Template-level behavior links (simple list, no priorities/conditions) TemplateUsesBehaviorsClause: Vec = { "uses" "behaviors" ":" )*> => { let mut names = vec![first]; names.extend(rest); let span = Span::new(start, end); names.into_iter().map(|name| BehaviorLink { tree: vec![name], condition: None, priority: Priority::Normal, span: span.clone(), }).collect() }, }; // Template-level schedule links TemplateUsesScheduleClause: Vec = { "uses" "schedule" ":" => vec![name], }; // Template/Species include clause Include: String = { "include" => name }; // ===== Fields ===== Field: Field = { ":" => Field { name: path.join("."), value, span: Span::new(start, end), }, => Field { name: pb.tag.clone(), value: Value::ProseBlock(pb), span: Span::new(start, end), } }; Value: Value = { => Value::Number(<>), => Value::Decimal(<>), => Value::Text(<>), => Value::Boolean(<>), "any" => Value::Any, ".." => Value::Range( Box::new(Value::Number(lo)), Box::new(Value::Number(hi)) ), ".." => Value::Range( Box::new(Value::Decimal(lo)), Box::new(Value::Decimal(hi)) ), => Value::Time(t), => Value::Duration(d), => Value::Identifier(p), => Value::ProseBlock(<>), "[" > "]" => Value::List(values), "{" "}" => Value::Object(fields), => Value::Override(<>), }; BoolLit: bool = { "true" => true, "false" => false, }; Time: Time = { => { let parts: Vec<&str> = s.split(':').collect(); let hour = parts[0].parse().unwrap_or(0); let minute = parts[1].parse().unwrap_or(0); let second = if parts.len() > 2 { parts[2].parse().unwrap_or(0) } else { 0 }; Time { hour, minute, second } } }; Duration: Duration = { => { let mut hours = 0; let mut minutes = 0; let mut seconds = 0; let mut num = String::new(); for ch in s.chars() { if ch.is_ascii_digit() { num.push(ch); } else { let val: u32 = num.parse().unwrap_or(0); match ch { 'h' => hours = val, 'm' => minutes = val, 's' => seconds = val, _ => {} } num.clear(); } } Duration { hours, minutes, seconds } } }; // Duration string for decorator timeouts/cooldowns (e.g., "5s", "30m", "2h", "1d") BehaviorDurationLit: String = { => s }; ProseBlock: ProseBlock = { ProseBlockToken }; Override: Override = { "@" "{" "}" => Override { base, overrides, span: Span::new(start, end), } }; OverrideOp: OverrideOp = { "remove" => OverrideOp::Remove(name), "append" => OverrideOp::Append(f), => OverrideOp::Set(f), }; // ===== Life Arc ===== LifeArc: LifeArc = { "life_arc" "{" "}" => LifeArc { name, required_fields: reqs.unwrap_or_default(), states, span: Span::new(start, end), } }; RequiresClause: Vec = { "requires" "{" > "}" => reqs, }; FieldReq: FieldRequirement = { ":" => FieldRequirement { name, type_name, span: Span::new(start, end), } }; ArcState: ArcState = { "state" "{" "}" => ArcState { name, on_enter, transitions, span: Span::new(start, end), } }; OnEnter: Vec = { "on" "enter" "{" "}" => fields }; Transition: Transition = { "on" "->" => Transition { to, condition: cond, span: Span::new(start, end), } }; // ===== Schedule ===== Schedule: Schedule = { // Simple schedule: schedule Name { ... } "schedule" "{" "}" => Schedule { name, modifies: None, fields: body.0, blocks: body.1, recurrences: body.2, span: Span::new(start, end), }, // Modifying schedule: schedule Name modifies Base { ... } "schedule" "modifies" "{" "}" => Schedule { name, modifies: Some(base), fields: body.0, blocks: body.1, recurrences: body.2, span: Span::new(start, end), } }; // Schedule body can contain fields (prose blocks), blocks, and recurrence patterns ScheduleBody: (Vec, Vec, Vec) = { => { 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 = { => ScheduleBodyItem::Field(<>), => ScheduleBodyItem::Block(<>), => ScheduleBodyItem::Recurrence(<>), }; ScheduleBlock: ScheduleBlock = { // Legacy syntax: time -> time : activity { fields } "->" ":" "{" "}" => ScheduleBlock { name: None, is_override: false, start, end, activity, action: None, temporal_constraint: None, fields, span: Span::new(s, e), }, // Named block: block name { time, action, fields } "block" "{" "}" => 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(s, e), }, // Override block: override name { time, action, fields } "override" "{" "}" => 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(s, e), } }; // Block content: time range, optional action, and fields BlockContent: (Time, Time, Option>, Vec) = { => { 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 = { "->" ","? => BlockContentItem::TimeRange(start, end), => BlockContentItem::Field(<>), }; // Recurrence pattern: recurrence Name on DayOfWeek { blocks } RecurrencePattern: RecurrencePattern = { "recurrence" "on" "{" "}" => RecurrencePattern { name, constraint: TemporalConstraint::DayOfWeek(day), blocks, span: Span::new(start, end), } }; // ===== Behavior Trees ===== Behavior: Behavior = { "behavior" "{" "}" => Behavior { name, root, span: Span::new(start, end), } }; BehaviorNode: BehaviorNode = { , , , , , , }; // Selector node: choose { ... } or choose label { ... } SelectorNode: BehaviorNode = { "choose" "{" "}" => BehaviorNode::Selector { label, children, }, }; // Sequence node: then { ... } or then label { ... } SequenceNode: BehaviorNode = { "then" "{" "}" => BehaviorNode::Sequence { label, children, }, }; // Condition node: if(expr) or when(expr) // if(expr) { child } is the decorator form (replaces old "guard" keyword) ConditionNode: BehaviorNode = { "if" "(" ")" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::If(condition), child: Box::new(child), }, "if" "(" ")" => BehaviorNode::Condition(condition), "when" "(" ")" => BehaviorNode::Condition(condition), }; // Decorator node: keyword [params] { child } DecoratorNode: BehaviorNode = { , , , , , , , , , }; DecoratorRepeat: BehaviorNode = { "repeat" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::Repeat, child: Box::new(child), }, }; DecoratorRepeatN: BehaviorNode = { "repeat" "(" ")" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::RepeatN(n as u32), child: Box::new(child), }, }; DecoratorRepeatRange: BehaviorNode = { "repeat" "(" ".." ")" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::RepeatRange(min as u32, max as u32), child: Box::new(child), }, }; DecoratorInvert: BehaviorNode = { "invert" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::Invert, child: Box::new(child), }, }; DecoratorRetry: BehaviorNode = { "retry" "(" ")" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::Retry(n as u32), child: Box::new(child), }, }; DecoratorTimeout: BehaviorNode = { "timeout" "(" ")" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::Timeout(duration), child: Box::new(child), }, }; DecoratorCooldown: BehaviorNode = { "cooldown" "(" ")" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::Cooldown(duration), child: Box::new(child), }, }; DecoratorSucceedAlways: BehaviorNode = { "succeed_always" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::SucceedAlways, child: Box::new(child), }, }; DecoratorFailAlways: BehaviorNode = { "fail_always" "{" "}" => BehaviorNode::Decorator { decorator_type: DecoratorType::FailAlways, child: Box::new(child), }, }; // Action node: action_name or action_name(params) ActionNode: BehaviorNode = { "(" > ")" => BehaviorNode::Action(name, params), => BehaviorNode::Action(name, vec![]), }; ActionParam: Field = { // Named parameter: field: value ":" => Field { name: path.join("."), value, span: Span::new(start, end), }, // Positional parameter: just a value (use empty string as field name) => Field { name: String::new(), value, span: Span::new(start, end), }, }; // Subtree node: include path::to::subtree SubTreeNode: BehaviorNode = { "include" => BehaviorNode::SubTree(path), }; // ===== Institution ===== Institution: Institution = { "institution" "{" "}" => { Institution { name, fields: body.0, uses_behaviors: body.1, uses_schedule: body.2, span: Span::new(start, end), } } }; // Institution body can contain fields and uses clauses in any order InstitutionBody: (Vec, Option>, Option>) = { => { 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 = { => InstitutionBodyItem::Field(<>), => InstitutionBodyItem::UsesBehaviors(<>), => InstitutionBodyItem::UsesSchedule(<>), }; // ===== Relationship ===== Relationship: Relationship = { "relationship" "{" "}" => Relationship { name, participants, fields, span: Span::new(start, end), } }; Participant: Participant = { // Participant with role and block (block required) "as" "{" "}" => Participant { name, role: Some(role), fields, span: Span::new(start, end), }, // Participant without role (block still required) "{" "}" => Participant { name, role: None, fields, span: Span::new(start, end), }, }; // ===== Location ===== Location: Location = { "location" "{" "}" => Location { name, fields, span: Span::new(start, end), } }; // ===== Species ===== Species: Species = { "species" "{" "}" => Species { name, includes, fields, span: Span::new(start, end), } }; // ===== Enum ===== // ===== Type System Declarations ===== ConceptDecl: ConceptDecl = { "concept" => ConceptDecl { name, span: Span::new(start, end), } }; SubConceptDecl: SubConceptDecl = { // Enum-like sub_concept: sub_concept Cup.Type { Small, Medium, Large } "sub_concept" "." "{" > "}" => { SubConceptDecl { name, parent_concept: parent, kind: SubConceptKind::Enum { variants }, span: Span::new(start, end), } }, // Record-like sub_concept with at least one field: sub_concept Cup.Material { weight: 100 } "sub_concept" "." "{" ":" ":" )*> ","? "}" => { let field_span = Span::new(start, end); let mut fields = vec![Field { name: first, value: first_val, span: field_span.clone(), }]; for (field_name, field_val) in rest { fields.push(Field { name: field_name, value: field_val, span: field_span.clone(), }); } SubConceptDecl { name, parent_concept: parent, kind: SubConceptKind::Record { fields }, span: Span::new(start, end), } }, }; ConceptComparisonDecl: ConceptComparisonDecl = { "concept_comparison" "{" > "}" => ConceptComparisonDecl { name, variants, span: Span::new(start, end), } }; VariantPattern: VariantPattern = { ":" "{" > "}" => VariantPattern { name, conditions, span: Span::new(start, end), } }; FieldCondition: FieldCondition = { ":" "any" => FieldCondition { field_name: field, condition: Condition::Any, span: Span::new(start, end), }, ":" => FieldCondition { field_name: field, condition: Condition::Is(cond), span: Span::new(start, end), }, }; // Parse "FieldName is Value1 or FieldName is Value2" and extract the values IsCondition: Vec = { )*> => { let mut values = vec![first]; values.extend(rest); values } }; IsValue: String = { "is" => value }; // ===== Expressions ===== // Expression grammar with proper precedence: // or > and > not > field_access > comparison > term Expr: Expr = { , }; // Logical OR (lowest precedence) OrExpr: Expr = { "or" => { Expr::Logical( Box::new(left), LogicalOp::Or, Box::new(right) ) }, , }; // Logical AND AndExpr: Expr = { "and" => { Expr::Logical( Box::new(left), LogicalOp::And, Box::new(right) ) }, , }; // Unary NOT NotExpr: Expr = { "not" => { Expr::Unary( UnaryOp::Not, Box::new(expr) ) }, , }; // Comparison expressions ComparisonExpr: Expr = { // Equality: field access or path is (literal or identifier) "is" => { Expr::Comparison( Box::new(left), CompOp::Eq, Box::new(right) ) }, // Comparison: field access or path > literal/identifier, etc. => { Expr::Comparison( Box::new(left), op, Box::new(right) ) }, // Just a field access expression , }; // Field access with dot notation (binds tightest) FieldAccessExpr: Expr = { "." => { Expr::FieldAccess( Box::new(base), field ) }, , }; // Primary expressions (atoms) PrimaryExpr: Expr = { "self" => Expr::Identifier(vec!["self".to_string()]), "other" => Expr::Identifier(vec!["other".to_string()]), , => Expr::Identifier(<>), }; InequalityOp: CompOp = { ">" => CompOp::Gt, ">=" => CompOp::Ge, "<" => CompOp::Lt, "<=" => CompOp::Le, }; Literal: Expr = { => Expr::NumberLit(<>), => Expr::DecimalLit(<>), => Expr::TextLit(<>), => Expr::BooleanLit(<>), }; // ===== Helpers ===== Comma: Vec = { ",")*> => match e { None => v, Some(e) => { let mut v = v; v.push(e); v } } }; // ===== Token conversion ===== extern { type Location = usize; type Error = crate::syntax::ParseError; enum Token { // Keywords "use" => Token::Use, "character" => Token::Character, "template" => Token::Template, "life_arc" => Token::LifeArc, "schedule" => Token::Schedule, "behavior" => Token::Behavior, "institution" => Token::Institution, "relationship" => Token::Relationship, "location" => Token::Location, "species" => Token::Species, "concept" => Token::Concept, "sub_concept" => Token::SubConcept, "concept_comparison" => Token::ConceptComparison, "any" => Token::Any, "requires" => Token::Requires, "state" => Token::State, "on" => Token::On, "enter" => Token::Enter, "as" => Token::As, "self" => Token::SelfKw, "other" => Token::Other, "remove" => Token::Remove, "append" => Token::Append, "forall" => Token::ForAll, "exists" => Token::Exists, "in" => Token::In, "where" => Token::Where, "and" => Token::And, "or" => Token::Or, "not" => Token::Not, "strict" => Token::Strict, "include" => Token::Include, "from" => Token::From, "is" => Token::Is, "uses" => Token::Uses, "behaviors" => Token::Behaviors, "schedules" => Token::Schedules, "tree" => Token::Tree, "priority" => Token::Priority, "modifies" => Token::Modifies, "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(), NumberLit => Token::NumberLit(), DecimalLit => Token::DecimalLit(), TextLit => Token::TextLit(), TimeLit => Token::TimeLit(), DurationLit => Token::DurationLit(), ProseBlockToken => Token::ProseBlock(), // Punctuation "{" => Token::LBrace, "}" => Token::RBrace, "(" => Token::LParen, ")" => Token::RParen, "[" => Token::LBracket, "]" => Token::RBracket, ":" => Token::Colon, "::" => Token::ColonColon, ";" => Token::Semicolon, "," => Token::Comma, "." => Token::Dot, ".." => Token::DotDot, "*" => Token::Star, "?" => Token::Question, "@" => Token::At, // Operators ">" => Token::Gt, ">=" => Token::Ge, "<" => Token::Lt, "<=" => Token::Le, "->" => Token::Arrow, } }