2026-02-08 13:24:35 +00:00
|
|
|
use crate::syntax::ast::*;
|
|
|
|
|
use crate::syntax::lexer::Token;
|
|
|
|
|
|
|
|
|
|
grammar;
|
|
|
|
|
|
|
|
|
|
// ===== Top-level =====
|
|
|
|
|
|
|
|
|
|
pub File: File = {
|
|
|
|
|
<declarations:Declaration*> => File { declarations }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Declaration: Declaration = {
|
|
|
|
|
<u:UseDecl> => Declaration::Use(u),
|
|
|
|
|
<c:Character> => Declaration::Character(c),
|
|
|
|
|
<t:Template> => Declaration::Template(t),
|
|
|
|
|
<l:LifeArc> => Declaration::LifeArc(l),
|
|
|
|
|
<s:Schedule> => Declaration::Schedule(s),
|
|
|
|
|
<b:Behavior> => Declaration::Behavior(b),
|
|
|
|
|
<i:Institution> => Declaration::Institution(i),
|
|
|
|
|
<r:Relationship> => Declaration::Relationship(r),
|
|
|
|
|
<loc:Location> => Declaration::Location(loc),
|
|
|
|
|
<sp:Species> => Declaration::Species(sp),
|
|
|
|
|
<e:EnumDecl> => Declaration::Enum(e),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Use declarations =====
|
|
|
|
|
|
|
|
|
|
UseDecl: UseDecl = {
|
|
|
|
|
"use" <path:Path> ";" => UseDecl {
|
|
|
|
|
path,
|
|
|
|
|
kind: UseKind::Single,
|
|
|
|
|
span: Span::new(0, 0), // TODO: track actual spans
|
|
|
|
|
},
|
|
|
|
|
"use" <base:PathSegments> "::" "{" <items:Comma<Ident>> "}" ";" => UseDecl {
|
|
|
|
|
path: base,
|
|
|
|
|
kind: UseKind::Grouped(items),
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
},
|
|
|
|
|
"use" <path:PathSegments> "::" "*" ";" => UseDecl {
|
|
|
|
|
path,
|
|
|
|
|
kind: UseKind::Wildcard,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Path: Vec<String> = {
|
|
|
|
|
<PathSegments>
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PathSegments: Vec<String> = {
|
|
|
|
|
<Ident> => vec![<>],
|
|
|
|
|
<mut v:PathSegments> "::" <i:Ident> => {
|
|
|
|
|
v.push(i);
|
|
|
|
|
v
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 15:45:56 +00:00
|
|
|
DottedPath: Vec<String> = {
|
|
|
|
|
<Ident> => vec![<>],
|
|
|
|
|
<mut v:DottedPath> "." <i:Ident> => {
|
|
|
|
|
v.push(i);
|
|
|
|
|
v
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 13:24:35 +00:00
|
|
|
// ===== Character =====
|
|
|
|
|
|
|
|
|
|
Character: Character = {
|
2026-02-08 15:45:56 +00:00
|
|
|
"character" <name:Ident> <species:(":" <Ident>)?> <template:TemplateClause?> "{" <fields:Field*> "}" => Character {
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
2026-02-08 15:45:56 +00:00
|
|
|
species,
|
2026-02-08 13:24:35 +00:00
|
|
|
fields,
|
|
|
|
|
template,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
TemplateClause: Vec<String> = {
|
|
|
|
|
"from" <t:Ident> <rest:("," <Ident>)*> => {
|
|
|
|
|
let mut templates = vec![t];
|
|
|
|
|
templates.extend(rest);
|
|
|
|
|
templates
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Template =====
|
|
|
|
|
|
|
|
|
|
Template: Template = {
|
|
|
|
|
"template" <name:Ident> <strict:"strict"?> "{" <includes:Include*> <fields:Field*> "}" => Template {
|
|
|
|
|
name,
|
|
|
|
|
fields,
|
|
|
|
|
strict: strict.is_some(),
|
|
|
|
|
includes,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Include: String = {
|
|
|
|
|
"include" <name:Ident> => name
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Fields =====
|
|
|
|
|
|
|
|
|
|
Field: Field = {
|
2026-02-08 15:45:56 +00:00
|
|
|
<path:DottedPath> ":" <value:Value> => Field {
|
|
|
|
|
name: path.join("."),
|
2026-02-08 13:24:35 +00:00
|
|
|
value,
|
|
|
|
|
span: Span::new(0, 0),
|
2026-02-08 15:45:56 +00:00
|
|
|
},
|
|
|
|
|
<pb:ProseBlock> => Field {
|
|
|
|
|
name: pb.tag.clone(),
|
|
|
|
|
value: Value::ProseBlock(pb),
|
|
|
|
|
span: Span::new(0, 0),
|
2026-02-08 13:24:35 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Value: Value = {
|
|
|
|
|
<IntLit> => Value::Int(<>),
|
|
|
|
|
<FloatLit> => Value::Float(<>),
|
|
|
|
|
<StringLit> => Value::String(<>),
|
|
|
|
|
<BoolLit> => Value::Bool(<>),
|
|
|
|
|
<lo:IntLit> ".." <hi:IntLit> => Value::Range(
|
|
|
|
|
Box::new(Value::Int(lo)),
|
|
|
|
|
Box::new(Value::Int(hi))
|
|
|
|
|
),
|
|
|
|
|
<lo:FloatLit> ".." <hi:FloatLit> => Value::Range(
|
|
|
|
|
Box::new(Value::Float(lo)),
|
|
|
|
|
Box::new(Value::Float(hi))
|
|
|
|
|
),
|
|
|
|
|
<t:Time> => Value::Time(t),
|
|
|
|
|
<d:Duration> => Value::Duration(d),
|
|
|
|
|
<p:Path> => Value::Identifier(p),
|
|
|
|
|
<ProseBlock> => Value::ProseBlock(<>),
|
|
|
|
|
"[" <values:Comma<Value>> "]" => Value::List(values),
|
|
|
|
|
"{" <fields:Field*> "}" => Value::Object(fields),
|
|
|
|
|
<Override> => Value::Override(<>),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BoolLit: bool = {
|
|
|
|
|
"true" => true,
|
|
|
|
|
"false" => false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Time: Time = {
|
|
|
|
|
<s:TimeLit> => {
|
|
|
|
|
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 = {
|
|
|
|
|
<s:DurationLit> => {
|
|
|
|
|
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 = {
|
|
|
|
|
"@" <base:Path> "{" <overrides:OverrideOp*> "}" => Override {
|
|
|
|
|
base,
|
|
|
|
|
overrides,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
OverrideOp: OverrideOp = {
|
|
|
|
|
"remove" <name:Ident> => OverrideOp::Remove(name),
|
|
|
|
|
"append" <f:Field> => OverrideOp::Append(f),
|
|
|
|
|
<f:Field> => OverrideOp::Set(f),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Life Arc =====
|
|
|
|
|
|
|
|
|
|
LifeArc: LifeArc = {
|
2026-02-08 15:45:56 +00:00
|
|
|
"life_arc" <name:Ident> "{" <fields:Field*> <states:ArcState*> "}" => LifeArc {
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
|
|
|
|
states,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ArcState: ArcState = {
|
2026-02-08 15:45:56 +00:00
|
|
|
"state" <name:Ident> "{" <on_enter:OnEnter?> <fields:Field*> <transitions:Transition*> "}" => ArcState {
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
2026-02-08 15:45:56 +00:00
|
|
|
on_enter,
|
2026-02-08 13:24:35 +00:00
|
|
|
transitions,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 15:45:56 +00:00
|
|
|
OnEnter: Vec<Field> = {
|
|
|
|
|
"on" "enter" "{" <fields:Field*> "}" => fields
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 13:24:35 +00:00
|
|
|
Transition: Transition = {
|
|
|
|
|
"on" <cond:Expr> "->" <to:Ident> => Transition {
|
|
|
|
|
to,
|
|
|
|
|
condition: cond,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Schedule =====
|
|
|
|
|
|
|
|
|
|
Schedule: Schedule = {
|
2026-02-08 15:45:56 +00:00
|
|
|
"schedule" <name:Ident> "{" <fields:Field*> <blocks:ScheduleBlock*> "}" => Schedule {
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
|
|
|
|
blocks,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ScheduleBlock: ScheduleBlock = {
|
2026-02-08 15:45:56 +00:00
|
|
|
<start:Time> "->" <end:Time> ":" <activity:Ident> "{" <fields:Field*> "}" => ScheduleBlock {
|
2026-02-08 13:24:35 +00:00
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
activity,
|
2026-02-08 15:45:56 +00:00
|
|
|
fields,
|
2026-02-08 13:24:35 +00:00
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Behavior Trees =====
|
|
|
|
|
|
|
|
|
|
Behavior: Behavior = {
|
2026-02-08 15:45:56 +00:00
|
|
|
"behavior" <name:Ident> "{" <fields:Field*> <root:BehaviorNode> "}" => Behavior {
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
|
|
|
|
root,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
BehaviorNode: BehaviorNode = {
|
|
|
|
|
<SelectorNode>,
|
|
|
|
|
<SequenceNode>,
|
2026-02-08 15:45:56 +00:00
|
|
|
<RepeatNode>,
|
2026-02-08 13:24:35 +00:00
|
|
|
<ActionNode>,
|
|
|
|
|
<SubTreeNode>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SelectorNode: BehaviorNode = {
|
|
|
|
|
"?" "{" <nodes:BehaviorNode+> "}" => BehaviorNode::Selector(nodes),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SequenceNode: BehaviorNode = {
|
|
|
|
|
">" "{" <nodes:BehaviorNode+> "}" => BehaviorNode::Sequence(nodes),
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 15:45:56 +00:00
|
|
|
RepeatNode: BehaviorNode = {
|
|
|
|
|
"*" "{" <node:BehaviorNode> "}" => BehaviorNode::Decorator("repeat".to_string(), Box::new(node)),
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 13:24:35 +00:00
|
|
|
ActionNode: BehaviorNode = {
|
2026-02-08 15:45:56 +00:00
|
|
|
<name:Ident> "(" <params:Comma<ActionParam>> ")" => BehaviorNode::Action(name, params),
|
2026-02-08 13:24:35 +00:00
|
|
|
<name:Ident> => BehaviorNode::Action(name, vec![]),
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 15:45:56 +00:00
|
|
|
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),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-08 13:24:35 +00:00
|
|
|
SubTreeNode: BehaviorNode = {
|
|
|
|
|
"@" <path:Path> => BehaviorNode::SubTree(path),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Institution =====
|
|
|
|
|
|
|
|
|
|
Institution: Institution = {
|
|
|
|
|
"institution" <name:Ident> "{" <fields:Field*> "}" => Institution {
|
|
|
|
|
name,
|
|
|
|
|
fields,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Relationship =====
|
|
|
|
|
|
|
|
|
|
Relationship: Relationship = {
|
|
|
|
|
"relationship" <name:Ident> "{" <participants:Participant+> <fields:Field*> "}" => Relationship {
|
|
|
|
|
name,
|
|
|
|
|
participants,
|
|
|
|
|
fields,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Participant: Participant = {
|
2026-02-08 15:45:56 +00:00
|
|
|
// Participant with inline block after name
|
|
|
|
|
<name:Path> "{" <fields:Field*> "}" => Participant {
|
|
|
|
|
role: None,
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
2026-02-08 15:45:56 +00:00
|
|
|
self_block: Some(fields),
|
|
|
|
|
other_block: None,
|
2026-02-08 13:24:35 +00:00
|
|
|
span: Span::new(0, 0),
|
2026-02-08 15:45:56 +00:00
|
|
|
},
|
|
|
|
|
// 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),
|
|
|
|
|
},
|
2026-02-08 13:24:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SelfBlock: Vec<Field> = {
|
|
|
|
|
"self" "{" <fields:Field*> "}" => fields
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
OtherBlock: Vec<Field> = {
|
|
|
|
|
"other" "{" <fields:Field*> "}" => fields
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Location =====
|
|
|
|
|
|
|
|
|
|
Location: Location = {
|
|
|
|
|
"location" <name:Ident> "{" <fields:Field*> "}" => Location {
|
|
|
|
|
name,
|
|
|
|
|
fields,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Species =====
|
|
|
|
|
|
|
|
|
|
Species: Species = {
|
2026-02-08 15:45:56 +00:00
|
|
|
"species" <name:Ident> "{" <includes:Include*> <fields:Field*> "}" => Species {
|
2026-02-08 13:24:35 +00:00
|
|
|
name,
|
2026-02-08 15:45:56 +00:00
|
|
|
includes,
|
2026-02-08 13:24:35 +00:00
|
|
|
fields,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Enum =====
|
|
|
|
|
|
|
|
|
|
EnumDecl: EnumDecl = {
|
|
|
|
|
"enum" <name:Ident> "{" <variants:Comma<Ident>> "}" => EnumDecl {
|
|
|
|
|
name,
|
|
|
|
|
variants,
|
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Expressions =====
|
|
|
|
|
// Expression grammar with proper precedence:
|
|
|
|
|
// or > and > not > field_access > comparison > term
|
|
|
|
|
|
|
|
|
|
Expr: Expr = {
|
|
|
|
|
<OrExpr>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Logical OR (lowest precedence)
|
|
|
|
|
OrExpr: Expr = {
|
|
|
|
|
<left:OrExpr> "or" <right:AndExpr> => {
|
|
|
|
|
Expr::Logical(
|
|
|
|
|
Box::new(left),
|
|
|
|
|
LogicalOp::Or,
|
|
|
|
|
Box::new(right)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
<AndExpr>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Logical AND
|
|
|
|
|
AndExpr: Expr = {
|
|
|
|
|
<left:AndExpr> "and" <right:NotExpr> => {
|
|
|
|
|
Expr::Logical(
|
|
|
|
|
Box::new(left),
|
|
|
|
|
LogicalOp::And,
|
|
|
|
|
Box::new(right)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
<NotExpr>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Unary NOT
|
|
|
|
|
NotExpr: Expr = {
|
|
|
|
|
"not" <expr:NotExpr> => {
|
|
|
|
|
Expr::Unary(
|
|
|
|
|
UnaryOp::Not,
|
|
|
|
|
Box::new(expr)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
<ComparisonExpr>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Comparison expressions
|
|
|
|
|
ComparisonExpr: Expr = {
|
|
|
|
|
// Equality: field access or path is (literal or identifier)
|
|
|
|
|
<left:FieldAccessExpr> "is" <right:FieldAccessExpr> => {
|
|
|
|
|
Expr::Comparison(
|
|
|
|
|
Box::new(left),
|
|
|
|
|
CompOp::Eq,
|
|
|
|
|
Box::new(right)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
// Comparison: field access or path > literal/identifier, etc.
|
|
|
|
|
<left:FieldAccessExpr> <op:InequalityOp> <right:FieldAccessExpr> => {
|
|
|
|
|
Expr::Comparison(
|
|
|
|
|
Box::new(left),
|
|
|
|
|
op,
|
|
|
|
|
Box::new(right)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
// Just a field access expression
|
|
|
|
|
<FieldAccessExpr>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Field access with dot notation (binds tightest)
|
|
|
|
|
FieldAccessExpr: Expr = {
|
|
|
|
|
<base:FieldAccessExpr> "." <field:Ident> => {
|
|
|
|
|
Expr::FieldAccess(
|
|
|
|
|
Box::new(base),
|
|
|
|
|
field
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
<PrimaryExpr>,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Primary expressions (atoms)
|
|
|
|
|
PrimaryExpr: Expr = {
|
|
|
|
|
"self" => Expr::Identifier(vec!["self".to_string()]),
|
|
|
|
|
"other" => Expr::Identifier(vec!["other".to_string()]),
|
|
|
|
|
<Literal>,
|
|
|
|
|
<Path> => Expr::Identifier(<>),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
InequalityOp: CompOp = {
|
|
|
|
|
">" => CompOp::Gt,
|
|
|
|
|
">=" => CompOp::Ge,
|
|
|
|
|
"<" => CompOp::Lt,
|
|
|
|
|
"<=" => CompOp::Le,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Literal: Expr = {
|
|
|
|
|
<IntLit> => Expr::IntLit(<>),
|
|
|
|
|
<FloatLit> => Expr::FloatLit(<>),
|
|
|
|
|
<StringLit> => Expr::StringLit(<>),
|
|
|
|
|
<BoolLit> => Expr::BoolLit(<>),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ===== Helpers =====
|
|
|
|
|
|
|
|
|
|
Comma<T>: Vec<T> = {
|
|
|
|
|
<v:(<T> ",")*> <e:T?> => 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,
|
2026-02-08 15:45:56 +00:00
|
|
|
"enter" => Token::Enter,
|
2026-02-08 13:24:35 +00:00
|
|
|
"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(<String>),
|
|
|
|
|
IntLit => Token::IntLit(<i64>),
|
|
|
|
|
FloatLit => Token::FloatLit(<f64>),
|
|
|
|
|
StringLit => Token::StringLit(<String>),
|
|
|
|
|
TimeLit => Token::TimeLit(<String>),
|
|
|
|
|
DurationLit => Token::DurationLit(<String>),
|
|
|
|
|
ProseBlockToken => Token::ProseBlock(<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,
|
|
|
|
|
}
|
|
|
|
|
}
|