summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-07-30 18:04:08 +0200
committerLaurenz <laurmaedje@gmail.com>2021-07-30 18:49:19 +0200
commit1ee1d078e2480ddd08d40915bc7a74a8352acff0 (patch)
tree1e7ff367278a19fead3e404cf06d65bfb80a6cd9 /src
parent42a27b48df427edf8dbb624c51551a90ecf2e7ea (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/diag.rs127
-rw-r--r--src/eval/function.rs65
-rw-r--r--src/eval/mod.rs593
-rw-r--r--src/eval/ops.rs154
-rw-r--r--src/eval/scope.rs6
-rw-r--r--src/eval/template.rs9
-rw-r--r--src/eval/value.rs22
-rw-r--r--src/exec/context.rs21
-rw-r--r--src/exec/mod.rs4
-rw-r--r--src/font.rs3
-rw-r--r--src/image.rs3
-rw-r--r--src/lib.rs30
-rw-r--r--src/library/elements.rs91
-rw-r--r--src/library/layout.rs151
-rw-r--r--src/library/mod.rs1
-rw-r--r--src/library/text.rs94
-rw-r--r--src/library/utility.rs96
-rw-r--r--src/loading/mod.rs3
-rw-r--r--src/main.rs41
-rw-r--r--src/parse/lines.rs4
-rw-r--r--src/parse/mod.rs56
-rw-r--r--src/parse/parser.rs64
-rw-r--r--src/pretty.rs16
-rw-r--r--src/syntax/node.rs4
-rw-r--r--src/syntax/visit.rs2
-rw-r--r--src/util/mod.rs2
26 files changed, 782 insertions, 880 deletions
diff --git a/src/diag.rs b/src/diag.rs
index 6158b5c0..76d7c6b7 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<T> {
- /// The output of this compilation pass.
- pub output: T,
- /// User diagnostics accumulated in this pass.
- pub diags: DiagSet,
-}
-
-impl<T> Pass<T> {
- /// Create a new pass from output and diagnostics.
- pub fn new(output: T, diags: DiagSet) -> Self {
- Self { output, diags }
- }
-}
+/// The result type for typesetting and all its subpasses.
+pub type TypResult<T> = Result<T, Box<Vec<Error>>>;
-/// 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<Diag>;
+/// A result type with a string error message.
+pub type StrResult<T> = Result<T, String>;
-/// 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<Span>, level: Level, message: impl Into<String>) -> Self {
+impl Error {
+ /// Create a new, bare error.
+ pub fn new(file: FileId, span: impl Into<Span>, message: impl Into<String>) -> 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<Span>,
+ message: impl Into<String>,
+ ) -> Box<Vec<Self>> {
+ Box::new(vec![Self::new(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",
- })
+ /// 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<Span>,
+ ) -> impl FnOnce(String) -> Box<Vec<Self>> {
+ move |message| Self::boxed(file, span, message)
}
}
-/// 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)*),
- )
+macro_rules! bail {
+ ($file:expr, $span:expr, $message:expr $(,)?) => {
+ return Err(Box::new(vec![$crate::diag::Error::new(
+ $file, $span, $message,
+ )]));
};
-}
-/// 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)*),
- )
+ ($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 ca447a48..b9a168d2 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<T: ?Sized> {
func: T,
}
-type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> Value;
+type Func = dyn Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value>;
impl Function {
/// Create a new function from a rust closure.
pub fn new<F>(name: Option<EcoString>, func: F) -> Self
where
- F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
+ F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + '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<Spanned<Value>>,
{
- (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<T>(&mut self, ctx: &mut EvalContext, what: &str) -> Option<T>
+ pub fn expect<T>(&mut self, what: &str) -> TypResult<T>
where
T: Cast<Spanned<Value>>,
{
- 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<T>(&mut self) -> impl Iterator<Item = T> + '_
where
T: Cast<Spanned<Value>>,
@@ -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<T>(&mut self, ctx: &mut EvalContext, name: &str) -> Option<T>
+ pub fn named<T>(&mut self, name: &str) -> TypResult<Option<T>>
where
T: Cast<Spanned<Value>>,
{
- 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 682a3855..decd4281 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<SyntaxTree>) -> Pass<Module> {
+pub fn eval(ctx: &mut Context, file: FileId, ast: Rc<SyntaxTree>) -> TypResult<Module> {
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<Self::Output>;
}
/// 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<FileId>,
+ /// 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<FileId> {
- 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<FileId> {
+ 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<FileId> {
+ pub fn import(&mut self, path: &str, span: Span) -> TypResult<FileId> {
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<T>(&mut self, value: Value, span: Span) -> Option<T>
- where
- T: Cast<Value>,
- {
- 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<SyntaxTree> {
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<Self::Output> {
+ 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(())
+ }
+ }
+
+ let map = {
+ let prev = mem::take(&mut ctx.map);
+ self.walk(ctx)?;
+ mem::replace(&mut ctx.map, prev)
+ };
- TemplateTree { tree: Rc::clone(self), map: visitor.map }.into()
+ 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<Self::Output> {
+ 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::Output> {
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::Output> {
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::Output> {
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::Output> {
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<Self::Output> {
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<Self::Output> {
+ 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<Self::Output> {
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<F>(&self, ctx: &mut EvalContext, op: F) -> Value
+ fn apply<F>(&self, ctx: &mut EvalContext, op: F) -> TypResult<Value>
where
- F: FnOnce(Value, Value) -> Value,
+ F: FnOnce(Value, Value) -> StrResult<Value>,
{
- // 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,
- _ => {}
- }
+ let lhs = self.lhs.eval(ctx)?;
- 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);
+ // Short-circuit boolean operations.
+ if (self.op == BinOp::And && lhs == Value::Bool(false))
+ || (self.op == BinOp::Or && lhs == Value::Bool(true))
+ {
+ return Ok(lhs);
}
- out
+ let rhs = self.rhs.eval(ctx)?;
+ op(lhs, rhs).map_err(Error::partial(ctx.file, self.span))
}
/// Apply an assignment operation.
- fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> Value
+ fn assign<F>(&self, ctx: &mut EvalContext, op: F) -> TypResult<Value>
where
- F: FnOnce(Value, Value) -> Value,
+ F: FnOnce(Value, Value) -> StrResult<Value>,
{
+ 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::<Function>(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<Self::Output> {
+ let callee = self
+ .callee
+ .eval(ctx)?
+ .cast::<Function>()
+ .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<Self::Output> {
+ Ok(FuncArgs {
+ file: ctx.file,
+ span: self.span,
+ items: self
+ .items
+ .iter()
+ .map(|arg| arg.eval(ctx))
+ .collect::<TypResult<Vec<_>>>()?,
+ })
}
}
impl Eval for CallArg {
type Output = FuncArg;
- fn eval(&self, ctx: &mut EvalContext) -> Self::Output {
- match self {
+ fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
+ 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<Self::Output> {
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::<Value>(ctx, param.as_str()).unwrap_or_default();
+ let value = args.expect::<Value>(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::<Function>(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();
-
- // Preprend the applied arguments so that the positional arguments
- // are in the right order.
- args.items.splice(.. 0, kept);
-
- // Call the original function.
- func(ctx, args)
- }))
- } else {
- Value::Error
- }
+ fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> {
+ let callee = self
+ .callee
+ .eval(ctx)?
+ .cast::<Function>()
+ .map_err(Error::partial(ctx.file, self.callee.span()))?;
+
+ let applied = self.args.eval(ctx)?;
+
+ 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<Self::Output> {
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<Self::Output> {
+ let condition = self
+ .condition
+ .eval(ctx)?
+ .cast::<bool>()
+ .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<Self::Output> {
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::<bool>()
+ .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<Self::Output> {
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::<EcoString>(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<Self::Output> {
+ let path = self
+ .path
+ .eval(ctx)?
+ .cast::<EcoString>()
+ .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::<EcoString>(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<Self::Output> {
+ let path = self
+ .path
+ .eval(ctx)?
+ .cast::<EcoString>()
+ .map_err(Error::partial(ctx.file, self.path.span()))?;
+
+ let id = ctx.import(&path, self.path.span())?;
+ let module = &ctx.modules[&id];
- Value::Error
+ Ok(Value::Template(module.template.clone()))
}
}
diff --git a/src/eval/ops.rs b/src/eval/ops.rs
index df8babe2..2bf1c189 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<Value, Value> {
+pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Value> {
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<Value> {
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<Value> {
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<Value> {
+ Ok(Bool(equal(&lhs, &rhs)))
+}
+
+/// Compute whether two values are equal.
+pub fn neq(lhs: Value, rhs: Value) -> StrResult<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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<Ordering> {
match (lhs, rhs) {
@@ -269,26 +300,3 @@ pub fn compare(lhs: &Value, rhs: &Value) -> Option<Ordering> {
_ => 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 72b524cf..2eb048fa 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<RefCell<Value>>;
@@ -89,7 +91,7 @@ impl Scope {
/// Define a constant function.
pub fn def_func<F>(&mut self, name: impl Into<EcoString>, f: F)
where
- F: Fn(&mut EvalContext, &mut FuncArgs) -> Value + 'static,
+ F: Fn(&mut EvalContext, &mut FuncArgs) -> TypResult<Value> + '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 9a71ada4..29b5663d 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<Item = &TemplateNode> + '_ {
+ pub fn iter(&self) -> std::slice::Iter<TemplateNode> {
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<TemplateTree> for Template {
diff --git a/src/eval/value.rs b/src/eval/value.rs
index b7fdcbc2..9bab067c 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<T>(self) -> Result<T, String>
+ pub fn cast<T>(self) -> StrResult<T>
where
T: Cast<Value>,
{
@@ -241,7 +239,7 @@ pub trait Cast<V>: Sized {
fn is(value: &V) -> bool;
/// Try to cast the value into an instance of `Self`.
- fn cast(value: V) -> Result<Self, String>;
+ fn cast(value: V) -> StrResult<Self>;
}
impl Cast<Value> for Value {
@@ -249,7 +247,7 @@ impl Cast<Value> for Value {
true
}
- fn cast(value: Value) -> Result<Self, String> {
+ fn cast(value: Value) -> StrResult<Self> {
Ok(value)
}
}
@@ -262,7 +260,7 @@ where
T::is(&value.v)
}
- fn cast(value: Spanned<Value>) -> Result<Self, String> {
+ fn cast(value: Spanned<Value>) -> StrResult<Self> {
T::cast(value.v)
}
}
@@ -275,7 +273,7 @@ where
T::is(&value.v)
}
- fn cast(value: Spanned<Value>) -> Result<Self, String> {
+ fn cast(value: Spanned<Value>) -> StrResult<Self> {
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<Self, String> {
+ fn cast(value: Value) -> StrResult<Self> {
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<Self, String> {
+ fn cast(value: $crate::eval::Value) -> $crate::diag::StrResult<Self> {
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 4d351692..2d419a56 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<LayoutTree> {
+ 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<EcoString>) -> ParChild {
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 8bac76e8..762b555d 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<LayoutTree> {
+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 3dec3a63..94548699 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 5738be5f..93b95bda 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 da006333..be99fb58 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<Vec<Rc<Frame>>> {
- 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<Vec<Rc<Frame>>> {
+ 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 afd7444e..3d318d36 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::<Spanned<EcoString>>(ctx, "path to image file");
- let width = args.named(ctx, "width");
- let height = args.named(ctx, "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"));
- }
- }
- }
-
- Value::template(move |ctx| {
- if let Some(node) = node {
- ctx.push_into_par(node);
- }
- })
+pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let path = args.expect::<Spanned<EcoString>>("path to image file")?;
+ let width = args.named("width")?;
+ let height = args.named("height")?;
+
+ 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"),
+ };
+
+ 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<Value> {
+ 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::<Length>(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<Value> {
+ let length = args.named::<Length>("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<Value> {
+ 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::<Length>(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<Value> {
+ 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 f3f3f81e..727bbcc3 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::<Spanned<EcoString>>().and_then(|name| {
- Paper::from_name(&name.v).or_else(|| {
- ctx.diag(error!(name.span, "invalid paper name"));
- 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::<Template>(ctx, "body").unwrap_or_default();
-
- Value::template(move |ctx| {
+pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let paper = match args.eat::<Spanned<EcoString>>() {
+ 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("width")?;
+ let height = args.named("height")?;
+ let margins = args.named("margins")?;
+ let left = args.named("left")?;
+ let top = args.named("top")?;
+ let right = args.named("right")?;
+ let bottom = args.named("bottom")?;
+ let flip = args.named("flip")?;
+ let body = args.expect::<Template>("body")?;
+
+ Ok(Value::template(move |ctx| {
let snapshot = ctx.state.clone();
let state = ctx.state.page_mut();
@@ -65,50 +65,45 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
std::mem::swap(&mut state.size.width, &mut state.size.height);
}
- ctx.pagebreak(false, true, span);
+ ctx.pagebreak(false, true);
body.exec(ctx);
ctx.state = snapshot;
- ctx.pagebreak(true, false, span);
- })
+ ctx.pagebreak(true, false);
+ }))
}
/// `pagebreak`: Start a new page.
-pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let span = args.span;
- Value::template(move |ctx| {
- ctx.pagebreak(true, true, span);
- })
+pub fn pagebreak(_: &mut EvalContext, _: &mut FuncArgs) -> TypResult<Value> {
+ Ok(Value::template(move |ctx| ctx.pagebreak(true, true)))
}
/// `h`: Horizontal spacing.
-pub fn h(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- spacing_impl(ctx, args, GenAxis::Cross)
+pub fn h(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ spacing_impl(args, GenAxis::Cross)
}
/// `v`: Vertical spacing.
-pub fn v(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- spacing_impl(ctx, args, GenAxis::Main)
+pub fn v(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ spacing_impl(args, GenAxis::Main)
}
-fn spacing_impl(ctx: &mut EvalContext, args: &mut FuncArgs, axis: GenAxis) -> Value {
- let spacing: Option<Linear> = args.expect(ctx, "spacing");
- Value::template(move |ctx| {
- if let Some(linear) = spacing {
- // TODO: Should this really always be font-size relative?
- let amount = linear.resolve(ctx.state.font.size);
- ctx.push_spacing(axis, amount);
- }
- })
+fn spacing_impl(args: &mut FuncArgs, axis: GenAxis) -> TypResult<Value> {
+ let spacing = args.expect::<Linear>("spacing")?;
+ Ok(Value::template(move |ctx| {
+ // TODO: Should this really always be font-size relative?
+ let amount = spacing.resolve(ctx.state.font.size);
+ ctx.push_spacing(axis, amount);
+ }))
}
/// `align`: Configure the alignment along the layouting axes.
-pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
+pub fn align(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let mut horizontal = args.named("horizontal")?;
+ let mut vertical = args.named("vertical")?;
let first = args.eat::<Align>();
let second = args.eat::<Align>();
- let mut horizontal = args.named(ctx, "horizontal");
- let mut vertical = args.named(ctx, "vertical");
- let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
+ let body = args.expect::<Template>("body")?;
for value in first.into_iter().chain(second) {
match value.axis() {
@@ -122,7 +117,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
}
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
if let Some(horizontal) = horizontal {
ctx.state.aligns.cross = horizontal;
}
@@ -133,37 +128,37 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
body.exec(ctx);
- })
+ }))
}
/// `box`: Place content in a rectangular box.
-pub fn boxed(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let width = args.named(ctx, "width");
- let height = args.named(ctx, "height");
+pub fn boxed(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let width = args.named("width")?;
+ let height = args.named("height")?;
let body = args.eat().unwrap_or_default();
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
let child = ctx.exec_template_stack(&body).into();
ctx.push_into_par(FixedNode { width, height, child });
- })
+ }))
}
/// `block`: Place content in a block.
-pub fn block(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let body = args.expect(ctx, "body").unwrap_or_default();
- Value::template(move |ctx| {
+pub fn block(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let body = args.expect("body")?;
+ Ok(Value::template(move |ctx| {
let block = ctx.exec_template_stack(&body);
ctx.push_into_stack(block);
- })
+ }))
}
/// `pad`: Pad content at the sides.
-pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
+pub fn pad(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
let all = args.eat();
- 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 body = args.expect(ctx, "body").unwrap_or_default();
+ let left = args.named("left")?;
+ let top = args.named("top")?;
+ let right = args.named("right")?;
+ let bottom = args.named("bottom")?;
+ let body = args.expect("body")?;
let padding = Sides::new(
left.or(all).unwrap_or_default(),
@@ -172,18 +167,18 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
bottom.or(all).unwrap_or_default(),
);
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
let child = ctx.exec_template_stack(&body).into();
ctx.push_into_stack(PadNode { padding, child });
- })
+ }))
}
/// `stack`: Stack children along an axis.
-pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let dir = args.named(ctx, "dir");
+pub fn stack(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let dir = args.named("dir")?;
let children: Vec<_> = args.all().collect();
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
let children = children
.iter()
.map(|child| {
@@ -201,23 +196,23 @@ pub fn stack(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
ctx.push_into_stack(StackNode { dirs, aspect: None, children });
- })
+ }))
}
/// `grid`: Arrange children into a grid.
-pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let columns = args.named(ctx, "columns").unwrap_or_default();
- let rows = args.named(ctx, "rows").unwrap_or_default();
+pub fn grid(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let columns = args.named("columns")?.unwrap_or_default();
+ let rows = args.named("rows")?.unwrap_or_default();
- let gutter_columns = args.named(ctx, "gutter-columns");
- let gutter_rows = args.named(ctx, "gutter-rows");
+ let gutter_columns = args.named("gutter-columns")?;
+ let gutter_rows = args.named("gutter-rows")?;
let default = args
- .named(ctx, "gutter")
+ .named("gutter")?
.map(|v| vec![TrackSizing::Linear(v)])
.unwrap_or_default();
- let column_dir = args.named(ctx, "column-dir");
- let row_dir = args.named(ctx, "row-dir");
+ let column_dir = args.named("column-dir")?;
+ let row_dir = args.named("row-dir")?;
let children: Vec<_> = args.all().collect();
@@ -227,7 +222,7 @@ pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
gutter_rows.unwrap_or(default),
);
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
let children = children
.iter()
.map(|child| ctx.exec_template_stack(child).into())
@@ -257,7 +252,7 @@ pub fn grid(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
gutter: gutter.clone(),
children,
})
- })
+ }))
}
/// Defines size of rows and columns in a grid.
diff --git a/src/library/mod.rs b/src/library/mod.rs
index ff2fb3e8..3591e742 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -17,6 +17,7 @@ use std::convert::TryFrom;
use std::rc::Rc;
use crate::color::{Color, RgbaColor};
+use crate::diag::TypResult;
use crate::eval::{EvalContext, FuncArgs, Scope, Template, Type, Value};
use crate::exec::Exec;
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
diff --git a/src/library/text.rs b/src/library/text.rs
index 54e9794a..5973a7e2 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -4,29 +4,27 @@ use crate::layout::Paint;
use super::*;
/// `font`: Configure the font.
-pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let size = args.eat::<Linear>().or_else(|| args.named(ctx, "size"));
- let style = args.named(ctx, "style");
- let weight = args.named(ctx, "weight");
- let stretch = args.named(ctx, "stretch");
- let top_edge = args.named(ctx, "top-edge");
- let bottom_edge = args.named(ctx, "bottom-edge");
- let fill = args.named(ctx, "fill");
-
- let families: Vec<_> = args.all().collect();
- let list = if families.is_empty() {
- args.named(ctx, "family")
- } else {
- Some(FontDef(Rc::new(families)))
- };
+pub fn font(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let size = args.named::<Linear>("size")?.or_else(|| args.eat());
+ let style = args.named("style")?;
+ let weight = args.named("weight")?;
+ let stretch = args.named("stretch")?;
+ let top_edge = args.named("top-edge")?;
+ let bottom_edge = args.named("bottom-edge")?;
+ let fill = args.named("fill")?;
+
+ let list = args.named("family")?.or_else(|| {
+ let families: Vec<_> = args.all().collect();
+ (!families.is_empty()).then(|| FontDef(Rc::new(families)))
+ });
- let serif = args.named(ctx, "serif");
- let sans_serif = args.named(ctx, "sans-serif");
- let monospace = args.named(ctx, "monospace");
+ let serif = args.named("serif")?;
+ let sans_serif = args.named("sans-serif")?;
+ let monospace = args.named("monospace")?;
- let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
+ let body = args.expect::<Template>("body")?;
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
let state = ctx.state.font_mut();
if let Some(size) = size {
@@ -74,7 +72,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
body.exec(ctx);
- })
+ }))
}
struct FontDef(Rc<Vec<FontFamily>>);
@@ -106,12 +104,12 @@ castable! {
}
/// `par`: Configure paragraphs.
-pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- let par_spacing = args.named(ctx, "spacing");
- let line_spacing = args.named(ctx, "leading");
- let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
+pub fn par(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let par_spacing = args.named("spacing")?;
+ let line_spacing = args.named("leading")?;
+ let body = args.expect::<Template>("body")?;
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
let state = ctx.state.par_mut();
if let Some(par_spacing) = par_spacing {
@@ -124,33 +122,32 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
ctx.parbreak();
body.exec(ctx);
- })
+ }))
}
/// `lang`: Configure the language.
-pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
+pub fn lang(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
let iso = args.eat::<EcoString>();
- let dir = if let Some(dir) = args.named::<Spanned<Dir>>(ctx, "dir") {
+ let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? {
if dir.v.axis() == SpecAxis::Horizontal {
Some(dir.v)
} else {
- ctx.diag(error!(dir.span, "must be horizontal"));
- None
+ bail!(args.file, dir.span, "must be horizontal");
}
} else {
iso.as_deref().map(lang_dir)
};
- let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
+ let body = args.expect::<Template>("body")?;
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
if let Some(dir) = dir {
ctx.state.dirs.cross = dir;
}
ctx.parbreak();
body.exec(ctx);
- })
+ }))
}
/// The default direction for the language identified by the given `iso` code.
@@ -163,30 +160,29 @@ fn lang_dir(iso: &str) -> Dir {
}
/// `strike`: Enable striken-through text.
-pub fn strike(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- line_impl(ctx, args, |font| &mut font.strikethrough)
+pub fn strike(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ line_impl(args, |font| &mut font.strikethrough)
}
/// `underline`: Enable underlined text.
-pub fn underline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- line_impl(ctx, args, |font| &mut font.underline)
+pub fn underline(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ line_impl(args, |font| &mut font.underline)
}
/// `overline`: Add an overline above text.
-pub fn overline(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- line_impl(ctx, args, |font| &mut font.overline)
+pub fn overline(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ line_impl(args, |font| &mut font.overline)
}
fn line_impl(
- ctx: &mut EvalContext,
args: &mut FuncArgs,
substate: fn(&mut FontState) -> &mut Option<Rc<LineState>>,
-) -> Value {
- let stroke = args.eat().or_else(|| args.named(ctx, "stroke"));
- let thickness = args.eat::<Linear>().or_else(|| args.named(ctx, "thickness"));
- let offset = args.named(ctx, "offset");
- let extent = args.named(ctx, "extent").unwrap_or_default();
- let body = args.expect::<Template>(ctx, "body").unwrap_or_default();
+) -> TypResult<Value> {
+ let stroke = args.named("stroke")?.or_else(|| args.eat());
+ let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat());
+ let offset = args.named("offset")?;
+ let extent = args.named("extent")?.unwrap_or_default();
+ let body = args.expect::<Template>("body")?;
// Suppress any existing strikethrough if strength is explicitly zero.
let state = thickness.map_or(true, |s| !s.is_zero()).then(|| {
@@ -198,8 +194,8 @@ fn line_impl(
})
});
- Value::template(move |ctx| {
+ Ok(Value::template(move |ctx| {
*substate(ctx.state.font_mut()) = state.clone();
body.exec(ctx);
- })
+ }))
}
diff --git a/src/library/utility.rs b/src/library/utility.rs
index 4001d14f..fb39fce3 100644
--- a/src/library/utility.rs
+++ b/src/library/utility.rs
@@ -7,94 +7,78 @@ use crate::pretty::pretty;
use super::*;
/// `type`: The name of a value's type.
-pub fn type_(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- match args.expect::<Value>(ctx, "value") {
- Some(value) => value.type_name().into(),
- None => Value::Error,
- }
+pub fn type_(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let value = args.expect::<Value>("value")?;
+ Ok(value.type_name().into())
}
/// `repr`: The string representation of a value.
-pub fn repr(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- match args.expect::<Value>(ctx, "value") {
- Some(value) => pretty(&value).into(),
- None => Value::Error,
- }
+pub fn repr(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let value = args.expect::<Value>("value")?;
+ Ok(pretty(&value).into())
}
/// `len`: The length of a string, an array or a dictionary.
-pub fn len(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- match args.expect(ctx, "collection") {
- Some(Spanned { v: Value::Str(v), .. }) => Value::Int(v.len() as i64),
- Some(Spanned { v: Value::Array(v), .. }) => Value::Int(v.len() as i64),
- Some(Spanned { v: Value::Dict(v), .. }) => Value::Int(v.len() as i64),
- Some(other) if other.v != Value::Error => {
- ctx.diag(error!(other.span, "expected string, array or dictionary"));
- Value::Error
- }
- _ => Value::Error,
- }
+pub fn len(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ let Spanned { v, span } = args.expect("collection")?;
+ Ok(match v {
+ Value::Str(v) => Value::Int(v.len() as i64),
+ Value::Array(v) => Value::Int(v.len() as i64),
+ Value::Dict(v) => Value::Int(v.len() as i64),
+ _ => bail!(args.file, span, "expected string, array or dictionary"),
+ })
}
/// `rgb`: Create an RGB(A) color.
-pub fn rgb(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- Value::Color(Color::Rgba(
+pub fn rgb(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ Ok(Value::Color(Color::Rgba(
if let Some(string) = args.eat::<Spanned<EcoString>>() {
match RgbaColor::from_str(&string.v) {
Ok(color) => color,
- Err(_) => {
- ctx.diag(error!(string.span, "invalid color"));
- return Value::Error;
- }
+ Err(_) => bail!(args.file, string.span, "invalid color"),
}
} else {
- let r = args.expect(ctx, "red component").unwrap_or(0.0);
- let g = args.expect(ctx, "green component").unwrap_or(0.0);
- let b = args.expect(ctx, "blue component").unwrap_or(0.0);
+ let r = args.expect("red component")?;
+ let g = args.expect("green component")?;
+ let b = args.expect("blue component")?;
let a = args.eat().unwrap_or(1.0);
let f = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
RgbaColor::new(f(r), f(g), f(b), f(a))
},
- ))
+ )))
}
/// `min`: The minimum of a sequence of values.
-pub fn min(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- minmax(ctx, args, Ordering::Less)
+pub fn min(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ minmax(args, Ordering::Less)
}
/// `max`: The maximum of a sequence of values.
-pub fn max(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
- minmax(ctx, args, Ordering::Greater)
+pub fn max(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
+ minmax(args, Ordering::Greater)
}
/// Find the minimum or maximum of a sequence of values.
-fn minmax(ctx: &mut EvalContext, args: &mut FuncArgs, goal: Ordering) -> Value {
- let span = args.span;
- let mut extremum = None;
+fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult<Value> {
+ let &mut FuncArgs { file, span, .. } = args;
+ let mut extremum = args.expect::<Value>("value")?;
for value in args.all::<Value>() {
- if let Some(prev) = &extremum {
- match value.partial_cmp(&prev) {
- Some(ordering) if ordering == goal => extremum = Some(value),
- Some(_) => {}
- None => {
- ctx.diag(error!(
- span,
- "cannot compare {} with {}",
- prev.type_name(),
- value.type_name(),
- ));
- return Value::Error;
+ match value.partial_cmp(&extremum) {
+ Some(ordering) => {
+ if ordering == goal {
+ extremum = value;
}
}
- } else {
- extremum = Some(value);
+ None => bail!(
+ file,
+ span,
+ "cannot compare {} with {}",
+ extremum.type_name(),
+ value.type_name(),
+ ),
}
}
- extremum.unwrap_or_else(|| {
- args.expect::<Value>(ctx, "value");
- Value::Error
- })
+ Ok(extremum)
}
diff --git a/src/loading/mod.rs b/src/loading/mod.rs
index 64a65580..ab52439d 100644
--- a/src/loading/mod.rs
+++ b/src/loading/mod.rs
@@ -34,7 +34,8 @@ pub trait Loader {
/// A file id that can be [resolved](Loader::resolve_from) from a path.
///
/// Should be the same for all paths pointing to the same file.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[derive(Serialize, Deserialize)]
pub struct FileId(u64);
impl FileId {
diff --git a/src/main.rs b/src/main.rs
index 91edff17..7891082d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -35,32 +35,35 @@ fn main() -> anyhow::Result<()> {
.wrap();
// Resolve the file id of the source file and read the file.
- let src_id = loader.resolve(src_path).context("source file not found")?;
+ let file = loader.resolve(src_path).context("source file not found")?;
let src = fs::read_to_string(&src_path)
.map_err(|_| anyhow!("failed to read source file"))?;
// Typeset.
let mut ctx = typst::Context::new(loader);
- let pass = ctx.typeset(src_id, &src);
+ match ctx.typeset(file, &src) {
+ // Export the PDF.
+ Ok(document) => {
+ let buffer = typst::export::pdf(&ctx, &document);
+ fs::write(&dest_path, buffer).context("failed to write PDF file")?;
+ }
- // Print diagnostics.
- let map = typst::parse::LineMap::new(&src);
- for diag in pass.diags {
- let start = map.location(diag.span.start).unwrap();
- let end = map.location(diag.span.end).unwrap();
- println!(
- "{}: {}:{}-{}: {}",
- diag.level,
- src_path.display(),
- start,
- end,
- diag.message,
- );
+ // Print diagnostics.
+ Err(errors) => {
+ let map = typst::parse::LineMap::new(&src);
+ for error in errors.iter() {
+ let start = map.location(error.span.start).unwrap();
+ let end = map.location(error.span.end).unwrap();
+ println!(
+ "Error: {}:{}-{}: {}",
+ src_path.display(),
+ start,
+ end,
+ error.message,
+ );
+ }
+ }
}
- // Export the PDF.
- let buffer = typst::export::pdf(&ctx, &pass.output);
- fs::write(&dest_path, buffer).context("failed to write PDF file")?;
-
Ok(())
}
diff --git a/src/parse/lines.rs b/src/parse/lines.rs
index e42d110b..2d97a25c 100644
--- a/src/parse/lines.rs
+++ b/src/parse/lines.rs
@@ -77,8 +77,8 @@ impl<'s> LineMap<'s> {
}
}
-/// Determine the column at the end of the string.
-pub fn search_column(src: &str) -> usize {
+/// Count how many column the string would fill.
+pub fn count_columns(src: &str) -> usize {
let mut column = 0;
for c in src.chars().rev() {
if is_newline(c) {
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index a94345a8..70c23442 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -14,14 +14,21 @@ pub use tokens::*;
use std::rc::Rc;
-use crate::diag::Pass;
+use crate::diag::TypResult;
+use crate::loading::FileId;
use crate::syntax::*;
use crate::util::EcoString;
/// Parse a string of source code.
-pub fn parse(src: &str) -> Pass<SyntaxTree> {
- let mut p = Parser::new(src);
- Pass::new(tree(&mut p), p.diags)
+pub fn parse(file: FileId, src: &str) -> TypResult<SyntaxTree> {
+ let mut p = Parser::new(file, src);
+ let tree = tree(&mut p);
+ let errors = p.finish();
+ if errors.is_empty() {
+ Ok(tree)
+ } else {
+ Err(Box::new(errors))
+ }
}
/// Parse a syntax tree.
@@ -126,7 +133,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<SyntaxNode> {
p.start_group(group, TokenMode::Code);
let expr = expr_with(p, true, 0);
if stmt && expr.is_some() && !p.eof() {
- p.expected_at("semicolon or line break", p.prev_end());
+ p.expected_at(p.prev_end(), "semicolon or line break");
}
p.end_group();
@@ -160,12 +167,12 @@ fn unicode_escape(p: &mut Parser, token: UnicodeEscapeToken) -> EcoString {
c.into()
} else {
// Print out the escape sequence verbatim if it is invalid.
- p.diag(error!(span, "invalid unicode escape sequence"));
+ p.error(span, "invalid unicode escape sequence");
p.peek_src().into()
};
if !token.terminated {
- p.diag(error!(span.end, "expected closing brace"));
+ p.error(span.end, "expected closing brace");
}
text
@@ -176,7 +183,7 @@ fn raw(p: &mut Parser, token: RawToken) -> SyntaxNode {
let span = p.peek_span();
let raw = resolve::resolve_raw(span, token.text, token.backticks);
if !token.terminated {
- p.diag(error!(p.peek_span().end, "expected backtick(s)"));
+ p.error(span.end, "expected backtick(s)");
}
SyntaxNode::Raw(raw)
}
@@ -198,11 +205,7 @@ fn heading(p: &mut Parser) -> SyntaxNode {
let body = tree_indented(p);
- SyntaxNode::Heading(HeadingNode {
- span: p.span(start),
- level,
- body: Rc::new(body),
- })
+ SyntaxNode::Heading(HeadingNode { span: p.span(start), level, body })
}
/// Parse a single list item.
@@ -356,7 +359,7 @@ fn literal(p: &mut Parser) -> Option<Expr> {
Token::Fraction(p) => Expr::Fractional(span, p),
Token::Str(token) => Expr::Str(span, {
if !token.terminated {
- p.expected_at("quote", p.peek_span().end);
+ p.expected_at(span.end, "quote");
}
resolve::resolve_string(token.string)
}),
@@ -421,7 +424,7 @@ fn collection(p: &mut Parser) -> (Vec<CallArg>, bool) {
items.push(arg);
if let Some(pos) = missing_coma.take() {
- p.expected_at("comma", pos);
+ p.expected_at(pos, "comma");
}
if p.eof() {
@@ -447,7 +450,7 @@ fn item(p: &mut Parser) -> Option<CallArg> {
if let Expr::Ident(name) = first {
Some(CallArg::Named(Named { name, expr: expr(p)? }))
} else {
- p.diag(error!(first.span(), "expected identifier"));
+ p.error(first.span(), "expected identifier");
expr(p);
None
}
@@ -461,7 +464,7 @@ fn array(p: &mut Parser, items: Vec<CallArg>, span: Span) -> Expr {
let items = items.into_iter().filter_map(|item| match item {
CallArg::Pos(expr) => Some(expr),
CallArg::Named(_) => {
- p.diag(error!(item.span(), "expected expression, found named pair"));
+ p.error(item.span(), "expected expression, found named pair");
None
}
});
@@ -474,7 +477,7 @@ fn dict(p: &mut Parser, items: Vec<CallArg>, span: Span) -> Expr {
let items = items.into_iter().filter_map(|item| match item {
CallArg::Named(named) => Some(named),
CallArg::Pos(_) => {
- p.diag(error!(item.span(), "expected named pair, found expression"));
+ p.error(item.span(), "expected named pair, found expression");
None
}
});
@@ -488,7 +491,7 @@ fn idents(p: &mut Parser, items: Vec<CallArg>) -> Vec<Ident> {
let items = items.into_iter().filter_map(|item| match item {
CallArg::Pos(Expr::Ident(id)) => Some(id),
_ => {
- p.diag(error!(item.span(), "expected identifier"));
+ p.error(item.span(), "expected identifier");
None
}
});
@@ -512,7 +515,7 @@ fn block(p: &mut Parser, scoping: bool) -> Expr {
if let Some(expr) = expr(p) {
exprs.push(expr);
if !p.eof() {
- p.expected_at("semicolon or line break", p.prev_end());
+ p.expected_at(p.prev_end(), "semicolon or line break");
}
}
p.end_group();
@@ -529,10 +532,7 @@ fn call(p: &mut Parser, callee: Expr) -> Option<Expr> {
let mut wide = p.eat_if(Token::Excl);
if wide && p.outer_mode() == TokenMode::Code {
let span = p.span(callee.span().start);
- p.diag(error!(
- span,
- "wide calls are only allowed directly in templates",
- ));
+ p.error(span, "wide calls are only allowed directly in templates");
wide = false;
}
@@ -548,7 +548,7 @@ fn call(p: &mut Parser, callee: Expr) -> Option<Expr> {
items: vec![],
},
_ => {
- p.expected_at("argument list", p.prev_end());
+ p.expected_at(p.prev_end(), "argument list");
return None;
}
};
@@ -616,7 +616,7 @@ fn let_expr(p: &mut Parser) -> Option<Expr> {
init = expr(p);
} else if params.is_some() {
// Function definitions must have a body.
- p.expected_at("body", p.prev_end());
+ p.expected_at(p.prev_end(), "body");
}
// Rewrite into a closure expression if it's a function definition.
@@ -738,7 +738,7 @@ fn import_expr(p: &mut Parser) -> Option<Expr> {
p.start_group(Group::Imports, TokenMode::Code);
let items = collection(p).0;
if items.is_empty() {
- p.expected_at("import items", p.prev_end());
+ p.expected_at(p.prev_end(), "import items");
}
p.end_group();
Imports::Idents(idents(p, items))
@@ -790,7 +790,7 @@ fn body(p: &mut Parser) -> Option<Expr> {
Some(Token::LeftBracket) => Some(template(p)),
Some(Token::LeftBrace) => Some(block(p, true)),
_ => {
- p.expected_at("body", p.prev_end());
+ p.expected_at(p.prev_end(), "body");
None
}
}
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 5fba961a..0238c8be 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -1,14 +1,17 @@
use std::fmt::{self, Debug, Formatter};
use std::ops::Range;
-use super::{search_column, TokenMode, Tokens};
-use crate::diag::{Diag, DiagSet};
+use super::{count_columns, TokenMode, Tokens};
+use crate::diag::Error;
+use crate::loading::FileId;
use crate::syntax::{Pos, Span, Token};
/// A convenient token-based parser.
pub struct Parser<'s> {
- /// Parsing diagnostics.
- pub diags: DiagSet,
+ /// The id of the parsed file.
+ file: FileId,
+ /// Parsing errors.
+ errors: Vec<Error>,
/// An iterator over the source tokens.
tokens: Tokens<'s>,
/// The stack of open groups.
@@ -57,11 +60,12 @@ pub enum Group {
impl<'s> Parser<'s> {
/// Create a new parser for the source string.
- pub fn new(src: &'s str) -> Self {
+ pub fn new(file: FileId, src: &'s str) -> Self {
let mut tokens = Tokens::new(src, TokenMode::Markup);
let next = tokens.next();
Self {
- diags: DiagSet::new(),
+ file,
+ errors: vec![],
tokens,
groups: vec![],
next,
@@ -71,39 +75,45 @@ impl<'s> Parser<'s> {
}
}
- /// Add a diagnostic.
- pub fn diag(&mut self, diag: Diag) {
- self.diags.insert(diag);
+ /// Finish parsing and return all errors.
+ pub fn finish(self) -> Vec<Error> {
+ self.errors
}
- /// Eat the next token and add a diagnostic that it is not the expected
- /// `thing`.
+ /// Add an error with location and message.
+ pub fn error(&mut self, span: impl Into<Span>, message: impl Into<String>) {
+ self.errors.push(Error {
+ file: self.file,
+ span: span.into(),
+ message: message.into(),
+ });
+ }
+
+ /// Eat the next token and add an error that it is not the expected `thing`.
pub fn expected(&mut self, what: &str) {
let before = self.next_start();
if let Some(found) = self.eat() {
let after = self.prev_end();
- self.diag(error!(
+ self.error(
before .. after,
- "expected {}, found {}",
- what,
- found.name(),
- ));
+ format!("expected {}, found {}", what, found.name()),
+ );
} else {
- self.expected_at(what, self.next_start());
+ self.expected_at(self.next_start(), what);
}
}
- /// Add a diagnostic that `what` was expected at the given position.
- pub fn expected_at(&mut self, what: &str, pos: impl Into<Pos>) {
- self.diag(error!(pos.into(), "expected {}", what));
+ /// Add an error that `what` was expected at the given position.
+ pub fn expected_at(&mut self, pos: impl Into<Pos>, what: &str) {
+ self.error(pos.into(), format!("expected {}", what));
}
- /// Eat the next token and add a diagnostic that it is unexpected.
+ /// Eat the next token and add an error that it is unexpected.
pub fn unexpected(&mut self) {
let before = self.next_start();
if let Some(found) = self.eat() {
let after = self.prev_end();
- self.diag(error!(before .. after, "unexpected {}", found.name()));
+ self.error(before .. after, format!("unexpected {}", found.name()));
}
}
@@ -159,7 +169,7 @@ impl<'s> Parser<'s> {
self.bump();
rescan = false;
} else if required {
- self.diag(error!(self.next_start(), "expected {}", end.name()));
+ self.error(self.next_start(), format!("expected {}", end.name()));
}
}
@@ -276,12 +286,12 @@ impl<'s> Parser<'s> {
Span::new(start, self.prev_end())
}
- /// Consume the next token if it is the given one and produce a diagnostic
- /// if not.
+ /// Consume the next token if it is the given one and produce an error if
+ /// not.
pub fn expect(&mut self, t: Token) -> bool {
let eaten = self.eat_if(t);
if !eaten {
- self.expected_at(t.name(), self.prev_end());
+ self.expected_at(self.prev_end(), t.name());
}
eaten
}
@@ -314,7 +324,7 @@ impl<'s> Parser<'s> {
/// Determine the column for the given index in the source.
pub fn column(&self, index: usize) -> usize {
- search_column(self.tokens.scanner().get(.. index))
+ count_columns(self.tokens.scanner().get(.. index))
}
/// The span from `start` to [`self.prev_end()`](Self::prev_end).
diff --git a/src/pretty.rs b/src/pretty.rs
index 10e4fbcb..a4e9b334 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -488,7 +488,6 @@ impl Pretty for Value {
Self::Template(v) => v.pretty(p),
Self::Func(v) => v.pretty(p),
Self::Dyn(v) => v.pretty(p),
- Self::Error => p.push_str("<error>"),
}
}
}
@@ -609,6 +608,7 @@ pretty_display! {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::loading::FileId;
use crate::parse::parse;
#[track_caller]
@@ -618,7 +618,7 @@ mod tests {
#[track_caller]
fn test_parse(src: &str, exp: &str) {
- let ast = parse(src).output;
+ let ast = parse(FileId::from_raw(0), src).unwrap();
let found = pretty(&ast);
if exp != found {
println!("tree: {:#?}", ast);
@@ -732,6 +732,7 @@ mod tests {
#[test]
fn test_pretty_print_value() {
+ // Primitives.
test_value(Value::None, "none");
test_value(false, "false");
test_value(12i64, "12");
@@ -742,6 +743,8 @@ mod tests {
test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
test_value(Fractional::one() * 7.55, "7.55fr");
test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
+
+ // Collections.
test_value("hello", r#""hello""#);
test_value("\n", r#""\n""#);
test_value("\\", r#""\\""#);
@@ -752,12 +755,15 @@ mod tests {
test_value(dict![], "(:)");
test_value(dict!["one" => 1], "(one: 1)");
test_value(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
- test_value(Function::new(None, |_, _| Value::None), "<function>");
+
+ // Functions.
+ test_value(Function::new(None, |_, _| Ok(Value::None)), "<function>");
test_value(
- Function::new(Some("nil".into()), |_, _| Value::None),
+ Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
"<function nil>",
);
+
+ // Dynamics.
test_value(Dynamic::new(1), "1");
- test_value(Value::Error, "<error>");
}
}
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index 9294fecd..881752a6 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -1,5 +1,3 @@
-use std::rc::Rc;
-
use super::*;
/// A syntax node, encompassing a single logical entity of parsed source code.
@@ -52,7 +50,7 @@ pub struct HeadingNode {
/// The section depth (numer of equals signs).
pub level: usize,
/// The contents of the heading.
- pub body: Rc<SyntaxTree>,
+ pub body: SyntaxTree,
}
/// An item in an unordered list: `- ...`.
diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs
index b6ee657a..c38e87aa 100644
--- a/src/syntax/visit.rs
+++ b/src/syntax/visit.rs
@@ -102,7 +102,7 @@ impl_visitors! {
}
visit_heading(v, heading: HeadingNode) {
- v.visit_tree(r!(rc: heading.body));
+ v.visit_tree(r!(heading.body));
}
visit_list(v, item: ListItem) {
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 2c53bc70..dc400af8 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -73,7 +73,7 @@ impl<T> SliceExt<T> for [T] {
}
}
-/// This struct is produced by [`SliceExt::group_by_key`].
+/// This struct is created by [`SliceExt::group_by_key`].
pub struct GroupByKey<'a, T, F> {
slice: &'a [T],
f: F,