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::Enum(e), }; // ===== Use declarations ===== UseDecl: UseDecl = { "use" ";" => UseDecl { path, kind: UseKind::Single, span: Span::new(0, 0), // TODO: track actual spans }, "use" "::" "{" > "}" ";" => UseDecl { path: base, kind: UseKind::Grouped(items), span: Span::new(0, 0), }, "use" "::" "*" ";" => UseDecl { path, kind: UseKind::Wildcard, span: Span::new(0, 0), }, }; Path: Vec = { }; PathSegments: Vec = { => vec![<>], "::" => { v.push(i); v } }; DottedPath: Vec = { => vec![<>], "." => { v.push(i); v } }; // ===== Character ===== Character: Character = { "character" )?> "{" "}" => Character { name, species, fields, template, span: Span::new(0, 0), } }; TemplateClause: Vec = { "from" )*> => { let mut templates = vec![t]; templates.extend(rest); templates } }; // ===== Template ===== Template: Template = { "template" "{" "}" => Template { name, fields, strict: strict.is_some(), includes, span: Span::new(0, 0), } }; Include: String = { "include" => name }; // ===== Fields ===== Field: Field = { ":" => Field { name: path.join("."), value, span: Span::new(0, 0), }, => Field { name: pb.tag.clone(), value: Value::ProseBlock(pb), span: Span::new(0, 0), } }; Value: Value = { => Value::Int(<>), => Value::Float(<>), => Value::String(<>), => Value::Bool(<>), ".." => Value::Range( Box::new(Value::Int(lo)), Box::new(Value::Int(hi)) ), ".." => Value::Range( Box::new(Value::Float(lo)), Box::new(Value::Float(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 } } }; ProseBlock: ProseBlock = { ProseBlockToken }; Override: Override = { "@" "{" "}" => Override { base, overrides, span: Span::new(0, 0), } }; OverrideOp: OverrideOp = { "remove" => OverrideOp::Remove(name), "append" => OverrideOp::Append(f), => OverrideOp::Set(f), }; // ===== Life Arc ===== LifeArc: LifeArc = { "life_arc" "{" "}" => LifeArc { name, states, span: Span::new(0, 0), } }; ArcState: ArcState = { "state" "{" "}" => ArcState { name, on_enter, transitions, span: Span::new(0, 0), } }; OnEnter: Vec = { "on" "enter" "{" "}" => fields }; Transition: Transition = { "on" "->" => Transition { to, condition: cond, span: Span::new(0, 0), } }; // ===== Schedule ===== Schedule: Schedule = { "schedule" "{" "}" => Schedule { name, blocks, span: Span::new(0, 0), } }; ScheduleBlock: ScheduleBlock = { "->" ":" "{" "}" => ScheduleBlock { start, end, activity, fields, span: Span::new(0, 0), } }; // ===== Behavior Trees ===== Behavior: Behavior = { "behavior" "{" "}" => Behavior { name, root, span: Span::new(0, 0), } }; BehaviorNode: BehaviorNode = { , , , , , }; SelectorNode: BehaviorNode = { "?" "{" "}" => BehaviorNode::Selector(nodes), }; SequenceNode: BehaviorNode = { ">" "{" "}" => BehaviorNode::Sequence(nodes), }; RepeatNode: BehaviorNode = { "*" "{" "}" => BehaviorNode::Decorator("repeat".to_string(), Box::new(node)), }; ActionNode: BehaviorNode = { "(" > ")" => BehaviorNode::Action(name, params), => BehaviorNode::Action(name, vec![]), }; ActionParam: Field = { // Named parameter: field: value ":" => Field { name: path.join("."), value, span: Span::new(0, 0), }, // Positional parameter: just a value (use empty string as field name) => Field { name: String::new(), value, span: Span::new(0, 0), }, }; SubTreeNode: BehaviorNode = { "@" => BehaviorNode::SubTree(path), }; // ===== Institution ===== Institution: Institution = { "institution" "{" "}" => Institution { name, fields, span: Span::new(0, 0), } }; // ===== Relationship ===== Relationship: Relationship = { "relationship" "{" "}" => Relationship { name, participants, fields, span: Span::new(0, 0), } }; Participant: Participant = { // Participant with inline block after name "{" "}" => Participant { role: None, name, self_block: Some(fields), other_block: None, span: Span::new(0, 0), }, // Participant with role and inline block "as" "{" "}" => Participant { role: Some(role), name, self_block: Some(fields), other_block: None, span: Span::new(0, 0), }, // Participant without blocks (bare name) => Participant { role: None, name, self_block: None, other_block: None, span: Span::new(0, 0), }, }; SelfBlock: Vec = { "self" "{" "}" => fields }; OtherBlock: Vec = { "other" "{" "}" => fields }; // ===== Location ===== Location: Location = { "location" "{" "}" => Location { name, fields, span: Span::new(0, 0), } }; // ===== Species ===== Species: Species = { "species" "{" "}" => Species { name, includes, fields, span: Span::new(0, 0), } }; // ===== Enum ===== EnumDecl: EnumDecl = { "enum" "{" > "}" => EnumDecl { name, variants, span: Span::new(0, 0), } }; // ===== 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::IntLit(<>), => Expr::FloatLit(<>), => Expr::StringLit(<>), => Expr::BoolLit(<>), }; // ===== 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, "enum" => Token::Enum, "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, "true" => Token::True, "false" => Token::False, // Literals Ident => Token::Ident(), IntLit => Token::IntLit(), FloatLit => Token::FloatLit(), StringLit => Token::StringLit(), 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, } }