use crate::{
dialogue::{
ChoiceDef, Command, Dialogue, DialogueNode, StepId, StepKind, TextLine,
builder::{BuildError, DialogueNodeBuilder},
},
parser::{
END_NODE_NAME, Parsed, RETURN_NODE_NAME,
ast::{NodeDecl, Stmt, StmtKind},
diagnostic::{Diagnostics, Severity},
},
};
pub struct Compiled {
pub dialogue: Option<Dialogue>,
pub diagnostics: Diagnostics,
}
pub fn compile(parsed: Parsed) -> Compiled {
if parsed
.diagnostics
.iter()
.find(|d| d.severity() == Severity::Error)
.is_some()
{
return Compiled {
dialogue: None,
diagnostics: parsed.diagnostics,
};
}
let nodes = parsed
.nodes
.into_iter()
.map(compile_node)
.collect::<Vec<_>>();
Compiled {
dialogue: Some(Dialogue::new(nodes)),
diagnostics: parsed.diagnostics,
}
}
#[derive(Clone, Copy)]
struct LoopTargets {
test: StepId,
after: StepId,
}
fn compile_node(node: NodeDecl) -> DialogueNode {
let mut builder = DialogueNodeBuilder::default();
let end = builder.push(StepKind::Return);
let start = build_block(&mut builder, node.body, end, None).unwrap();
builder.build(node.name.into(), start).unwrap()
}
fn build_block(
builder: &mut DialogueNodeBuilder,
body: Vec<Stmt>,
last_id: StepId,
loop_targets: Option<LoopTargets>,
) -> Result<StepId, BuildError> {
let mut current_id = last_id;
for stmt in body.into_iter().rev() {
current_id = match stmt.kind {
StmtKind::Say {
speaker,
text,
tags,
} => builder.push(StepKind::Say {
line: TextLine {
speaker: speaker.map(|s| s.into_inner()),
text: text
.into_iter()
.map(|p| p.value.try_into())
.collect::<Result<_, _>>()?,
tags: tags.into_iter().map(|t| t.into_inner()).collect(),
},
next: current_id,
}),
StmtKind::Command {
name,
args,
awaited,
} => builder.push(StepKind::Command {
command: Command {
name: name.into_inner(),
args: args
.into_iter()
.map(|v| v.value.try_into())
.collect::<Result<_, _>>()?,
awaited,
},
next: current_id,
}),
StmtKind::Set { name, attrs, value } => builder.push(StepKind::Set {
name: name.into_inner(),
attrs: attrs.into_iter().map(|s| s.value).collect(),
value: value.value.try_into().expect("checked by the parser"),
next: current_id,
}),
StmtKind::If {
branches,
otherwise,
} => {
let mut next_branch = match otherwise {
Some(body) => build_block(builder, body, current_id, loop_targets)?,
None => current_id,
};
for branch in branches.into_iter().rev() {
let then = build_block(builder, branch.body, current_id, loop_targets)?;
next_branch = builder.push(StepKind::Branch {
condition: branch
.condition
.value
.try_into()
.expect("checked by the parser"),
then,
otherwise: next_branch,
});
}
next_branch
}
StmtKind::Jump { dest, .. } if dest.value == END_NODE_NAME => {
builder.push(StepKind::End)
}
StmtKind::Jump { dest, .. } if dest.value == RETURN_NODE_NAME => {
builder.push(StepKind::Return)
}
StmtKind::Jump {
dest,
jump_back: false,
} => builder.push(StepKind::Jump(dest.into())),
StmtKind::Jump {
dest,
jump_back: true,
} => builder.push(StepKind::JumpBack {
dest: dest.into(),
next: current_id,
}),
StmtKind::Choice { choices } => {
let choices = choices
.into_iter()
.map(|c| {
let target = build_block(builder, c.body, current_id, loop_targets)?;
Ok(ChoiceDef {
text: c
.text
.into_iter()
.map(|p| p.value.try_into())
.collect::<Result<_, _>>()?,
condition: c
.condition
.map(|e| e.value.try_into().expect("checked by the parser")),
tags: c.tags.into_iter().map(|t| t.into_inner()).collect(),
target,
})
})
.collect::<Result<_, _>>()?;
builder.push(StepKind::Choice { choices })
}
StmtKind::While { condition, body } => {
let test = builder.reserve();
let targets = LoopTargets {
test,
after: current_id,
};
let then = build_block(builder, body, test, Some(targets))?;
builder.fill(
test,
StepKind::Branch {
condition: condition.value.try_into().expect("checked by the parser"),
then,
otherwise: current_id,
},
);
test
}
StmtKind::Break => loop_targets.expect("checked by the parser").after,
StmtKind::Continue => loop_targets.expect("checked by the parser").test,
};
}
Ok(current_id)
}