use crate::compiler::ast::{Expr, TextSegment};
use crate::compiler::expr::parse_expr_at;
use crate::compiler::markup::{TextToken, owned_properties, scan_text_segments};
use crate::error::{DialogueError, Result};
use crate::runtime::eval::eval;
use crate::runtime::event::{MarkupSpan, line_id_from_tags};
use crate::value::{Value, VariableStorage};
type OpenTag = (String, Vec<(String, String)>, usize);
use super::Runner;
fn push_value(out: &mut String, value: &Value) {
use std::fmt::Write as _;
write!(out, "{value}").expect("writing to a String cannot fail");
}
fn close_markup(
open_stack: &mut Vec<OpenTag>,
spans: &mut Vec<MarkupSpan>,
name: &str,
end: usize,
) {
if let Some(pos) = open_stack.iter().rposition(|(n, _, _)| n == name) {
let (open_name, properties, start) = open_stack.remove(pos);
spans.push(MarkupSpan {
name: open_name,
start,
length: end - start,
properties,
});
}
}
fn self_closing_markup(
spans: &mut Vec<MarkupSpan>,
name: String,
properties: Vec<(String, String)>,
start: usize,
) {
spans.push(MarkupSpan {
name,
start,
length: 0,
properties,
});
}
fn finish_open_markup(open_stack: Vec<OpenTag>, spans: &mut Vec<MarkupSpan>) {
for (name, properties, start) in open_stack {
spans.push(MarkupSpan {
name,
start,
length: 0,
properties,
});
}
}
impl<S: VariableStorage> Runner<S> {
pub(super) fn eval_expr(&self, expr: &Expr) -> Result<Value> {
eval(expr, &self.storage, &|name, args| {
self.call_function(name, args)
})
}
fn call_function(&self, name: &str, args: Vec<Value>) -> Result<Value> {
match (name, args.as_slice()) {
("visited", [Value::Text(title)]) => Ok(Value::Bool(
self.visits.get(title).copied().unwrap_or(0) > 0,
)),
("visited", _) => Err(DialogueError::Function {
name: "visited".into(),
message: "expected one string argument".into(),
}),
("visited_count", [Value::Text(title)]) => Ok(Value::Number(f64::from(
self.visits.get(title).copied().unwrap_or(0),
))),
("visited_count", _) => Err(DialogueError::Function {
name: "visited_count".into(),
message: "expected one string argument".into(),
}),
_ => self.library.call(name, args),
}
}
pub(super) fn eval_segments(
&self,
segments: &[TextSegment],
) -> Result<(String, Vec<MarkupSpan>)> {
let mut out = String::new();
let mut spans: Vec<MarkupSpan> = Vec::new();
let mut open_stack: Vec<OpenTag> = Vec::new();
for seg in segments {
match seg {
TextSegment::Literal(s) => out.push_str(s),
TextSegment::Expr(e) => push_value(&mut out, &self.eval_expr(e.as_ref())?),
TextSegment::MarkupOpen { name, properties } => {
open_stack.push((name.clone(), properties.clone(), out.len()));
}
TextSegment::MarkupClose { name } => {
close_markup(&mut open_stack, &mut spans, name, out.len());
}
TextSegment::MarkupSelfClose { name, properties } => {
self_closing_markup(&mut spans, name.clone(), properties.clone(), out.len());
}
}
}
finish_open_markup(open_stack, &mut spans);
Ok((out, spans))
}
pub(super) fn eval_segments_as_args(&self, segments: &[TextSegment]) -> Result<Vec<String>> {
let (text, _spans) = self.eval_segments(segments)?;
Ok(text.split_whitespace().map(str::to_owned).collect())
}
pub(super) fn eval_template(&self, template: &str) -> Result<(String, Vec<MarkupSpan>)> {
let tokens = scan_text_segments(template).map_err(|e| DialogueError::Parse {
file: "<translation>".into(),
line: 0,
message: e.describe("translated template", template),
})?;
let mut out = String::with_capacity(template.len());
let mut spans: Vec<MarkupSpan> = Vec::new();
let mut open_stack: Vec<OpenTag> = Vec::new();
for tok in tokens {
match tok {
TextToken::Literal(s) => out.push_str(s),
TextToken::Expr(src) => {
let expr = parse_expr_at(src, "<translation>", 0)?;
push_value(&mut out, &self.eval_expr(&expr)?);
}
TextToken::MarkupOpen { name, properties } => {
open_stack.push((name.to_owned(), owned_properties(&properties), out.len()));
}
TextToken::MarkupClose { name } => {
close_markup(&mut open_stack, &mut spans, name, out.len());
}
TextToken::MarkupSelfClose { name, properties } => self_closing_markup(
&mut spans,
name.to_owned(),
owned_properties(&properties),
out.len(),
),
}
}
finish_open_markup(open_stack, &mut spans);
Ok((out, spans))
}
pub(super) fn eval_line_text(
&self,
segments: &[TextSegment],
tags: &[String],
) -> Result<(String, Vec<MarkupSpan>, Option<String>)> {
let line_id = line_id_from_tags(tags);
let (text, spans) = line_id
.as_deref()
.and_then(|id| self.provider.get(id))
.map_or_else(
|| self.eval_segments(segments),
|template| self.eval_template(&template),
)?;
Ok((text, spans, line_id))
}
}