use brink_format::{AliasEntry, CountingFlags, DefinitionId, NameId};
use crate::hir::FileId;
use crate::lir::lower::CoalesceShape;
use crate::provenance::Provenance;
use crate::{AssignOp, InfixOp, PostfixOp, PrefixOp, SequenceType};
#[derive(Clone)]
pub struct Program {
pub root: Container,
pub globals: Vec<GlobalDef>,
pub lists: Vec<ListDef>,
pub list_items: Vec<ListItemDef>,
pub externals: Vec<ExternalDef>,
pub name_table: Vec<String>,
pub struct_shapes: Vec<StructShapeDef>,
pub private_defs: Vec<DefinitionId>,
pub aliases: Vec<AliasEntry>,
pub file_paths: std::collections::BTreeMap<FileId, String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructShapeDef {
pub id: u32,
pub name: NameId,
pub fields: Vec<NameId>,
}
#[derive(Clone)]
pub struct GlobalDef {
pub id: DefinitionId,
pub name: NameId,
pub mutable: bool,
pub default: ConstValue,
pub local: bool,
}
#[derive(Clone)]
pub struct ListDef {
pub id: DefinitionId,
pub name: NameId,
pub items: Vec<(NameId, i32)>,
}
#[derive(Clone)]
pub struct ListItemDef {
pub id: DefinitionId,
pub name: NameId,
pub origin: DefinitionId,
pub ordinal: i32,
}
#[derive(Clone)]
pub struct ExternalDef {
pub id: DefinitionId,
pub name: NameId,
pub arg_count: u8,
pub fallback: Option<DefinitionId>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstValue {
Int(i32),
Float(f32),
Bool(bool),
String(String),
List {
items: Vec<DefinitionId>,
origins: Vec<DefinitionId>,
},
DivertTarget(DefinitionId),
Null,
Array(Vec<ConstValue>),
Map(Vec<(ConstMapKey, ConstValue)>),
Record {
shape_id: u32,
fields: Vec<ConstValue>,
},
FnRef(DefinitionId),
Closure {
target: DefinitionId,
env: Vec<ConstClosureEntry>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstClosureEntry {
Val { name: String, value: ConstValue },
Ref { name: String, cell: DefinitionId },
}
#[derive(Debug, Clone, PartialEq)]
pub enum ConstMapKey {
Int(i32),
Str(String),
Bool(bool),
}
#[expect(
clippy::struct_excessive_bools,
reason = "independent structural flags, not a state machine"
)]
#[derive(Clone)]
pub struct Container {
pub id: DefinitionId,
pub provenance: Provenance,
pub name: Option<String>,
pub kind: ContainerKind,
pub params: Vec<Param>,
pub body: Vec<Stmt>,
pub children: Vec<Container>,
pub counting_flags: CountingFlags,
pub temp_slot_count: u16,
pub labeled: bool,
pub inline: bool,
pub is_function: bool,
pub local: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ContainerKind {
Root,
Knot,
Stitch,
Gather,
ChoiceTarget,
Sequence,
SequenceBranch,
ConditionalBranch,
}
#[derive(Clone)]
pub struct Param {
pub name: NameId,
pub slot: u16,
pub is_ref: bool,
pub is_divert: bool,
}
#[derive(Clone)]
pub struct Stmt {
pub kind: StmtKind,
pub provenance: Provenance,
}
impl Stmt {
#[must_use]
pub const fn new(kind: StmtKind, provenance: Provenance) -> Self {
Self { kind, provenance }
}
}
#[derive(Clone)]
pub enum StmtKind {
EmitContent(Content),
EmitLine(ContentEmission),
EvalLine(ContentEmission),
EmitLineVariants(VariantLineEmission),
ChoiceOutput {
content: Content,
emission: Option<ContentEmission>,
},
Divert(Divert),
TunnelCall(TunnelCall),
ThreadStart(ThreadStart),
DeclareTemp {
slot: u16,
name: NameId,
value: Option<Expr>,
synthetic: bool,
},
Assign {
target: AssignTarget,
op: AssignOp,
value: Expr,
},
Return {
value: Option<Expr>,
is_tunnel: bool,
args: Vec<CallArg>,
},
ChoiceSet(ChoiceSet),
Conditional(Conditional),
Sequence(Sequence),
EnterContainer(brink_format::DefinitionId),
ExprStmt(Expr),
EndOfLine,
LogicWhile(LogicWhile),
LogicBreak,
LogicContinue,
AttachElement(Expr),
EndElementRun,
}
#[derive(Clone)]
pub struct LogicWhile {
pub condition: Expr,
pub body: Vec<Stmt>,
pub post: Vec<Stmt>,
}
#[derive(Clone)]
pub enum AssignTarget {
Global(DefinitionId),
Temp(u16, NameId),
}
#[derive(Clone)]
pub struct Divert {
pub target: DivertTarget,
pub args: Vec<CallArg>,
}
#[derive(Clone)]
pub struct TunnelCall {
pub targets: Vec<TunnelTarget>,
}
#[derive(Clone)]
pub struct TunnelTarget {
pub target: DivertTarget,
pub args: Vec<CallArg>,
}
#[derive(Clone)]
pub struct ThreadStart {
pub target: DivertTarget,
pub args: Vec<CallArg>,
}
#[derive(Clone)]
pub enum DivertTarget {
Address(DefinitionId),
Variable(DefinitionId),
VariableTemp(u16, NameId),
Done,
End,
}
#[derive(Clone)]
pub enum CallArg {
Value(Expr),
RefGlobal(DefinitionId),
RefTemp(u16, NameId),
RefProjection {
root: DefinitionId,
segments: Vec<Expr>,
},
}
#[derive(Clone)]
pub struct ChoiceSet {
pub choices: Vec<Choice>,
pub gather_target: Option<DefinitionId>,
}
#[derive(Clone)]
pub struct Choice {
pub is_sticky: bool,
pub is_fallback: bool,
pub condition: Option<Expr>,
pub start_content: Option<Content>,
pub choice_only_content: Option<Content>,
pub inner_content: Option<Content>,
pub display_emission: Option<ContentEmission>,
pub output_emission: Option<ContentEmission>,
pub target: DefinitionId,
pub tags: Vec<Vec<ContentPart>>,
}
#[derive(Clone)]
pub enum CondKind {
InitialCondition,
IfElse,
Switch(Expr),
}
#[derive(Clone)]
pub struct Conditional {
pub kind: CondKind,
pub branches: Vec<CondBranch>,
}
#[derive(Clone)]
pub struct CondBranch {
pub condition: Option<Expr>,
pub body: Vec<Stmt>,
}
#[derive(Clone)]
pub struct Sequence {
pub kind: SequenceType,
pub branches: Vec<Vec<Stmt>>,
pub counter: Option<brink_format::DefinitionId>,
}
#[derive(Clone)]
pub struct LineMetadata {
pub source_hash: u64,
pub slot_info: Vec<brink_format::SlotInfo>,
pub source_location: Option<brink_format::SourceLocation>,
}
#[derive(Clone)]
pub enum RecognizedLine {
Plain(String),
Template {
parts: Vec<brink_format::LinePart>,
slot_exprs: Vec<Expr>,
},
}
#[derive(Clone)]
pub struct VariantAltEmission {
pub container_id: brink_format::DefinitionId,
pub kind: crate::hir::SequenceType,
pub branch_count: u16,
}
#[derive(Clone)]
pub struct VariantLineEmission {
pub alts: Vec<VariantAltEmission>,
pub dims: Vec<u16>,
pub variants: Vec<ContentEmission>,
}
#[derive(Clone)]
pub struct ContentEmission {
pub line: RecognizedLine,
pub metadata: LineMetadata,
pub tags: Vec<Vec<ContentPart>>,
}
#[derive(Clone)]
pub struct Content {
pub parts: Vec<ContentPart>,
pub tags: Vec<Vec<ContentPart>>,
pub source_location: Option<brink_format::SourceLocation>,
}
#[derive(Clone)]
pub enum ContentPart {
Text(String),
Glue,
Spring,
Interpolation(Expr),
InlineConditional(Conditional),
InlineSequence(Sequence),
EnterSequence(brink_format::DefinitionId),
}
#[derive(Clone)]
pub struct Expr {
pub kind: ExprKind,
pub provenance: Provenance,
}
impl Expr {
#[must_use]
pub const fn new(kind: ExprKind, provenance: Provenance) -> Self {
Self { kind, provenance }
}
#[must_use]
pub fn is_function_call(&self) -> bool {
self.kind.is_function_call()
}
#[must_use]
pub fn contains_function_call(&self) -> bool {
self.kind.contains_function_call()
}
}
#[derive(Clone)]
pub enum ExprKind {
Int(i32),
Float(f32),
Bool(bool),
String(StringExpr),
Null,
GetGlobal(DefinitionId),
GetTemp(u16, NameId),
TakeGlobal(DefinitionId),
TakeTemp(u16, NameId),
VisitCount(DefinitionId),
DivertTarget(DefinitionId),
ListLiteral {
items: Vec<DefinitionId>,
origins: Vec<DefinitionId>,
},
Prefix(PrefixOp, Box<Expr>),
Infix(Box<Expr>, InfixOp, Box<Expr>),
Postfix(Box<Expr>, PostfixOp),
Coalesce {
lhs: Box<Expr>,
rhs: Box<Expr>,
shape: CoalesceShape,
},
Call {
target: DefinitionId,
args: Vec<CallArg>,
},
CallExternal {
target: DefinitionId,
args: Vec<CallArg>,
arg_count: u8,
},
CallVariable {
target: DefinitionId,
args: Vec<CallArg>,
},
CallVariableTemp {
slot: u16,
name: NameId,
args: Vec<CallArg>,
},
CallBuiltin {
builtin: BuiltinFn,
args: Vec<Expr>,
},
MakeFnValue {
target: DefinitionId,
bound: Vec<CallArg>,
},
CallValue {
callee: Box<Expr>,
args: Vec<Expr>,
},
BindValue {
callee: Box<Expr>,
args: Vec<Expr>,
},
ConstLiteral(ConstValue),
ArrayNew(Vec<Expr>),
MapNew(Vec<(Expr, Expr)>),
Index {
base: Box<Expr>,
index: Box<Expr>,
},
IndexSet {
base: Box<Expr>,
index: Box<Expr>,
value: Box<Expr>,
},
CollectionLen(Box<Expr>),
CollectionKeys(Box<Expr>),
CollectionValues(Box<Expr>),
CollectionContains {
container: Box<Expr>,
needle: Box<Expr>,
},
CollectionInsert {
base: Box<Expr>,
key: Box<Expr>,
value: Box<Expr>,
},
CollectionRemove {
base: Box<Expr>,
key: Box<Expr>,
},
SeqRemoveAt {
base: Box<Expr>,
index: Box<Expr>,
},
CharAt {
s: Box<Expr>,
index: Box<Expr>,
},
OptionNone,
OptionSome(Box<Expr>),
OptionBind {
value: Box<Expr>,
slot: u16,
name: NameId,
},
StrFind {
s: Box<Expr>,
sub: Box<Expr>,
},
SeqIndexOf {
seq: Box<Expr>,
needle: Box<Expr>,
},
SeqMin(Box<Expr>),
SeqMax(Box<Expr>),
SeqFirst(Box<Expr>),
SeqLast(Box<Expr>),
SeqPop {
root: AssignTarget,
},
MapGetOpt {
map: Box<Expr>,
key: Box<Expr>,
},
MapContainsValue {
map: Box<Expr>,
value: Box<Expr>,
},
MapClear(Box<Expr>),
SeqSorted(Box<Expr>),
SeqSortedBy {
seq: Box<Expr>,
cmp: Box<Expr>,
},
SeqMap {
seq: Box<Expr>,
f: Box<Expr>,
},
SeqFilter {
seq: Box<Expr>,
pred: Box<Expr>,
},
SeqFold {
seq: Box<Expr>,
init: Box<Expr>,
f: Box<Expr>,
},
SeqFilterMap {
seq: Box<Expr>,
f: Box<Expr>,
},
SeqEach {
seq: Box<Expr>,
f: Box<Expr>,
},
SeqMapEach {
seq: Box<Expr>,
f: Box<Expr>,
},
Tower {
op: brink_format::TowerOp,
args: Vec<Expr>,
},
WeightedNew {
pairs: Vec<(Expr, Expr)>,
},
RandRoll(Box<Expr>),
HeapPush {
seq: Box<Expr>,
value: Box<Expr>,
},
HeapPop {
root: AssignTarget,
},
HeapPeek(Box<Expr>),
RandFloat,
RandChance(Box<Expr>),
RandPick(Box<Expr>),
RandShuffle(Box<Expr>),
RangeMake {
start: Box<Expr>,
end: Box<Expr>,
inclusive: bool,
},
RangeNonEmpty(Box<Expr>),
RecordNew {
shape_id: u32,
fields: Vec<Expr>,
prelude: Vec<(u16, NameId, Expr)>,
},
RecordGet {
base: Box<Expr>,
field: NameId,
static_offset: Option<u16>,
},
RecordSet {
base: Box<Expr>,
field: NameId,
static_offset: Option<u16>,
value: Box<Expr>,
},
ConvertInt(Box<Expr>),
ConvertFloat(Box<Expr>),
ConvertString(Box<Expr>),
Fragment(Vec<Stmt>),
}
impl ExprKind {
#[must_use]
pub const fn at(self, provenance: Provenance) -> Expr {
Expr::new(self, provenance)
}
pub fn is_function_call(&self) -> bool {
matches!(
self,
Self::Call { .. }
| Self::CallVariable { .. }
| Self::CallVariableTemp { .. }
| Self::CallExternal { .. }
)
}
pub fn contains_function_call(&self) -> bool {
let arg_calls = |args: &[CallArg]| {
args.iter().any(|a| match a {
CallArg::Value(e) => e.contains_function_call(),
CallArg::RefGlobal(_) | CallArg::RefTemp(..) | CallArg::RefProjection { .. } => {
false
}
})
};
let any = |exprs: &[Expr]| exprs.iter().any(Expr::contains_function_call);
match self {
Self::Call { .. }
| Self::CallVariable { .. }
| Self::CallVariableTemp { .. }
| Self::CallExternal { .. }
| Self::CallValue { .. } => true,
Self::Prefix(_, e)
| Self::Postfix(e, _)
| Self::CollectionLen(e)
| Self::CollectionKeys(e)
| Self::CollectionValues(e)
| Self::OptionSome(e) => e.contains_function_call(),
Self::Infix(l, _, r) => l.contains_function_call() || r.contains_function_call(),
Self::Coalesce { lhs, rhs, .. } => {
lhs.contains_function_call() || rhs.contains_function_call()
}
Self::CallBuiltin { args, .. } => any(args),
Self::MakeFnValue { bound, .. } => arg_calls(bound),
Self::BindValue { callee, args } => callee.contains_function_call() || any(args),
Self::ArrayNew(items) => any(items),
Self::MapNew(pairs) => pairs
.iter()
.any(|(k, v)| k.contains_function_call() || v.contains_function_call()),
Self::Index { base, index } => {
base.contains_function_call() || index.contains_function_call()
}
Self::IndexSet { base, index, value } => {
base.contains_function_call()
|| index.contains_function_call()
|| value.contains_function_call()
}
Self::CollectionContains { container, needle } => {
container.contains_function_call() || needle.contains_function_call()
}
Self::String(s) => s.parts.iter().any(|part| match part {
StringPart::Literal(_) => false,
StringPart::Interpolation(e) => e.contains_function_call(),
}),
_ => false,
}
}
}
#[derive(Clone)]
pub struct StringExpr {
pub parts: Vec<StringPart>,
}
#[derive(Clone)]
pub enum StringPart {
Literal(String),
Interpolation(Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinFn {
TurnsSince,
ReadCount,
Turns,
ChoiceCount,
Random,
SeedRandom,
CastToInt,
CastToFloat,
Floor,
Ceiling,
Pow,
Min,
Max,
ListCount,
ListMin,
ListMax,
ListAll,
ListInvert,
ListRange,
ListRandom,
ListValue,
ListFromInt,
}