use crate::ast::{Ast, Pattern};
use crate::crossref::CrossRefs;
use crate::value::{BaseEnv, Env, Value};
use rustyfi_backend::{DocInfo, FontMetrics, ImageResource, MathCmdId};
use rustyfi_syntax::{RustyfiVersion, Span};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug)]
pub enum DecoEntry {
Inline {
deco: Value,
version: RustyfiVersion,
},
Block {
pads: rustyfi_backend::Paddings,
width: rustyfi_backend::Length,
decoset: [Value; 4],
version: RustyfiVersion,
},
InlineBreakable {
pads: rustyfi_backend::Paddings,
decoset: [Value; 4],
version: RustyfiVersion,
},
}
impl DecoEntry {
pub fn version(&self) -> RustyfiVersion {
match self {
DecoEntry::Inline { version, .. }
| DecoEntry::Block { version, .. }
| DecoEntry::InlineBreakable { version, .. } => *version,
}
}
}
#[derive(Debug, thiserror::Error)]
#[error("{}{msg}", .span.map(|s| format!("{s}: ")).unwrap_or_default())]
pub struct EvalError {
pub span: Option<Span>,
pub msg: String,
}
pub(crate) fn eval_error<T>(msg: impl Into<String>) -> Result<T, EvalError> {
Err(EvalError {
span: None,
msg: msg.into(),
})
}
pub(crate) fn available_fields(map: &std::collections::BTreeMap<String, Value>) -> String {
let mut keys: Vec<&str> = map.keys().map(|s| s.as_str()).collect();
keys.sort();
keys.join(", ")
}
pub struct Interp<'a> {
pub metrics: &'a dyn FontMetrics,
pub images: Vec<ImageResource>,
pub hooks: Vec<Value>,
pub math_commands: Vec<Value>,
pub crossrefs: Rc<RefCell<CrossRefs>>,
pub annotations: Vec<rustyfi_backend::Annot>,
pub destinations: Vec<rustyfi_backend::NamedDest>,
pub outline: Vec<rustyfi_backend::OutlineEntry>,
pub page_graphics: Vec<Vec<rustyfi_backend::GraphicsElem>>,
pub doc_info: Option<DocInfo>,
pub current_page: Option<usize>,
pub current_deco_id: Option<rustyfi_backend::DecoId>,
pub pending_dests: Option<Vec<(String, rustyfi_backend::Point)>>,
pub link_decos: Vec<(rustyfi_backend::DecoId, rustyfi_backend::AnnotAction)>,
pub dest_decos: Vec<(rustyfi_backend::DecoId, String)>,
pub frame_decos: Vec<(rustyfi_backend::DecoId, rustyfi_backend::FrameDecoration)>,
dest_names: std::collections::HashMap<String, String>,
pub decos: Vec<DecoEntry>,
pub outer_graphics: Vec<(Value, RustyfiVersion)>,
pub version: RustyfiVersion,
}
impl<'a> Interp<'a> {
pub fn new(metrics: &'a dyn FontMetrics) -> Self {
Interp {
metrics,
images: Vec::new(),
hooks: Vec::new(),
math_commands: Vec::new(),
crossrefs: Rc::new(RefCell::new(CrossRefs::new())),
annotations: Vec::new(),
destinations: Vec::new(),
outline: Vec::new(),
page_graphics: Vec::new(),
doc_info: None,
current_page: None,
current_deco_id: None,
pending_dests: None,
link_decos: Vec::new(),
dest_decos: Vec::new(),
frame_decos: Vec::new(),
dest_names: std::collections::HashMap::new(),
decos: Vec::new(),
outer_graphics: Vec::new(),
version: RustyfiVersion::V0_0,
}
}
pub fn eval(&mut self, base: &BaseEnv, ast: &Ast) -> Result<Value, EvalError> {
crate::compile::compile_program(ast, base).run(&Env::root(), self)
}
pub fn register_math_command(&mut self, cmd: Value) -> MathCmdId {
self.math_commands.push(cmd);
MathCmdId(self.math_commands.len() - 1)
}
pub fn dest_name(&mut self, key: &str) -> String {
if let Some(n) = self.dest_names.get(key) {
return n.clone();
}
let n = format!("nameddest{}", self.dest_names.len());
self.dest_names.insert(key.to_string(), n.clone());
n
}
pub fn apply(&mut self, func: Value, arg: Value) -> Result<Value, EvalError> {
self.apply_with_opts(func, Vec::new(), arg)
}
pub fn apply_with_opts(
&mut self,
func: Value,
opt_vals: Vec<(String, Value)>,
arg: Value,
) -> Result<Value, EvalError> {
match func {
Value::CompiledClosure {
opt_labels,
body,
env,
} => {
let mut slots = Vec::with_capacity(opt_labels.len() + 1);
push_opt_slots(&mut slots, &opt_labels, &opt_vals);
slots.push(arg);
body.run(&env.child(slots), self)
}
Value::Prim { def, mut applied } => {
if !opt_vals.is_empty() {
return eval_error(
"labeled optional arguments to a primitive are roadmap phase 5",
);
}
applied.push(arg);
if applied.len() == def.arity {
(def.run)(self, applied)
} else {
Ok(Value::Prim { def, applied })
}
}
other => eval_error(format!(
"cannot apply a value of type {} as a function",
other.type_name()
)),
}
}
}
fn push_opt_slots(slots: &mut Vec<Value>, opt_labels: &[String], opt_vals: &[(String, Value)]) {
for label in opt_labels {
slots.push(match opt_vals.iter().find(|(l, _)| l == label) {
Some((_, v)) => Value::Ctor("Some".to_string(), Some(Box::new(v.clone()))),
None => Value::Ctor("None".to_string(), None),
});
}
}
pub fn match_pattern(pat: &Pattern, value: &Value, bindings: &mut Vec<Value>) -> bool {
match pat {
Pattern::Wild => true,
Pattern::Var(_) => {
bindings.push(value.clone());
true
}
Pattern::As(inner_pat, _) => {
if match_pattern(inner_pat, value, bindings) {
bindings.push(value.clone());
true
} else {
false
}
}
Pattern::Unit => matches!(value, Value::Unit),
Pattern::Bool(b) => matches!(value, Value::Bool(v) if v == b),
Pattern::Int(n) => matches!(value, Value::Int(v) if v == n),
Pattern::Str(s) => matches!(value, Value::Str(v) if v == s),
Pattern::Tuple(ps) => match value {
Value::Tuple(vs) if ps.len() == vs.len() => ps
.iter()
.zip(vs.iter())
.all(|(p, v)| match_pattern(p, v, bindings)),
_ => false,
},
Pattern::EmptyList => matches!(value, Value::List(vs) if vs.is_empty()),
Pattern::Cons(head_pat, tail_pat) => match value {
Value::List(vs) if !vs.is_empty() => {
if !match_pattern(head_pat, &vs[0], bindings) {
return false;
}
let tail = Value::List(vs[1..].to_vec());
match_pattern(tail_pat, &tail, bindings)
}
_ => false,
},
Pattern::Ctor(name, parg) => match value {
Value::Ctor(vname, vpayload) if name == vname => match (parg, vpayload) {
(None, None) => true,
(Some(p), Some(v)) => match_pattern(p, v, bindings),
_ => false,
},
_ => false,
},
}
}