From 1ee1d078e2480ddd08d40915bc7a74a8352acff0 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 30 Jul 2021 18:04:08 +0200 Subject: [PATCH] Fatal errors - Makes errors fatal, so that a phase is only reached when all previous phases were error-free - Parsing still recovers and can produce multiple errors - Evaluation fails fast and can thus produce only a single error (except for parse errors due to an import) - The single error that could occur during execution is removed for now - Removes Value::Error variant --- bench/src/clock.rs | 33 +- bench/src/parsing.rs | 7 +- src/diag.rs | 135 +++--- src/eval/function.rs | 65 +-- src/eval/mod.rs | 589 ++++++++++++--------------- src/eval/ops.rs | 154 +++---- src/eval/scope.rs | 6 +- src/eval/template.rs | 9 +- src/eval/value.rs | 22 +- src/exec/context.rs | 21 +- src/exec/mod.rs | 4 +- src/font.rs | 3 +- src/image.rs | 3 +- src/lib.rs | 30 +- src/library/elements.rs | 87 ++-- src/library/layout.rs | 147 ++++--- src/library/mod.rs | 1 + src/library/text.rs | 92 ++--- src/library/utility.rs | 96 ++--- src/loading/mod.rs | 3 +- src/main.rs | 41 +- src/parse/lines.rs | 4 +- src/parse/mod.rs | 56 +-- src/parse/parser.rs | 64 +-- src/pretty.rs | 16 +- src/syntax/node.rs | 4 +- src/syntax/visit.rs | 2 +- src/util/mod.rs | 2 +- tests/ref/code/array.png | Bin 3396 -> 1971 bytes tests/ref/code/block-invalid.png | Bin 512 -> 0 bytes tests/ref/code/block.png | Bin 3152 -> 1440 bytes tests/ref/code/call-invalid.png | Bin 2975 -> 0 bytes tests/ref/code/call-wide.png | Bin 2686 -> 0 bytes tests/ref/code/call.png | Bin 6150 -> 3722 bytes tests/ref/code/closure.png | Bin 0 -> 657 bytes tests/ref/code/comment.png | Bin 1904 -> 1829 bytes tests/ref/code/dict.png | Bin 1602 -> 884 bytes tests/ref/code/for.png | Bin 4074 -> 2346 bytes tests/ref/code/if.png | Bin 2731 -> 1838 bytes tests/ref/code/import.png | Bin 4279 -> 1891 bytes tests/ref/code/let.png | Bin 2245 -> 1465 bytes tests/ref/code/while.png | Bin 1531 -> 811 bytes tests/ref/insert/circle.png | Bin 13518 -> 13064 bytes tests/ref/insert/square.png | Bin 7163 -> 7069 bytes tests/ref/layout/spacing.png | Bin 3247 -> 2527 bytes tests/ref/markup/escape.png | Bin 9849 -> 5578 bytes tests/ref/markup/raw.png | Bin 7281 -> 6833 bytes tests/ref/text/decorations.png | Bin 10865 -> 10821 bytes tests/ref/text/whitespace.png | Bin 7160 -> 6882 bytes tests/typ/code/array.typ | 1 + tests/typ/code/block-invalid.typ | 38 -- tests/typ/code/block-scoping.typ | 45 -- tests/typ/code/block.typ | 145 +++++-- tests/typ/code/call-invalid.typ | 39 -- tests/typ/code/call-wide.typ | 40 -- tests/typ/code/call.typ | 146 +++++-- tests/typ/code/closure.typ | 31 +- tests/typ/code/for-pattern.typ | 35 -- tests/typ/code/for.typ | 91 +++-- tests/typ/code/if.typ | 10 +- tests/typ/code/import.typ | 27 +- tests/typ/code/importable/cycle1.typ | 1 - tests/typ/code/importable/cycle2.typ | 1 - tests/typ/code/include.typ | 13 +- tests/typ/code/let.typ | 48 +-- tests/typ/code/ops-invalid.typ | 41 +- tests/typ/code/repr.typ | 3 - tests/typ/code/while.typ | 21 +- tests/typ/insert/circle.typ | 6 +- tests/typ/insert/image.typ | 14 +- tests/typ/insert/square.typ | 11 +- tests/typ/layout/grid-3.typ | 18 +- tests/typ/layout/pad.typ | 3 +- tests/typ/layout/page.typ | 3 - tests/typ/layout/pagebreak.typ | 3 - tests/typ/layout/spacing.typ | 1 + tests/typ/markup/escape.typ | 9 +- tests/typ/text/decorations.typ | 31 +- tests/typ/text/font.typ | 26 +- tests/typ/text/whitespace.typ | 6 - tests/typ/utility/basics.typ | 3 +- tests/typ/utility/color.typ | 17 +- tests/typ/utility/math.typ | 2 + tests/typeset.rs | 164 ++++---- 84 files changed, 1316 insertions(+), 1473 deletions(-) delete mode 100644 tests/ref/code/block-invalid.png delete mode 100644 tests/ref/code/call-invalid.png delete mode 100644 tests/ref/code/call-wide.png create mode 100644 tests/ref/code/closure.png delete mode 100644 tests/typ/code/block-invalid.typ delete mode 100644 tests/typ/code/block-scoping.typ delete mode 100644 tests/typ/code/call-invalid.typ delete mode 100644 tests/typ/code/call-wide.typ delete mode 100644 tests/typ/code/for-pattern.typ diff --git a/bench/src/clock.rs b/bench/src/clock.rs index c3c5b3785..7cd32711a 100644 --- a/bench/src/clock.rs +++ b/bench/src/clock.rs @@ -4,7 +4,6 @@ use std::rc::Rc; use criterion::{criterion_group, criterion_main, Criterion}; -use typst::diag::Pass; use typst::eval::{eval, Module}; use typst::exec::exec; use typst::export::pdf; @@ -25,9 +24,9 @@ fn benchmarks(c: &mut Criterion) { for case in CASES { let path = Path::new(TYP_DIR).join(case); let name = path.file_stem().unwrap().to_string_lossy(); - let src_id = loader.resolve(&path).unwrap(); + let file = loader.resolve(&path).unwrap(); let src = std::fs::read_to_string(&path).unwrap(); - let case = Case::new(src_id, src, ctx.clone()); + let case = Case::new(file, src, ctx.clone()); macro_rules! bench { ($step:literal, setup = |$ctx:ident| $setup:expr, code = $code:expr $(,)?) => { @@ -80,7 +79,7 @@ fn benchmarks(c: &mut Criterion) { /// A test case with prepared intermediate results. struct Case { ctx: Rc>, - src_id: FileId, + file: FileId, src: String, ast: Rc, module: Module, @@ -89,16 +88,16 @@ struct Case { } impl Case { - fn new(src_id: FileId, src: String, ctx: Rc>) -> Self { + fn new(file: FileId, src: String, ctx: Rc>) -> Self { let mut borrowed = ctx.borrow_mut(); - let ast = Rc::new(parse(&src).output); - let module = eval(&mut borrowed, src_id, Rc::clone(&ast)).output; - let tree = exec(&mut borrowed, &module.template).output; + let ast = Rc::new(parse(file, &src).unwrap()); + let module = eval(&mut borrowed, file, Rc::clone(&ast)).unwrap(); + let tree = exec(&mut borrowed, &module.template); let frames = layout(&mut borrowed, &tree); drop(borrowed); Self { ctx, - src_id, + file, src, ast, module, @@ -108,18 +107,14 @@ impl Case { } fn parse(&self) -> SyntaxTree { - parse(&self.src).output + parse(self.file, &self.src).unwrap() } - fn eval(&self) -> Pass { - eval( - &mut self.ctx.borrow_mut(), - self.src_id, - Rc::clone(&self.ast), - ) + fn eval(&self) -> Module { + eval(&mut self.ctx.borrow_mut(), self.file, Rc::clone(&self.ast)).unwrap() } - fn exec(&self) -> Pass { + fn exec(&self) -> LayoutTree { exec(&mut self.ctx.borrow_mut(), &self.module.template) } @@ -127,8 +122,8 @@ impl Case { layout(&mut self.ctx.borrow_mut(), &self.tree) } - fn typeset(&self) -> Pass>> { - self.ctx.borrow_mut().typeset(self.src_id, &self.src) + fn typeset(&self) -> Vec> { + self.ctx.borrow_mut().typeset(self.file, &self.src).unwrap() } fn pdf(&self) -> Vec { diff --git a/bench/src/parsing.rs b/bench/src/parsing.rs index d34faf624..d9064a633 100644 --- a/bench/src/parsing.rs +++ b/bench/src/parsing.rs @@ -1,6 +1,7 @@ use iai::{black_box, main}; -use typst::diag::Pass; +use typst::diag::TypResult; +use typst::loading::FileId; use typst::parse::{parse, Scanner, TokenMode, Tokens}; use typst::syntax::SyntaxTree; @@ -30,8 +31,8 @@ fn bench_tokenize() -> usize { Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count() } -fn bench_parse() -> Pass { - parse(black_box(SRC)) +fn bench_parse() -> TypResult { + parse(FileId::from_raw(0), black_box(SRC)) } main!(bench_decode, bench_scan, bench_tokenize, bench_parse); diff --git a/src/diag.rs b/src/diag.rs index 6158b5c0e..76d7c6b7b 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -1,113 +1,70 @@ -//! Diagnostics for source code. -//! -//! Errors are never fatal, the document will always compile and yield a layout. - -use std::collections::BTreeSet; -use std::fmt::{self, Display, Formatter}; +//! Diagnostics. use serde::{Deserialize, Serialize}; +use crate::loading::FileId; use crate::syntax::Span; -/// The result of some pass: Some output `T` and diagnostics. -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct Pass { - /// The output of this compilation pass. - pub output: T, - /// User diagnostics accumulated in this pass. - pub diags: DiagSet, -} +/// The result type for typesetting and all its subpasses. +pub type TypResult = Result>>; -impl Pass { - /// Create a new pass from output and diagnostics. - pub fn new(output: T, diags: DiagSet) -> Self { - Self { output, diags } - } -} +/// A result type with a string error message. +pub type StrResult = Result; -/// A set of diagnostics. -/// -/// Since this is a [`BTreeSet`], there cannot be two equal (up to span) -/// diagnostics and you can quickly iterate diagnostics in source location -/// order. -pub type DiagSet = BTreeSet; - -/// A diagnostic with severity level and message. +/// An error in a source file. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] -pub struct Diag { - /// The source code location. +pub struct Error { + /// The file that contains the error. + pub file: FileId, + /// The erronous location in the source code. pub span: Span, - /// How severe / important the diagnostic is. - pub level: Level, - /// A message describing the diagnostic. + /// A diagnostic message describing the problem. pub message: String, } -impl Diag { - /// Create a new diagnostic from message and level. - pub fn new(span: impl Into, level: Level, message: impl Into) -> Self { +impl Error { + /// Create a new, bare error. + pub fn new(file: FileId, span: impl Into, message: impl Into) -> Self { Self { + file, span: span.into(), - level, message: message.into(), } } -} -impl Display for Diag { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}: {}", self.level, self.message) + /// Create a boxed vector containing one error. The return value is suitable + /// as the `Err` variant of a [`TypResult`]. + pub fn boxed( + file: FileId, + span: impl Into, + message: impl Into, + ) -> Box> { + Box::new(vec![Self::new(file, span, message)]) + } + + /// Partially build a vec-boxed error, returning a function that just needs + /// the message. + /// + /// This is useful in to convert from [`StrResult`] to a [`TypResult`] using + /// [`map_err`](Result::map_err). + pub fn partial( + file: FileId, + span: impl Into, + ) -> impl FnOnce(String) -> Box> { + move |message| Self::boxed(file, span, message) } } -/// How severe / important a diagnostic is. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "kebab-case")] -pub enum Level { - Warning, - Error, -} - -impl Display for Level { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Warning => "warning", - Self::Error => "error", - }) - } -} - -/// Construct a diagnostic with [`Error`](Level::Error) level. -/// -/// ``` -/// # use typst::error; -/// # use typst::syntax::Span; -/// # let span = Span::ZERO; -/// # let thing = ""; -/// let error = error!(span, "there is an error with {}", thing); -/// ``` +/// Early-return with a vec-boxed [`Error`]. #[macro_export] -macro_rules! error { - ($span:expr, $($tts:tt)*) => { - $crate::diag::Diag::new( - $span, - $crate::diag::Level::Error, - format!($($tts)*), - ) - }; -} - -/// Construct a diagnostic with [`Warning`](Level::Warning) level. -/// -/// This works exactly like [`error!`]. -#[macro_export] -macro_rules! warning { - ($span:expr, $($tts:tt)*) => { - $crate::diag::Diag::new( - $span, - $crate::diag::Level::Warning, - format!($($tts)*), - ) +macro_rules! bail { + ($file:expr, $span:expr, $message:expr $(,)?) => { + return Err(Box::new(vec![$crate::diag::Error::new( + $file, $span, $message, + )])); + }; + + ($file:expr, $span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => { + $crate::bail!($file, $span, format!($fmt, $($arg),+)); }; } diff --git a/src/eval/function.rs b/src/eval/function.rs index ca447a48d..b9a168d25 100644 --- a/src/eval/function.rs +++ b/src/eval/function.rs @@ -3,8 +3,10 @@ use std::ops::Deref; use std::rc::Rc; use super::{Cast, EvalContext, Value}; -use crate::util::EcoString; +use crate::diag::{Error, TypResult}; +use crate::loading::FileId; use crate::syntax::{Span, Spanned}; +use crate::util::EcoString; /// An evaluatable function. #[derive(Clone)] @@ -16,13 +18,13 @@ struct Repr { func: T, } -type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value; +type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult; impl Function { /// Create a new function from a rust closure. pub fn new(name: Option, func: F) -> Self where - F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, + F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult + 'static, { Self(Rc::new(Repr { name, func })) } @@ -57,6 +59,8 @@ impl PartialEq for Function { /// Evaluated arguments to a function. #[derive(Debug, Clone, PartialEq)] pub struct FuncArgs { + /// The file in which the function was called. + pub file: FileId, /// The span of the whole argument list. pub span: Span, /// The positional arguments. @@ -80,32 +84,30 @@ impl FuncArgs { where T: Cast>, { - (0 .. self.items.len()).find_map(|index| { - let slot = self.items.get_mut(index)?; + for (i, slot) in self.items.iter().enumerate() { if slot.name.is_none() { if T::is(&slot.value) { - let value = self.items.remove(index).value; + let value = self.items.remove(i).value; return T::cast(value).ok(); } } - None - }) + } + None } - /// Find and consume the first castable positional argument, producing a + /// Find and consume the first castable positional argument, returning a /// `missing argument: {what}` error if no match was found. - pub fn expect(&mut self, ctx: &mut EvalContext, what: &str) -> Option + pub fn expect(&mut self, what: &str) -> TypResult where T: Cast>, { - let found = self.eat(); - if found.is_none() { - ctx.diag(error!(self.span, "missing argument: {}", what)); + match self.eat() { + Some(found) => Ok(found), + None => bail!(self.file, self.span, "missing argument: {}", what), } - found } - /// Find, consume and collect all castable positional arguments. + /// Find and consume all castable positional arguments. pub fn all(&mut self) -> impl Iterator + '_ where T: Cast>, @@ -113,35 +115,34 @@ impl FuncArgs { std::iter::from_fn(move || self.eat()) } - /// Cast and remove the value for the given named argument, producing an + /// Cast and remove the value for the given named argument, returning an /// error if the conversion fails. - pub fn named(&mut self, ctx: &mut EvalContext, name: &str) -> Option + pub fn named(&mut self, name: &str) -> TypResult> where T: Cast>, { - let index = self + let index = match self .items .iter() - .position(|arg| arg.name.as_ref().map_or(false, |other| other == name))?; + .filter_map(|arg| arg.name.as_deref()) + .position(|other| name == other) + { + Some(index) => index, + None => return Ok(None), + }; let value = self.items.remove(index).value; let span = value.span; - match T::cast(value) { - Ok(t) => Some(t), - Err(msg) => { - ctx.diag(error!(span, "{}", msg)); - None - } - } + T::cast(value).map(Some).map_err(Error::partial(self.file, span)) } - /// Produce "unexpected argument" errors for all remaining arguments. - pub fn finish(self, ctx: &mut EvalContext) { - for arg in &self.items { - if arg.value.v != Value::Error { - ctx.diag(error!(arg.span, "unexpected argument")); - } + /// Return an "unexpected argument" error if there is any remaining + /// argument. + pub fn finish(self) -> TypResult<()> { + if let Some(arg) = self.items.first() { + bail!(self.file, arg.span, "unexpected argument"); } + Ok(()) } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 682a3855c..decd42810 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -25,22 +25,21 @@ use std::mem; use std::path::Path; use std::rc::Rc; -use crate::diag::{Diag, DiagSet, Pass}; -use crate::util::EcoString; +use crate::diag::{Error, StrResult, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; use crate::image::ImageCache; use crate::loading::{FileId, Loader}; use crate::parse::parse; use crate::syntax::visit::Visit; use crate::syntax::*; +use crate::util::EcoString; use crate::Context; /// Evaluate a parsed source file into a module. -pub fn eval(ctx: &mut Context, file: FileId, ast: Rc) -> Pass { +pub fn eval(ctx: &mut Context, file: FileId, ast: Rc) -> TypResult { let mut ctx = EvalContext::new(ctx, file); - let template = ast.eval(&mut ctx); - let module = Module { scope: ctx.scopes.top, template }; - Pass::new(module, ctx.diags) + let template = ast.eval(&mut ctx)?; + Ok(Module { scope: ctx.scopes.top, template }) } /// Caches evaluated modules. @@ -61,7 +60,7 @@ pub trait Eval { type Output; /// Evaluate the expression to the output value. - fn eval(&self, ctx: &mut EvalContext) -> Self::Output; + fn eval(&self, ctx: &mut EvalContext) -> TypResult; } /// The context for evaluation. @@ -74,10 +73,12 @@ pub struct EvalContext<'a> { pub modules: &'a mut ModuleCache, /// The active scopes. pub scopes: Scopes<'a>, - /// Evaluation diagnostics. - pub diags: DiagSet, + /// The currently evaluated file. + pub file: FileId, /// The stack of imported files that led to evaluation of the current file. pub route: Vec, + /// The expression map for the currently built template. + pub map: ExprMap, } impl<'a> EvalContext<'a> { @@ -88,141 +89,123 @@ impl<'a> EvalContext<'a> { images: &mut ctx.images, modules: &mut ctx.modules, scopes: Scopes::new(Some(&ctx.std)), - diags: DiagSet::new(), - route: vec![file], + file, + route: vec![], + map: ExprMap::new(), } } /// Resolve a path relative to the current file. /// - /// Generates an error if the file is not found. - pub fn resolve(&mut self, path: &str, span: Span) -> Option { - let base = *self.route.last()?; - self.loader.resolve_from(base, Path::new(path)).ok().or_else(|| { - self.diag(error!(span, "file not found")); - None - }) + /// Returns an error if the file is not found. + pub fn resolve(&mut self, path: &str, span: Span) -> TypResult { + self.loader + .resolve_from(self.file, Path::new(path)) + .map_err(|_| Error::boxed(self.file, span, "file not found")) } /// Process an import of a module relative to the current location. - pub fn import(&mut self, path: &str, span: Span) -> Option { + pub fn import(&mut self, path: &str, span: Span) -> TypResult { let id = self.resolve(path, span)?; // Prevent cyclic importing. - if self.route.contains(&id) { - self.diag(error!(span, "cyclic import")); - return None; + if self.file == id || self.route.contains(&id) { + bail!(self.file, span, "cyclic import"); } // Check whether the module was already loaded. if self.modules.get(&id).is_some() { - return Some(id); + return Ok(id); } - let buffer = self.loader.load_file(id).ok().or_else(|| { - self.diag(error!(span, "failed to load file")); - None - })?; + // Load the source file. + let buffer = self + .loader + .load_file(id) + .map_err(|_| Error::boxed(self.file, span, "failed to load file"))?; - let string = std::str::from_utf8(&buffer).ok().or_else(|| { - self.diag(error!(span, "file is not valid utf-8")); - None - })?; + // Decode UTF-8. + let string = std::str::from_utf8(&buffer) + .map_err(|_| Error::boxed(self.file, span, "file is not valid utf-8"))?; // Parse the file. - let parsed = parse(string); + let ast = parse(id, string)?; // Prepare the new context. let new_scopes = Scopes::new(self.scopes.base); let old_scopes = mem::replace(&mut self.scopes, new_scopes); - let old_diags = mem::replace(&mut self.diags, parsed.diags); - self.route.push(id); + self.route.push(self.file); + self.file = id; // Evaluate the module. - let ast = Rc::new(parsed.output); - let template = ast.eval(self); + let template = Rc::new(ast).eval(self)?; // Restore the old context. let new_scopes = mem::replace(&mut self.scopes, old_scopes); - let new_diags = mem::replace(&mut self.diags, old_diags); - self.route.pop(); - - // Put all diagnostics from the module on the import. - for mut diag in new_diags { - diag.span = span; - self.diag(diag); - } + self.file = self.route.pop().unwrap(); // Save the evaluated module. let module = Module { scope: new_scopes.top, template }; self.modules.insert(id, module); - Some(id) - } - - /// Add a diagnostic. - pub fn diag(&mut self, diag: Diag) { - self.diags.insert(diag); - } - - /// Cast a value to a type and diagnose a possible error / warning. - pub fn cast(&mut self, value: Value, span: Span) -> Option - where - T: Cast, - { - if value == Value::Error { - return None; - } - - match T::cast(value) { - Ok(value) => Some(value), - Err(msg) => { - self.diag(error!(span, "{}", msg)); - None - } - } - } - - /// Join with another value. - pub fn join(&mut self, lhs: Value, rhs: Value, span: Span) -> Value { - let (a, b) = (lhs.type_name(), rhs.type_name()); - match ops::join(lhs, rhs) { - Ok(joined) => joined, - Err(prev) => { - self.diag(error!(span, "cannot join {} with {}", a, b)); - prev - } - } + Ok(id) } } impl Eval for Rc { type Output = Template; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - struct ExprVisitor<'a, 'b> { - ctx: &'a mut EvalContext<'b>, - map: ExprMap, + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + trait Walk { + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()>; } - impl<'ast> Visit<'ast> for ExprVisitor<'_, '_> { - fn visit_expr(&mut self, node: &'ast Expr) { - self.map.insert(node as *const _, node.eval(self.ctx)); + impl Walk for SyntaxTree { + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { + for node in self.iter() { + node.walk(ctx)?; + } + Ok(()) } } - let mut visitor = ExprVisitor { ctx, map: ExprMap::new() }; - visitor.visit_tree(self); + impl Walk for SyntaxNode { + fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { + match self { + Self::Text(_) => {} + Self::Space => {} + Self::Linebreak(_) => {} + Self::Parbreak(_) => {} + Self::Strong(_) => {} + Self::Emph(_) => {} + Self::Raw(_) => {} + Self::Heading(n) => n.body.walk(ctx)?, + Self::List(n) => n.body.walk(ctx)?, + Self::Enum(n) => n.body.walk(ctx)?, + Self::Expr(n) => { + let value = n.eval(ctx)?; + ctx.map.insert(n as *const _, value); + } + } + Ok(()) + } + } - TemplateTree { tree: Rc::clone(self), map: visitor.map }.into() + let map = { + let prev = mem::take(&mut ctx.map); + self.walk(ctx)?; + mem::replace(&mut ctx.map, prev) + }; + + Ok(TemplateTree { tree: Rc::clone(self), map }.into()) } } impl Eval for Expr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - match *self { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(match *self { Self::None(_) => Value::None, Self::Auto(_) => Value::Auto, Self::Bool(_, v) => Value::Bool(v), @@ -235,35 +218,32 @@ impl Eval for Expr { Self::Str(_, ref v) => Value::Str(v.clone()), Self::Ident(ref v) => match ctx.scopes.get(&v) { Some(slot) => slot.borrow().clone(), - None => { - ctx.diag(error!(v.span, "unknown variable")); - Value::Error - } + None => bail!(ctx.file, v.span, "unknown variable"), }, - Self::Array(ref v) => Value::Array(v.eval(ctx)), - Self::Dict(ref v) => Value::Dict(v.eval(ctx)), - Self::Template(ref v) => Value::Template(v.eval(ctx)), - Self::Group(ref v) => v.eval(ctx), - Self::Block(ref v) => v.eval(ctx), - Self::Call(ref v) => v.eval(ctx), - Self::Closure(ref v) => v.eval(ctx), - Self::With(ref v) => v.eval(ctx), - Self::Unary(ref v) => v.eval(ctx), - Self::Binary(ref v) => v.eval(ctx), - Self::Let(ref v) => v.eval(ctx), - Self::If(ref v) => v.eval(ctx), - Self::While(ref v) => v.eval(ctx), - Self::For(ref v) => v.eval(ctx), - Self::Import(ref v) => v.eval(ctx), - Self::Include(ref v) => v.eval(ctx), - } + Self::Array(ref v) => Value::Array(v.eval(ctx)?), + Self::Dict(ref v) => Value::Dict(v.eval(ctx)?), + Self::Template(ref v) => Value::Template(v.eval(ctx)?), + Self::Group(ref v) => v.eval(ctx)?, + Self::Block(ref v) => v.eval(ctx)?, + Self::Call(ref v) => v.eval(ctx)?, + Self::Closure(ref v) => v.eval(ctx)?, + Self::With(ref v) => v.eval(ctx)?, + Self::Unary(ref v) => v.eval(ctx)?, + Self::Binary(ref v) => v.eval(ctx)?, + Self::Let(ref v) => v.eval(ctx)?, + Self::If(ref v) => v.eval(ctx)?, + Self::While(ref v) => v.eval(ctx)?, + Self::For(ref v) => v.eval(ctx)?, + Self::Import(ref v) => v.eval(ctx)?, + Self::Include(ref v) => v.eval(ctx)?, + }) } } impl Eval for ArrayExpr { type Output = Array; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.items.iter().map(|expr| expr.eval(ctx)).collect() } } @@ -271,10 +251,10 @@ impl Eval for ArrayExpr { impl Eval for DictExpr { type Output = Dict; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.items .iter() - .map(|Named { name, expr }| (name.string.clone(), expr.eval(ctx))) + .map(|Named { name, expr }| Ok((name.string.clone(), expr.eval(ctx)?))) .collect() } } @@ -282,7 +262,7 @@ impl Eval for DictExpr { impl Eval for TemplateExpr { type Output = Template; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.tree.eval(ctx) } } @@ -290,7 +270,7 @@ impl Eval for TemplateExpr { impl Eval for GroupExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { self.expr.eval(ctx) } } @@ -298,58 +278,44 @@ impl Eval for GroupExpr { impl Eval for BlockExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { if self.scoping { ctx.scopes.enter(); } let mut output = Value::None; for expr in &self.exprs { - let value = expr.eval(ctx); - output = ctx.join(output, value, expr.span()); + let value = expr.eval(ctx)?; + output = ops::join(output, value) + .map_err(Error::partial(ctx.file, expr.span()))?; } if self.scoping { ctx.scopes.exit(); } - output + Ok(output) } } impl Eval for UnaryExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let value = self.expr.eval(ctx); - if value == Value::Error { - return Value::Error; - } - - let ty = value.type_name(); - let out = match self.op { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let value = self.expr.eval(ctx)?; + let result = match self.op { UnOp::Pos => ops::pos(value), UnOp::Neg => ops::neg(value), UnOp::Not => ops::not(value), }; - - if out == Value::Error { - ctx.diag(error!( - self.span, - "cannot apply '{}' to {}", - self.op.as_str(), - ty, - )); - } - - out + result.map_err(Error::partial(ctx.file, self.span)) } } impl Eval for BinaryExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { match self.op { BinOp::Add => self.apply(ctx, ops::add), BinOp::Sub => self.apply(ctx, ops::sub), @@ -363,7 +329,7 @@ impl Eval for BinaryExpr { BinOp::Leq => self.apply(ctx, ops::leq), BinOp::Gt => self.apply(ctx, ops::gt), BinOp::Geq => self.apply(ctx, ops::geq), - BinOp::Assign => self.assign(ctx, |_, b| b), + BinOp::Assign => self.assign(ctx, |_, b| Ok(b)), BinOp::AddAssign => self.assign(ctx, ops::add), BinOp::SubAssign => self.assign(ctx, ops::sub), BinOp::MulAssign => self.assign(ctx, ops::mul), @@ -375,131 +341,110 @@ impl Eval for BinaryExpr { impl BinaryExpr { /// Apply a basic binary operation. - fn apply(&self, ctx: &mut EvalContext, op: F) -> Value + fn apply(&self, ctx: &mut EvalContext, op: F) -> TypResult where - F: FnOnce(Value, Value) -> Value, + F: FnOnce(Value, Value) -> StrResult, { + let lhs = self.lhs.eval(ctx)?; + // Short-circuit boolean operations. - let lhs = self.lhs.eval(ctx); - match (self.op, &lhs) { - (BinOp::And, Value::Bool(false)) => return lhs, - (BinOp::Or, Value::Bool(true)) => return lhs, - _ => {} + if (self.op == BinOp::And && lhs == Value::Bool(false)) + || (self.op == BinOp::Or && lhs == Value::Bool(true)) + { + return Ok(lhs); } - let rhs = self.rhs.eval(ctx); - if lhs == Value::Error || rhs == Value::Error { - return Value::Error; - } - - // Save type names before we consume the values in case of error. - let types = (lhs.type_name(), rhs.type_name()); - let out = op(lhs, rhs); - if out == Value::Error { - self.error(ctx, types); - } - - out + let rhs = self.rhs.eval(ctx)?; + op(lhs, rhs).map_err(Error::partial(ctx.file, self.span)) } /// Apply an assignment operation. - fn assign(&self, ctx: &mut EvalContext, op: F) -> Value + fn assign(&self, ctx: &mut EvalContext, op: F) -> TypResult where - F: FnOnce(Value, Value) -> Value, + F: FnOnce(Value, Value) -> StrResult, { + let lspan = self.lhs.span(); let slot = if let Expr::Ident(id) = self.lhs.as_ref() { match ctx.scopes.get(id) { Some(slot) => Rc::clone(slot), - None => { - ctx.diag(error!(self.lhs.span(), "unknown variable")); - return Value::Error; - } + None => bail!(ctx.file, lspan, "unknown variable"), } } else { - ctx.diag(error!(self.lhs.span(), "cannot assign to this expression")); - return Value::Error; + bail!(ctx.file, lspan, "cannot assign to this expression",); }; - let rhs = self.rhs.eval(ctx); + let rhs = self.rhs.eval(ctx)?; let mut mutable = match slot.try_borrow_mut() { Ok(mutable) => mutable, Err(_) => { - ctx.diag(error!(self.lhs.span(), "cannot assign to a constant")); - return Value::Error; + bail!(ctx.file, lspan, "cannot assign to a constant",); } }; let lhs = mem::take(&mut *mutable); - let types = (lhs.type_name(), rhs.type_name()); - *mutable = op(lhs, rhs); + *mutable = op(lhs, rhs).map_err(Error::partial(ctx.file, self.span))?; - if *mutable == Value::Error { - self.error(ctx, types); - return Value::Error; - } - - Value::None - } - - fn error(&self, ctx: &mut EvalContext, (a, b): (&str, &str)) { - ctx.diag(error!(self.span, "{}", match self.op { - BinOp::Add => format!("cannot add {} and {}", a, b), - BinOp::Sub => format!("cannot subtract {1} from {0}", a, b), - BinOp::Mul => format!("cannot multiply {} with {}", a, b), - BinOp::Div => format!("cannot divide {} by {}", a, b), - _ => format!("cannot apply '{}' to {} and {}", self.op.as_str(), a, b), - })); + Ok(Value::None) } } impl Eval for CallExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let callee = self.callee.eval(ctx); - if let Some(func) = ctx.cast::(callee, self.callee.span()) { - let mut args = self.args.eval(ctx); - let returned = func(ctx, &mut args); - args.finish(ctx); - returned - } else { - Value::Error - } + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let callee = self + .callee + .eval(ctx)? + .cast::() + .map_err(Error::partial(ctx.file, self.callee.span()))?; + + let mut args = self.args.eval(ctx)?; + let returned = callee(ctx, &mut args)?; + args.finish()?; + + Ok(returned) } } impl Eval for CallArgs { type Output = FuncArgs; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let items = self.items.iter().map(|arg| arg.eval(ctx)).collect(); - FuncArgs { span: self.span, items } + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(FuncArgs { + file: ctx.file, + span: self.span, + items: self + .items + .iter() + .map(|arg| arg.eval(ctx)) + .collect::>>()?, + }) } } impl Eval for CallArg { type Output = FuncArg; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - match self { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + Ok(match self { Self::Pos(expr) => FuncArg { span: self.span(), name: None, - value: Spanned::new(expr.eval(ctx), expr.span()), + value: Spanned::new(expr.eval(ctx)?, expr.span()), }, Self::Named(Named { name, expr }) => FuncArg { span: self.span(), name: Some(name.string.clone()), - value: Spanned::new(expr.eval(ctx), expr.span()), + value: Spanned::new(expr.eval(ctx)?, expr.span()), }, - } + }) } } impl Eval for ClosureExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { let params = Rc::clone(&self.params); let body = Rc::clone(&self.body); @@ -511,86 +456,92 @@ impl Eval for ClosureExpr { }; let name = self.name.as_ref().map(|name| name.string.clone()); - Value::Func(Function::new(name, move |ctx, args| { + let func = Function::new(name, move |ctx, args| { // Don't leak the scopes from the call site. Instead, we use the // scope of captured variables we collected earlier. let prev = mem::take(&mut ctx.scopes); ctx.scopes.top = captured.clone(); for param in params.iter() { - // Set the parameter to `none` if the argument is missing. - let value = args.expect::(ctx, param.as_str()).unwrap_or_default(); + let value = args.expect::(param.as_str())?; ctx.scopes.def_mut(param.as_str(), value); } - let value = body.eval(ctx); + let result = body.eval(ctx); ctx.scopes = prev; - value - })) + result + }); + + Ok(Value::Func(func)) } } impl Eval for WithExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let callee = self.callee.eval(ctx); - if let Some(func) = ctx.cast::(callee, self.callee.span()) { - let applied = self.args.eval(ctx); - let name = func.name().cloned(); - Value::Func(Function::new(name, move |ctx, args| { - // Remove named arguments that were overridden. - let kept: Vec<_> = applied - .items - .iter() - .filter(|arg| { - arg.name.is_none() - || args.items.iter().all(|other| arg.name != other.name) - }) - .cloned() - .collect(); + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let callee = self + .callee + .eval(ctx)? + .cast::() + .map_err(Error::partial(ctx.file, self.callee.span()))?; - // Preprend the applied arguments so that the positional arguments - // are in the right order. - args.items.splice(.. 0, kept); + let applied = self.args.eval(ctx)?; - // Call the original function. - func(ctx, args) - })) - } else { - Value::Error - } + let name = callee.name().cloned(); + let func = Function::new(name, move |ctx, args| { + // Remove named arguments that were overridden. + let kept: Vec<_> = applied + .items + .iter() + .filter(|arg| { + arg.name.is_none() + || args.items.iter().all(|other| arg.name != other.name) + }) + .cloned() + .collect(); + + // Preprend the applied arguments so that the positional arguments + // are in the right order. + args.items.splice(.. 0, kept); + + // Call the original function. + callee(ctx, args) + }); + + Ok(Value::Func(func)) } } impl Eval for LetExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { let value = match &self.init { - Some(expr) => expr.eval(ctx), + Some(expr) => expr.eval(ctx)?, None => Value::None, }; ctx.scopes.def_mut(self.binding.as_str(), value); - Value::None + Ok(Value::None) } } impl Eval for IfExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let condition = self.condition.eval(ctx); - if let Some(condition) = ctx.cast(condition, self.condition.span()) { - if condition { - self.if_body.eval(ctx) - } else if let Some(else_body) = &self.else_body { - else_body.eval(ctx) - } else { - Value::None - } + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let condition = self + .condition + .eval(ctx)? + .cast::() + .map_err(Error::partial(ctx.file, self.condition.span()))?; + + if condition { + self.if_body.eval(ctx) + } else if let Some(else_body) = &self.else_body { + else_body.eval(ctx) } else { - Value::Error + Ok(Value::None) } } } @@ -598,28 +549,28 @@ impl Eval for IfExpr { impl Eval for WhileExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { let mut output = Value::None; - loop { - let condition = self.condition.eval(ctx); - if let Some(condition) = ctx.cast(condition, self.condition.span()) { - if condition { - let value = self.body.eval(ctx); - output = ctx.join(output, value, self.body.span()); - } else { - return output; - } - } else { - return Value::Error; - } + + while self + .condition + .eval(ctx)? + .cast::() + .map_err(Error::partial(ctx.file, self.condition.span()))? + { + let value = self.body.eval(ctx)?; + output = ops::join(output, value) + .map_err(Error::partial(ctx.file, self.body.span()))?; } + + Ok(output) } } impl Eval for ForExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { + fn eval(&self, ctx: &mut EvalContext) -> TypResult { macro_rules! iter { (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ let mut output = Value::None; @@ -629,17 +580,18 @@ impl Eval for ForExpr { for ($($value),*) in $iter { $(ctx.scopes.def_mut($binding.as_str(), $value);)* - let value = self.body.eval(ctx); - output = ctx.join(output, value, self.body.span()); + let value = self.body.eval(ctx)?; + output = ops::join(output, value) + .map_err(Error::partial(ctx.file, self.body.span()))?; } ctx.scopes.exit(); - output + Ok(output) }}; } - let iter = self.iter.eval(ctx); - match (self.pattern.clone(), iter) { + let iter = self.iter.eval(ctx)?; + match (&self.pattern, iter) { (ForPattern::Value(v), Value::Str(string)) => { iter!(for (v => value) in string.chars().map(|c| Value::Str(c.into()))) } @@ -655,22 +607,15 @@ impl Eval for ForExpr { (ForPattern::KeyValue(k, v), Value::Dict(dict)) => { iter!(for (k => key, v => value) in dict.into_iter()) } - (ForPattern::KeyValue(_, _), Value::Str(_)) => { - ctx.diag(error!(self.pattern.span(), "mismatched pattern")); - Value::Error - } - - (_, iter) => { - if iter != Value::Error { - ctx.diag(error!( - self.iter.span(), - "cannot loop over {}", - iter.type_name(), - )); - } - Value::Error + bail!(ctx.file, self.pattern.span(), "mismatched pattern"); } + (_, iter) => bail!( + ctx.file, + self.iter.span(), + "cannot loop over {}", + iter.type_name(), + ), } } } @@ -678,50 +623,50 @@ impl Eval for ForExpr { impl Eval for ImportExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let path = self.path.eval(ctx); - if let Some(path) = ctx.cast::(path, self.path.span()) { - if let Some(hash) = ctx.import(&path, self.path.span()) { - let mut module = &ctx.modules[&hash]; - match &self.imports { - Imports::Wildcard => { - for (var, slot) in module.scope.iter() { - let value = slot.borrow().clone(); - ctx.scopes.def_mut(var, value); - } - } - Imports::Idents(idents) => { - for ident in idents { - if let Some(slot) = module.scope.get(&ident) { - let value = slot.borrow().clone(); - ctx.scopes.def_mut(ident.as_str(), value); - } else { - ctx.diag(error!(ident.span, "unresolved import")); - module = &ctx.modules[&hash]; - } - } + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let path = self + .path + .eval(ctx)? + .cast::() + .map_err(Error::partial(ctx.file, self.path.span()))?; + + let id = ctx.import(&path, self.path.span())?; + let module = &ctx.modules[&id]; + + match &self.imports { + Imports::Wildcard => { + for (var, slot) in module.scope.iter() { + ctx.scopes.def_mut(var, slot.borrow().clone()); + } + } + Imports::Idents(idents) => { + for ident in idents { + if let Some(slot) = module.scope.get(&ident) { + ctx.scopes.def_mut(ident.as_str(), slot.borrow().clone()); + } else { + bail!(ctx.file, ident.span, "unresolved import"); } } - - return Value::None; } } - Value::Error + Ok(Value::None) } } impl Eval for IncludeExpr { type Output = Value; - fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let path = self.path.eval(ctx); - if let Some(path) = ctx.cast::(path, self.path.span()) { - if let Some(hash) = ctx.import(&path, self.path.span()) { - return Value::Template(ctx.modules[&hash].template.clone()); - } - } + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + let path = self + .path + .eval(ctx)? + .cast::() + .map_err(Error::partial(ctx.file, self.path.span()))?; - Value::Error + let id = ctx.import(&path, self.path.span())?; + let module = &ctx.modules[&id]; + + Ok(Value::Template(module.template.clone())) } } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index df8babe26..2bf1c1893 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,30 +1,34 @@ use std::cmp::Ordering; use super::Value; +use crate::diag::StrResult; use Value::*; +/// Bail with a type mismatch error. +macro_rules! mismatch { + ($fmt:expr, $($value:expr),* $(,)?) => { + return Err(format!($fmt, $($value.type_name()),*)); + }; +} + /// Join a value with another value. -pub fn join(lhs: Value, rhs: Value) -> Result { +pub fn join(lhs: Value, rhs: Value) -> StrResult { Ok(match (lhs, rhs) { - (_, Error) => Error, - (Error, _) => Error, (a, None) => a, (None, b) => b, - (Str(a), Str(b)) => Str(a + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), (Template(a), Template(b)) => Template(a + b), (Template(a), Str(b)) => Template(a + b), (Str(a), Template(b)) => Template(a + b), - - (lhs, _) => return Err(lhs), + (a, b) => mismatch!("cannot join {} with {}", a, b), }) } /// Apply the plus operator to a value. -pub fn pos(value: Value) -> Value { - match value { +pub fn pos(value: Value) -> StrResult { + Ok(match value { Int(v) => Int(v), Float(v) => Float(v), Length(v) => Length(v), @@ -32,13 +36,13 @@ pub fn pos(value: Value) -> Value { Relative(v) => Relative(v), Linear(v) => Linear(v), Fractional(v) => Fractional(v), - _ => Error, - } + v => mismatch!("cannot apply '+' to {}", v), + }) } /// Compute the negation of a value. -pub fn neg(value: Value) -> Value { - match value { +pub fn neg(value: Value) -> StrResult { + Ok(match value { Int(v) => Int(-v), Float(v) => Float(-v), Length(v) => Length(-v), @@ -46,13 +50,13 @@ pub fn neg(value: Value) -> Value { Relative(v) => Relative(-v), Linear(v) => Linear(-v), Fractional(v) => Fractional(-v), - _ => Error, - } + v => mismatch!("cannot apply '-' to {}", v), + }) } /// Compute the sum of two values. -pub fn add(lhs: Value, rhs: Value) -> Value { - match (lhs, rhs) { +pub fn add(lhs: Value, rhs: Value) -> StrResult { + Ok(match (lhs, rhs) { (Int(a), Int(b)) => Int(a + b), (Int(a), Float(b)) => Float(a as f64 + b), (Float(a), Int(b)) => Float(a + b as f64), @@ -81,13 +85,13 @@ pub fn add(lhs: Value, rhs: Value) -> Value { (Template(a), Str(b)) => Template(a + b), (Str(a), Template(b)) => Template(a + b), - _ => Error, - } + (a, b) => mismatch!("cannot add {} and {}", a, b), + }) } /// Compute the difference of two values. -pub fn sub(lhs: Value, rhs: Value) -> Value { - match (lhs, rhs) { +pub fn sub(lhs: Value, rhs: Value) -> StrResult { + Ok(match (lhs, rhs) { (Int(a), Int(b)) => Int(a - b), (Int(a), Float(b)) => Float(a as f64 - b), (Float(a), Int(b)) => Float(a - b as f64), @@ -109,13 +113,13 @@ pub fn sub(lhs: Value, rhs: Value) -> Value { (Fractional(a), Fractional(b)) => Fractional(a - b), - _ => Error, - } + (a, b) => mismatch!("cannot subtract {1} from {0}", a, b), + }) } /// Compute the product of two values. -pub fn mul(lhs: Value, rhs: Value) -> Value { - match (lhs, rhs) { +pub fn mul(lhs: Value, rhs: Value) -> StrResult { + Ok(match (lhs, rhs) { (Int(a), Int(b)) => Int(a * b), (Int(a), Float(b)) => Float(a as f64 * b), (Float(a), Int(b)) => Float(a * b as f64), @@ -150,14 +154,16 @@ pub fn mul(lhs: Value, rhs: Value) -> Value { (Int(a), Str(b)) => Str(b.repeat(a.max(0) as usize)), (Array(a), Int(b)) => Array(a.repeat(b.max(0) as usize)), (Int(a), Array(b)) => Array(b.repeat(a.max(0) as usize)), + (Template(a), Int(b)) => Template(a.repeat(b.max(0) as usize)), + (Int(a), Template(b)) => Template(b.repeat(a.max(0) as usize)), - _ => Error, - } + (a, b) => mismatch!("cannot multiply {} with {}", a, b), + }) } /// Compute the quotient of two values. -pub fn div(lhs: Value, rhs: Value) -> Value { - match (lhs, rhs) { +pub fn div(lhs: Value, rhs: Value) -> StrResult { + Ok(match (lhs, rhs) { (Int(a), Int(b)) => Float(a as f64 / b as f64), (Int(a), Float(b)) => Float(a as f64 / b), (Float(a), Int(b)) => Float(a / b as f64), @@ -182,31 +188,67 @@ pub fn div(lhs: Value, rhs: Value) -> Value { (Linear(a), Int(b)) => Linear(a / b as f64), (Linear(a), Float(b)) => Linear(a / b), - _ => Error, - } + (a, b) => mismatch!("cannot divide {} by {}", a, b), + }) } /// Compute the logical "not" of a value. -pub fn not(value: Value) -> Value { +pub fn not(value: Value) -> StrResult { match value { - Bool(b) => Bool(!b), - _ => Error, + Bool(b) => Ok(Bool(!b)), + v => mismatch!("cannot apply 'not' to {}", v), } } /// Compute the logical "and" of two values. -pub fn and(lhs: Value, rhs: Value) -> Value { +pub fn and(lhs: Value, rhs: Value) -> StrResult { match (lhs, rhs) { - (Bool(a), Bool(b)) => Bool(a && b), - _ => Error, + (Bool(a), Bool(b)) => Ok(Bool(a && b)), + (a, b) => mismatch!("cannot apply 'and' to {} and {}", a, b), } } /// Compute the logical "or" of two values. -pub fn or(lhs: Value, rhs: Value) -> Value { +pub fn or(lhs: Value, rhs: Value) -> StrResult { match (lhs, rhs) { - (Bool(a), Bool(b)) => Bool(a || b), - _ => Error, + (Bool(a), Bool(b)) => Ok(Bool(a || b)), + (a, b) => mismatch!("cannot apply 'or' to {} and {}", a, b), + } +} + +/// Compute whether two values are equal. +pub fn eq(lhs: Value, rhs: Value) -> StrResult { + Ok(Bool(equal(&lhs, &rhs))) +} + +/// Compute whether two values are equal. +pub fn neq(lhs: Value, rhs: Value) -> StrResult { + Ok(Bool(!equal(&lhs, &rhs))) +} + +macro_rules! comparison { + ($name:ident, $op:tt, $($pat:tt)*) => { + /// Compute how a value compares with another value. + pub fn $name(lhs: Value, rhs: Value) -> StrResult { + if let Some(ordering) = compare(&lhs, &rhs) { + Ok(Bool(matches!(ordering, $($pat)*))) + } else { + mismatch!(concat!("cannot apply '", $op, "' to {} and {}"), lhs, rhs); + } + } + }; +} + +comparison!(lt, "<", Ordering::Less); +comparison!(leq, "<=", Ordering::Less | Ordering::Equal); +comparison!(gt, ">", Ordering::Greater); +comparison!(geq, ">=", Ordering::Greater | Ordering::Equal); + +/// Compute the range from `lhs` to `rhs`. +pub fn range(lhs: Value, rhs: Value) -> StrResult { + match (lhs, rhs) { + (Int(a), Int(b)) => Ok(Array((a ..= b).map(Int).collect())), + (a, b) => mismatch!("cannot apply '..' to {} and {}", a, b), } } @@ -231,7 +273,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { (Template(a), Template(b)) => a == b, (Func(a), Func(b)) => a == b, (Dyn(a), Dyn(b)) => a == b, - (Error, Error) => true, // Some technically different things should compare equal. (&Int(a), &Float(b)) => a as f64 == b, @@ -245,16 +286,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { } } -/// Compute whether two values are equal. -pub fn eq(lhs: Value, rhs: Value) -> Value { - Bool(equal(&lhs, &rhs)) -} - -/// Compute whether two values are equal. -pub fn neq(lhs: Value, rhs: Value) -> Value { - Bool(!equal(&lhs, &rhs)) -} - /// Compare two values. pub fn compare(lhs: &Value, rhs: &Value) -> Option { match (lhs, rhs) { @@ -269,26 +300,3 @@ pub fn compare(lhs: &Value, rhs: &Value) -> Option { _ => Option::None, } } - -macro_rules! comparison { - ($name:ident, $($pat:tt)*) => { - /// Compute how a value compares with another value. - pub fn $name(lhs: Value, rhs: Value) -> Value { - compare(&lhs, &rhs) - .map_or(Error, |x| Bool(matches!(x, $($pat)*))) - } - }; -} - -comparison!(lt, Ordering::Less); -comparison!(leq, Ordering::Less | Ordering::Equal); -comparison!(gt, Ordering::Greater); -comparison!(geq, Ordering::Greater | Ordering::Equal); - -/// Compute the range from `lhs` to `rhs`. -pub fn range(lhs: Value, rhs: Value) -> Value { - match (lhs, rhs) { - (Int(a), Int(b)) => Array((a ..= b).map(Int).collect()), - _ => Error, - } -} diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 72b524cfb..2eb048fa3 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -4,7 +4,9 @@ use std::fmt::{self, Debug, Formatter}; use std::iter; use std::rc::Rc; -use super::{EcoString, EvalContext, FuncArgs, Function, Value}; +use super::{EvalContext, FuncArgs, Function, Value}; +use crate::diag::TypResult; +use crate::util::EcoString; /// A slot where a variable is stored. pub type Slot = Rc>; @@ -89,7 +91,7 @@ impl Scope { /// Define a constant function. pub fn def_func(&mut self, name: impl Into, f: F) where - F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static, + F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult + 'static, { let name = name.into(); self.def_const(name.clone(), Function::new(Some(name), f)); diff --git a/src/eval/template.rs b/src/eval/template.rs index 9a71ada44..29b5663de 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -21,9 +21,16 @@ impl Template { } /// Iterate over the contained template nodes. - pub fn iter(&self) -> impl Iterator + '_ { + pub fn iter(&self) -> std::slice::Iter { self.nodes.iter() } + + /// Repeat this template `n` times. + pub fn repeat(&self, n: usize) -> Self { + let len = self.nodes.len().checked_mul(n).expect("capacity overflow"); + let nodes = self.iter().cloned().cycle().take(len).collect(); + Self { nodes: Rc::new(nodes) } + } } impl From for Template { diff --git a/src/eval/value.rs b/src/eval/value.rs index b7fdcbc21..9bab067cb 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -5,10 +5,11 @@ use std::rc::Rc; use super::{ops, Array, Dict, Function, Template, TemplateFunc}; use crate::color::{Color, RgbaColor}; -use crate::util::EcoString; +use crate::diag::StrResult; use crate::exec::ExecContext; use crate::geom::{Angle, Fractional, Length, Linear, Relative}; use crate::syntax::Spanned; +use crate::util::EcoString; /// A computational value. #[derive(Debug, Clone)] @@ -47,8 +48,6 @@ pub enum Value { Func(Function), /// A dynamic value. Dyn(Dynamic), - /// The result of invalid operations. - Error, } impl Value { @@ -80,7 +79,6 @@ impl Value { Self::Template(_) => Template::TYPE_NAME, Self::Func(_) => Function::TYPE_NAME, Self::Dyn(v) => v.type_name(), - Self::Error => "error", } } @@ -93,7 +91,7 @@ impl Value { } /// Try to cast the value into a specific type. - pub fn cast(self) -> Result + pub fn cast(self) -> StrResult where T: Cast, { @@ -241,7 +239,7 @@ pub trait Cast: Sized { fn is(value: &V) -> bool; /// Try to cast the value into an instance of `Self`. - fn cast(value: V) -> Result; + fn cast(value: V) -> StrResult; } impl Cast for Value { @@ -249,7 +247,7 @@ impl Cast for Value { true } - fn cast(value: Value) -> Result { + fn cast(value: Value) -> StrResult { Ok(value) } } @@ -262,7 +260,7 @@ where T::is(&value.v) } - fn cast(value: Spanned) -> Result { + fn cast(value: Spanned) -> StrResult { T::cast(value.v) } } @@ -275,7 +273,7 @@ where T::is(&value.v) } - fn cast(value: Spanned) -> Result { + fn cast(value: Spanned) -> StrResult { let span = value.span; T::cast(value.v).map(|t| Spanned::new(t, span)) } @@ -302,7 +300,7 @@ macro_rules! primitive { matches!(value, Value::$variant(_) $(| Value::$other(_))*) } - fn cast(value: Value) -> Result { + fn cast(value: Value) -> StrResult { match value { Value::$variant(v) => Ok(v), $(Value::$other($binding) => Ok($out),)* @@ -358,7 +356,7 @@ macro_rules! castable { } } - fn cast(value: $crate::eval::Value) -> Result { + fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult { let found = match value { $($pattern => return Ok($out),)* $crate::eval::Value::Dyn(dynamic) => { @@ -387,6 +385,6 @@ primitive! { Color: "color", Color } primitive! { EcoString: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } -primitive! { Template: "template", Template, Str(v) => v.into() } +primitive! { Template: "template", Template } primitive! { Function: "function", Func } primitive! { f64: "float", Float, Int(v) => v as f64 } diff --git a/src/exec/context.rs b/src/exec/context.rs index 4d3516924..2d419a56b 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -2,13 +2,12 @@ use std::mem; use std::rc::Rc; use super::{Exec, ExecWithMap, State}; -use crate::diag::{Diag, DiagSet, Pass}; use crate::eval::{ExprMap, Template}; use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, }; -use crate::syntax::{Span, SyntaxTree}; +use crate::syntax::SyntaxTree; use crate::util::EcoString; use crate::Context; @@ -16,8 +15,6 @@ use crate::Context; pub struct ExecContext { /// The active execution state. pub state: State, - /// Execution diagnostics. - pub diags: DiagSet, /// The tree of finished page runs. tree: LayoutTree, /// When we are building the top-level stack, this contains metrics of the @@ -32,18 +29,12 @@ impl ExecContext { pub fn new(ctx: &mut Context) -> Self { Self { state: ctx.state.clone(), - diags: DiagSet::new(), tree: LayoutTree { runs: vec![] }, page: Some(PageBuilder::new(&ctx.state, true)), stack: StackBuilder::new(&ctx.state), } } - /// Add a diagnostic. - pub fn diag(&mut self, diag: Diag) { - self.diags.insert(diag); - } - /// Execute a template and return the result as a stack node. pub fn exec_template_stack(&mut self, template: &Template) -> StackNode { self.exec_stack(|ctx| template.exec(ctx)) @@ -127,21 +118,19 @@ impl ExecContext { } /// Apply a forced page break. - pub fn pagebreak(&mut self, keep: bool, hard: bool, span: Span) { + pub fn pagebreak(&mut self, keep: bool, hard: bool) { if let Some(builder) = &mut self.page { let page = mem::replace(builder, PageBuilder::new(&self.state, hard)); let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state)); self.tree.runs.extend(page.build(stack.build(), keep)); - } else { - self.diag(error!(span, "cannot modify page from here")); } } /// Finish execution and return the created layout tree. - pub fn finish(mut self) -> Pass { + pub fn finish(mut self) -> LayoutTree { assert!(self.page.is_some()); - self.pagebreak(true, false, Span::default()); - Pass::new(self.tree, self.diags) + self.pagebreak(true, false); + self.tree } fn make_text_node(&self, text: impl Into) -> ParChild { diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 8bac76e82..762b555d0 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -8,7 +8,6 @@ pub use state::*; use std::fmt::Write; -use crate::diag::Pass; use crate::eval::{ExprMap, Template, TemplateFunc, TemplateNode, TemplateTree, Value}; use crate::geom::Gen; use crate::layout::{LayoutTree, StackChild, StackNode}; @@ -18,7 +17,7 @@ use crate::util::EcoString; use crate::Context; /// Execute a template to produce a layout tree. -pub fn exec(ctx: &mut Context, template: &Template) -> Pass { +pub fn exec(ctx: &mut Context, template: &Template) -> LayoutTree { let mut ctx = ExecContext::new(ctx); template.exec(&mut ctx); ctx.finish() @@ -138,7 +137,6 @@ impl Exec for Value { Value::Float(v) => ctx.push_text(pretty(v)), Value::Str(v) => ctx.push_text(v), Value::Template(v) => v.exec(ctx), - Value::Error => {} // For values which can't be shown "naturally", we print the // representation in monospace. other => ctx.push_monospace_text(pretty(other)), diff --git a/src/font.rs b/src/font.rs index 3dec3a631..945486999 100644 --- a/src/font.rs +++ b/src/font.rs @@ -316,7 +316,8 @@ impl FontCache { } /// A unique identifier for a loaded font face. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Serialize, Deserialize)] pub struct FaceId(u64); impl FaceId { diff --git a/src/image.rs b/src/image.rs index 5738be5fc..93b95bda2 100644 --- a/src/image.rs +++ b/src/image.rs @@ -103,7 +103,8 @@ impl ImageCache { } /// A unique identifier for a loaded image. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Serialize, Deserialize)] pub struct ImageId(u64); impl ImageId { diff --git a/src/lib.rs b/src/lib.rs index da0063333..be99fb587 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,9 +7,9 @@ //! module. //! - **Evaluation:** The next step is to [evaluate] the syntax tree. This //! computes the value of each node in the document and produces a [module]. -//! - **Execution:** Now, we can [execute] the parsed and evaluated module. -//! This produces a [layout tree], a high-level, fully styled representation -//! of the document. The nodes of this tree are self-contained and +//! - **Execution:** Now, we can [execute] the parsed and evaluated module. This +//! results in a [layout tree], a high-level, fully styled representation of +//! the document. The nodes of this tree are self-contained and //! order-independent and thus much better suited for layouting than the //! syntax tree. //! - **Layouting:** Next, the tree is [layouted] into a portable version of the @@ -49,7 +49,7 @@ pub mod util; use std::rc::Rc; -use crate::diag::Pass; +use crate::diag::TypResult; use crate::eval::{ModuleCache, Scope}; use crate::exec::State; use crate::font::FontCache; @@ -100,19 +100,15 @@ impl Context { /// The `file` identifies the source file and is used to resolve relative /// paths (for importing and image loading). /// - /// Returns a vector of frames representing individual pages alongside - /// diagnostic information (errors and warnings). - pub fn typeset(&mut self, file: FileId, src: &str) -> Pass>> { - let ast = parse::parse(src); - let module = eval::eval(self, file, Rc::new(ast.output)); - let tree = exec::exec(self, &module.output.template); - let frames = layout::layout(self, &tree.output); - - let mut diags = ast.diags; - diags.extend(module.diags); - diags.extend(tree.diags); - - Pass::new(frames, diags) + /// Returns either a vector of frames representing individual pages or + /// diagnostics in the form of a vector of error message with file and span + /// information. + pub fn typeset(&mut self, file: FileId, src: &str) -> TypResult>> { + let ast = parse::parse(file, src)?; + let module = eval::eval(self, file, Rc::new(ast))?; + let tree = exec::exec(self, &module.template); + let frames = layout::layout(self, &tree); + Ok(frames) } } diff --git a/src/library/elements.rs b/src/library/elements.rs index afd7444e1..3d318d362 100644 --- a/src/library/elements.rs +++ b/src/library/elements.rs @@ -8,46 +8,44 @@ use crate::layout::{ }; /// `image`: An image. -pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let path = args.expect::>(ctx, "path to image file"); - let width = args.named(ctx, "width"); - let height = args.named(ctx, "height"); +pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult { + let path = args.expect::>("path to image file")?; + let width = args.named("width")?; + let height = args.named("height")?; - let mut node = None; - if let Some(path) = &path { - if let Some(file) = ctx.resolve(&path.v, path.span) { - if let Some(id) = ctx.images.load(file) { - node = Some(ImageNode { id, width, height }); - } else { - ctx.diag(error!(path.span, "failed to load image")); - } - } - } + let file = ctx.resolve(&path.v, path.span)?; + let node = match ctx.images.load(file) { + Some(id) => ImageNode { id, width, height }, + None => bail!(args.file, path.span, "failed to load image"), + }; - Value::template(move |ctx| { - if let Some(node) = node { - ctx.push_into_par(node); - } - }) + Ok(Value::template(move |ctx| ctx.push_into_par(node))) } /// `rect`: A rectangle with optional content. -pub fn rect(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let width = args.named(ctx, "width"); - let height = args.named(ctx, "height"); - let fill = args.named(ctx, "fill"); +pub fn rect(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { + let width = args.named("width")?; + let height = args.named("height")?; + let fill = args.named("fill")?; let body = args.eat().unwrap_or_default(); - rect_impl(width, height, None, fill, body) + Ok(rect_impl(width, height, None, fill, body)) } /// `square`: A square with optional content. -pub fn square(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let length = args.named::(ctx, "length").map(Linear::from); - let width = length.or_else(|| args.named(ctx, "width")); - let height = width.is_none().then(|| args.named(ctx, "height")).flatten(); - let fill = args.named(ctx, "fill"); +pub fn square(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { + let length = args.named::("length")?.map(Linear::from); + let width = match length { + Some(length) => Some(length), + None => args.named("width")?, + }; + let height = match width { + Some(_) => None, + None => args.named("height")?, + }; + let aspect = Some(N64::from(1.0)); + let fill = args.named("fill")?; let body = args.eat().unwrap_or_default(); - rect_impl(width, height, Some(N64::from(1.0)), fill, body) + Ok(rect_impl(width, height, aspect, fill, body)) } fn rect_impl( @@ -76,22 +74,29 @@ fn rect_impl( } /// `ellipse`: An ellipse with optional content. -pub fn ellipse(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let width = args.named(ctx, "width"); - let height = args.named(ctx, "height"); - let fill = args.named(ctx, "fill"); +pub fn ellipse(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { + let width = args.named("width")?; + let height = args.named("height")?; + let fill = args.named("fill")?; let body = args.eat().unwrap_or_default(); - ellipse_impl(width, height, None, fill, body) + Ok(ellipse_impl(width, height, None, fill, body)) } /// `circle`: A circle with optional content. -pub fn circle(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let diameter = args.named::(ctx, "radius").map(|r| 2.0 * Linear::from(r)); - let width = diameter.or_else(|| args.named(ctx, "width")); - let height = width.is_none().then(|| args.named(ctx, "height")).flatten(); - let fill = args.named(ctx, "fill"); +pub fn circle(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { + let diameter = args.named("radius")?.map(|r: Length| 2.0 * Linear::from(r)); + let width = match diameter { + None => args.named("width")?, + diameter => diameter, + }; + let height = match width { + None => args.named("height")?, + width => width, + }; + let aspect = Some(N64::from(1.0)); + let fill = args.named("fill")?; let body = args.eat().unwrap_or_default(); - ellipse_impl(width, height, Some(N64::from(1.0)), fill, body) + Ok(ellipse_impl(width, height, aspect, fill, body)) } fn ellipse_impl( diff --git a/src/library/layout.rs b/src/library/layout.rs index f3f3f81e3..727bbcc39 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -3,26 +3,26 @@ use crate::layout::{FixedNode, GridNode, PadNode, StackChild, StackNode, TrackSi use crate::paper::{Paper, PaperClass}; /// `page`: Configure pages. -pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value { - let span = args.span; - let paper = args.eat::>().and_then(|name| { - Paper::from_name(&name.v).or_else(|| { - ctx.diag(error!(name.span, "invalid paper name")); - None - }) - }); +pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { + let paper = match args.eat::>() { + Some(name) => match Paper::from_name(&name.v) { + None => bail!(args.file, name.span, "invalid paper name"), + paper => paper, + }, + None => None, + }; - let width = args.named(ctx, "width"); - let height = args.named(ctx, "height"); - let margins = args.named(ctx, "margins"); - let left = args.named(ctx, "left"); - let top = args.named(ctx, "top"); - let right = args.named(ctx, "right"); - let bottom = args.named(ctx, "bottom"); - let flip = args.named(ctx, "flip"); - let body = args.expect::