diff --git a/benches/oneshot.rs b/benches/oneshot.rs index b1ed6f3f9..50fee9b18 100644 --- a/benches/oneshot.rs +++ b/benches/oneshot.rs @@ -1,9 +1,9 @@ -use std::io; use std::path::Path; use iai::{black_box, main, Iai}; use unscanny::Scanner; +use typst::diag::{FileError, FileResult}; use typst::font::{Font, FontBook}; use typst::parse::{TokenMode, Tokens}; use typst::source::{Source, SourceId}; @@ -120,8 +120,8 @@ impl World for BenchWorld { &self.config } - fn resolve(&self, _: &Path) -> io::Result { - Err(io::ErrorKind::NotFound.into()) + fn resolve(&self, path: &Path) -> FileResult { + Err(FileError::NotFound(path.into())) } fn source(&self, _: SourceId) -> &Source { @@ -132,11 +132,11 @@ impl World for BenchWorld { &self.book } - fn font(&self, _: usize) -> io::Result { - Ok(self.font.clone()) + fn font(&self, _: usize) -> Option { + Some(self.font.clone()) } - fn file(&self, _: &Path) -> io::Result { - Err(io::ErrorKind::NotFound.into()) + fn file(&self, path: &Path) -> FileResult { + Err(FileError::NotFound(path.into())) } } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 6871be1c4..052e7fcfc 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -331,7 +331,7 @@ fn generate_set( ) -> syn::ImplItemMethod { let user = user.map(|method| { let block = &method.block; - quote! { (|| -> TypResult<()> { #block; Ok(()) } )()?; } + quote! { (|| -> SourceResult<()> { #block; Ok(()) } )()?; } }); let mut shorthands = vec![]; @@ -367,7 +367,7 @@ fn generate_set( }); parse_quote! { - fn set(args: &mut Args, constructor: bool) -> TypResult { + fn set(args: &mut Args, constructor: bool) -> SourceResult { let mut styles = StyleMap::new(); #user #(#bindings)* diff --git a/src/diag.rs b/src/diag.rs index e96dfb004..ebd192c23 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -2,24 +2,28 @@ use std::fmt::{self, Display, Formatter}; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::syntax::{Span, Spanned}; use crate::World; -/// Early-return with a [`TypError`]. +/// Early-return with a [`SourceError`]. #[macro_export] macro_rules! bail { + ($error:expr) => { + return Err(Box::new(vec![$error])) + }; + ($($tts:tt)*) => { - return Err($crate::error!($($tts)*).into()) + $crate::bail!($crate::error!($($tts)*)) }; } -/// Construct a [`TypError`]. +/// Construct a [`SourceError`]. #[macro_export] macro_rules! error { ($span:expr, $message:expr $(,)?) => { - Box::new(vec![$crate::diag::Error::new($span, $message)]) + $crate::diag::SourceError::new($span, $message) }; ($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => { @@ -27,18 +31,12 @@ macro_rules! error { }; } -/// The result type for typesetting and all its subpasses. -pub type TypResult = Result; - -/// The error type for typesetting and all its subpasses. -pub type TypError = Box>; - -/// A result type with a string error message. -pub type StrResult = Result; +/// A result that can carry multiple source errors. +pub type SourceResult = Result>>; /// An error in a source file. #[derive(Debug, Clone, Eq, PartialEq)] -pub struct Error { +pub struct SourceError { /// The erroneous node in the source code. pub span: Span, /// A diagnostic message describing the problem. @@ -47,7 +45,7 @@ pub struct Error { pub trace: Vec>, } -impl Error { +impl SourceError { /// Create a new, bare error. pub fn new(span: Span, message: impl Into) -> Self { Self { @@ -58,7 +56,7 @@ impl Error { } } -/// A part of an error's [trace](Error::trace). +/// A part of an error's [trace](SourceError::trace). #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum Tracepoint { /// A function call. @@ -83,22 +81,7 @@ impl Display for Tracepoint { } } -/// Convert a [`StrResult`] to a [`TypResult`] by adding span information. -pub trait At { - /// Add the span information. - fn at(self, span: Span) -> TypResult; -} - -impl At for Result -where - S: Into, -{ - fn at(self, span: Span) -> TypResult { - self.map_err(|message| error!(span, message)) - } -} - -/// Enrich a [`TypResult`] with a tracepoint. +/// Enrich a [`SourceResult`] with a tracepoint. pub trait Trace { /// Add the tracepoint to all errors that lie outside the `span`. fn trace(self, world: &dyn World, make_point: F, span: Span) -> Self @@ -106,7 +89,7 @@ pub trait Trace { F: Fn() -> Tracepoint; } -impl Trace for TypResult { +impl Trace for SourceResult { fn trace(self, world: &dyn World, make_point: F, span: Span) -> Self where F: Fn() -> Tracepoint, @@ -127,6 +110,9 @@ impl Trace for TypResult { } } +/// A result type with a string error message. +pub type StrResult = Result; + /// Transform `expected X, found Y` into `expected X or A, found Y`. pub fn with_alternative(msg: String, alt: &str) -> String { let mut parts = msg.split(", found "); @@ -137,12 +123,70 @@ pub fn with_alternative(msg: String, alt: &str) -> String { } } -/// Format a file loading failure. -pub fn failed_to_load(target: &str, path: &Path, error: io::Error) -> String { - match error.kind() { - io::ErrorKind::NotFound => { - format!("file not found (searched at {})", path.display()) - } - _ => format!("failed to load {target} ({error})"), +/// Convert a [`StrResult`] to a [`SourceResult`] by adding span information. +pub trait At { + /// Add the span information. + fn at(self, span: Span) -> SourceResult; +} + +impl At for Result +where + S: Into, +{ + fn at(self, span: Span) -> SourceResult { + self.map_err(|message| Box::new(vec![error!(span, message)])) + } +} + +/// A result type with a file-related error. +pub type FileResult = Result; + +/// An error that occured while trying to load of a file. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum FileError { + /// A file was not found at this path. + NotFound(PathBuf), + /// A file could not be accessed. + AccessDenied, + /// The file was not valid UTF-8, but should have been. + InvalidUtf8, + /// Another error. + Other, +} + +impl FileError { + /// Create a file error from an I/O error. + pub fn from_io(error: io::Error, path: &Path) -> Self { + match error.kind() { + io::ErrorKind::NotFound => Self::NotFound(path.into()), + io::ErrorKind::PermissionDenied => Self::AccessDenied, + io::ErrorKind::InvalidData + if error.to_string().contains("stream did not contain valid UTF-8") => + { + Self::InvalidUtf8 + } + _ => Self::Other, + } + } +} + +impl std::error::Error for FileError {} + +impl Display for FileError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::NotFound(path) => { + write!(f, "file not found (searched at {})", path.display()) + } + Self::AccessDenied => f.pad("file access denied"), + Self::InvalidUtf8 => f.pad("file is not valid utf-8"), + Self::Other => f.pad("failed to load file"), + } + } +} + +impl From for String { + fn from(error: FileError) -> Self { + error.to_string() } } diff --git a/src/eval/args.rs b/src/eval/args.rs index 8d62b675a..f95fbf08c 100644 --- a/src/eval/args.rs +++ b/src/eval/args.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use super::{Array, Cast, Dict, Str, Value}; -use crate::diag::{At, TypResult}; +use crate::diag::{At, SourceResult}; use crate::syntax::{Span, Spanned}; /// Evaluated arguments to a function. @@ -48,7 +48,7 @@ impl Args { } /// Consume and cast the first positional argument if there is one. - pub fn eat(&mut self) -> TypResult> + pub fn eat(&mut self) -> SourceResult> where T: Cast>, { @@ -66,7 +66,7 @@ impl Args { /// /// Returns a `missing argument: {what}` error if no positional argument is /// left. - pub fn expect(&mut self, what: &str) -> TypResult + pub fn expect(&mut self, what: &str) -> SourceResult where T: Cast>, { @@ -77,7 +77,7 @@ impl Args { } /// Find and consume the first castable positional argument. - pub fn find(&mut self) -> TypResult> + pub fn find(&mut self) -> SourceResult> where T: Cast>, { @@ -92,7 +92,7 @@ impl Args { } /// Find and consume all castable positional arguments. - pub fn all(&mut self) -> TypResult> + pub fn all(&mut self) -> SourceResult> where T: Cast>, { @@ -105,7 +105,7 @@ impl Args { /// Cast and remove the value for the given named argument, returning an /// error if the conversion fails. - pub fn named(&mut self, name: &str) -> TypResult> + pub fn named(&mut self, name: &str) -> SourceResult> where T: Cast>, { @@ -126,7 +126,7 @@ impl Args { } /// Same as named, but with fallback to find. - pub fn named_or_find(&mut self, name: &str) -> TypResult> + pub fn named_or_find(&mut self, name: &str) -> SourceResult> where T: Cast>, { @@ -146,7 +146,7 @@ impl Args { /// Return an "unexpected argument" error if there is any remaining /// argument. - pub fn finish(self) -> TypResult<()> { + pub fn finish(self) -> SourceResult<()> { if let Some(arg) = self.items.first() { bail!(arg.span, "unexpected argument"); } @@ -171,17 +171,17 @@ impl Args { } /// Reinterpret these arguments as actually being an array index. - pub fn into_index(self) -> TypResult { + pub fn into_index(self) -> SourceResult { self.into_castable("index") } /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> TypResult { + pub fn into_key(self) -> SourceResult { self.into_castable("key") } /// Reinterpret these arguments as actually being a single castable thing. - fn into_castable(self, what: &str) -> TypResult { + fn into_castable(self, what: &str) -> SourceResult { let mut iter = self.items.into_iter(); let value = match iter.next() { Some(Arg { name: None, value, .. }) => value.v.cast().at(value.span)?, diff --git a/src/eval/array.rs b/src/eval/array.rs index 6d558393a..b77ce93c5 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign}; use std::sync::Arc; use super::{ops, Args, Func, Value, Vm}; -use crate::diag::{At, StrResult, TypResult}; +use crate::diag::{At, SourceResult, StrResult}; use crate::syntax::Spanned; use crate::util::ArcExt; @@ -124,7 +124,7 @@ impl Array { } /// Return the first matching element. - pub fn find(&self, vm: &mut Vm, f: Spanned) -> TypResult> { + pub fn find(&self, vm: &mut Vm, f: Spanned) -> SourceResult> { for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::().at(f.span)? { @@ -136,7 +136,7 @@ impl Array { } /// Return the index of the first matching element. - pub fn position(&self, vm: &mut Vm, f: Spanned) -> TypResult> { + pub fn position(&self, vm: &mut Vm, f: Spanned) -> SourceResult> { for (i, item) in self.iter().enumerate() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::().at(f.span)? { @@ -149,7 +149,7 @@ impl Array { /// Return a new array with only those elements for which the function /// returns true. - pub fn filter(&self, vm: &mut Vm, f: Spanned) -> TypResult { + pub fn filter(&self, vm: &mut Vm, f: Spanned) -> SourceResult { let mut kept = vec![]; for item in self.iter() { let args = Args::new(f.span, [item.clone()]); @@ -161,10 +161,9 @@ impl Array { } /// Transform each item in the array with a function. - pub fn map(&self, vm: &mut Vm, f: Spanned) -> TypResult { + pub fn map(&self, vm: &mut Vm, f: Spanned) -> SourceResult { let enumerate = f.v.argc() == Some(2); - Ok(self - .iter() + self.iter() .enumerate() .map(|(i, item)| { let mut args = Args::new(f.span, []); @@ -174,11 +173,11 @@ impl Array { args.push(f.span, item.clone()); f.v.call(vm, args) }) - .collect::>()?) + .collect() } /// Whether any element matches. - pub fn any(&self, vm: &mut Vm, f: Spanned) -> TypResult { + pub fn any(&self, vm: &mut Vm, f: Spanned) -> SourceResult { for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if f.v.call(vm, args)?.cast::().at(f.span)? { @@ -190,7 +189,7 @@ impl Array { } /// Whether all elements match. - pub fn all(&self, vm: &mut Vm, f: Spanned) -> TypResult { + pub fn all(&self, vm: &mut Vm, f: Spanned) -> SourceResult { for item in self.iter() { let args = Args::new(f.span, [item.clone()]); if !f.v.call(vm, args)?.cast::().at(f.span)? { diff --git a/src/eval/dict.rs b/src/eval/dict.rs index 3ba72c8ae..b95ead31c 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -4,7 +4,7 @@ use std::ops::{Add, AddAssign}; use std::sync::Arc; use super::{Args, Array, Func, Str, Value, Vm}; -use crate::diag::{StrResult, TypResult}; +use crate::diag::{SourceResult, StrResult}; use crate::parse::is_ident; use crate::syntax::Spanned; use crate::util::ArcExt; @@ -101,14 +101,13 @@ impl Dict { } /// Transform each pair in the array with a function. - pub fn map(&self, vm: &mut Vm, f: Spanned) -> TypResult { - Ok(self - .iter() + pub fn map(&self, vm: &mut Vm, f: Spanned) -> SourceResult { + self.iter() .map(|(key, value)| { let args = Args::new(f.span, [Value::Str(key.clone()), value.clone()]); f.v.call(vm, args) }) - .collect::>()?) + .collect() } /// Iterate over pairs of references to the contained keys and values. diff --git a/src/eval/func.rs b/src/eval/func.rs index d6b5252ab..b8730a8a9 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; use super::{Args, Eval, Flow, Scope, Scopes, Value, Vm}; -use crate::diag::{StrResult, TypResult}; +use crate::diag::{SourceResult, StrResult}; use crate::model::{Content, NodeId, StyleMap}; use crate::source::SourceId; use crate::syntax::ast::Expr; @@ -29,7 +29,7 @@ impl Func { /// Create a new function from a native rust function. pub fn from_fn( name: &'static str, - func: fn(&mut Vm, &mut Args) -> TypResult, + func: fn(&mut Vm, &mut Args) -> SourceResult, ) -> Self { Self(Arc::new(Repr::Native(Native { name, @@ -86,7 +86,7 @@ impl Func { } /// Call the function with the given arguments. - pub fn call(&self, vm: &mut Vm, mut args: Args) -> TypResult { + pub fn call(&self, vm: &mut Vm, mut args: Args) -> SourceResult { let value = match self.0.as_ref() { Repr::Native(native) => (native.func)(vm, &mut args)?, Repr::Closure(closure) => closure.call(vm, &mut args)?, @@ -100,13 +100,13 @@ impl Func { } /// Call the function without an existing virtual machine. - pub fn call_detached(&self, world: &dyn World, args: Args) -> TypResult { + pub fn call_detached(&self, world: &dyn World, args: Args) -> SourceResult { let mut vm = Vm::new(world, vec![], Scopes::new(None)); self.call(&mut vm, args) } /// Execute the function's set rule and return the resulting style map. - pub fn set(&self, mut args: Args) -> TypResult { + pub fn set(&self, mut args: Args) -> SourceResult { let styles = match self.0.as_ref() { Repr::Native(Native { set: Some(set), .. }) => set(&mut args)?, _ => StyleMap::new(), @@ -144,9 +144,9 @@ struct Native { /// The name of the function. pub name: &'static str, /// The function pointer. - pub func: fn(&mut Vm, &mut Args) -> TypResult, + pub func: fn(&mut Vm, &mut Args) -> SourceResult, /// The set rule. - pub set: Option TypResult>, + pub set: Option SourceResult>, /// The id of the node to customize with this function's show rule. pub node: Option, } @@ -169,13 +169,13 @@ pub trait Node: 'static { /// /// This is passed only the arguments that remain after execution of the /// node's set rule. - fn construct(vm: &mut Vm, args: &mut Args) -> TypResult; + fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult; /// Parse relevant arguments into style properties for this node. /// /// When `constructor` is true, [`construct`](Self::construct) will run /// after this invocation of `set` with the remaining arguments. - fn set(args: &mut Args, constructor: bool) -> TypResult; + fn set(args: &mut Args, constructor: bool) -> SourceResult; } /// A user-defined closure. @@ -198,7 +198,7 @@ pub struct Closure { impl Closure { /// Call the function in the context with the arguments. - pub fn call(&self, vm: &mut Vm, args: &mut Args) -> TypResult { + pub fn call(&self, vm: &mut Vm, args: &mut Args) -> SourceResult { // Don't leak the scopes from the call site. Instead, we use the scope // of captured variables we collected earlier. let mut scopes = Scopes::new(None); @@ -235,7 +235,7 @@ impl Closure { match sub.flow { Some(Flow::Return(_, Some(explicit))) => return Ok(explicit), Some(Flow::Return(_, None)) => {} - Some(flow) => return Err(flow.forbidden())?, + Some(flow) => bail!(flow.forbidden()), None => {} } diff --git a/src/eval/methods.rs b/src/eval/methods.rs index 08d7dd3fa..19f3d65ee 100644 --- a/src/eval/methods.rs +++ b/src/eval/methods.rs @@ -1,7 +1,7 @@ //! Methods on values. use super::{Args, Value, Vm}; -use crate::diag::{At, TypResult}; +use crate::diag::{At, SourceResult}; use crate::syntax::Span; use crate::util::EcoString; @@ -12,7 +12,7 @@ pub fn call( method: &str, mut args: Args, span: Span, -) -> TypResult { +) -> SourceResult { let name = value.type_name(); let missing = || Err(missing_method(name, method)).at(span); @@ -121,7 +121,7 @@ pub fn call_mut( method: &str, mut args: Args, span: Span, -) -> TypResult<()> { +) -> SourceResult<()> { let name = value.type_name(); let missing = || Err(missing_method(name, method)).at(span); diff --git a/src/eval/mod.rs b/src/eval/mod.rs index bae9ac940..eeb95534c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -36,7 +36,7 @@ use std::collections::BTreeMap; use unicode_segmentation::UnicodeSegmentation; -use crate::diag::{failed_to_load, At, StrResult, Trace, Tracepoint, TypResult}; +use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint}; use crate::geom::{Angle, Em, Fraction, Length, Ratio}; use crate::library; use crate::model::{Content, Pattern, Recipe, StyleEntry, StyleMap}; @@ -55,7 +55,7 @@ pub fn evaluate( world: &dyn World, id: SourceId, mut route: Vec, -) -> TypResult { +) -> SourceResult { // Prevent cyclic evaluation. if route.contains(&id) { let path = world.source(id).path().display(); @@ -73,7 +73,7 @@ pub fn evaluate( // Handle control flow. if let Some(flow) = vm.flow { - return Err(flow.forbidden()); + bail!(flow.forbidden()); } // Assemble the module. @@ -95,13 +95,13 @@ pub trait Eval { type Output; /// Evaluate the expression to the output value. - fn eval(&self, vm: &mut Vm) -> TypResult; + fn eval(&self, vm: &mut Vm) -> SourceResult; } impl Eval for Markup { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { eval_markup(vm, &mut self.nodes()) } } @@ -110,7 +110,7 @@ impl Eval for Markup { fn eval_markup( vm: &mut Vm, nodes: &mut impl Iterator, -) -> TypResult { +) -> SourceResult { let flow = vm.flow.take(); let mut seq = Vec::with_capacity(nodes.size_hint().1.unwrap_or_default()); @@ -157,7 +157,7 @@ fn eval_markup( impl Eval for MarkupNode { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(match self { Self::Space => Content::Space, Self::Parbreak => Content::Parbreak, @@ -181,7 +181,7 @@ impl Eval for MarkupNode { impl Eval for StrongNode { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(Content::show(library::text::StrongNode( self.body().eval(vm)?, ))) @@ -191,7 +191,7 @@ impl Eval for StrongNode { impl Eval for EmphNode { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(Content::show(library::text::EmphNode( self.body().eval(vm)?, ))) @@ -201,7 +201,7 @@ impl Eval for EmphNode { impl Eval for RawNode { type Output = Content; - fn eval(&self, _: &mut Vm) -> TypResult { + fn eval(&self, _: &mut Vm) -> SourceResult { let content = Content::show(library::text::RawNode { text: self.text.clone(), block: self.block, @@ -216,7 +216,7 @@ impl Eval for RawNode { impl Eval for Spanned { type Output = Content; - fn eval(&self, _: &mut Vm) -> TypResult { + fn eval(&self, _: &mut Vm) -> SourceResult { Ok(Content::show(library::math::MathNode { formula: self.clone().map(|math| math.formula), display: self.v.display, @@ -227,7 +227,7 @@ impl Eval for Spanned { impl Eval for HeadingNode { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(Content::show(library::structure::HeadingNode { body: self.body().eval(vm)?, level: self.level(), @@ -238,7 +238,7 @@ impl Eval for HeadingNode { impl Eval for ListNode { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(Content::Item(library::structure::ListItem { kind: library::structure::UNORDERED, number: None, @@ -250,7 +250,7 @@ impl Eval for ListNode { impl Eval for EnumNode { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { Ok(Content::Item(library::structure::ListItem { kind: library::structure::ORDERED, number: self.number(), @@ -262,7 +262,7 @@ impl Eval for EnumNode { impl Eval for Expr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let forbidden = |name| { error!( self.span(), @@ -285,9 +285,9 @@ impl Eval for Expr { Self::Unary(v) => v.eval(vm), Self::Binary(v) => v.eval(vm), Self::Let(v) => v.eval(vm), - Self::Set(_) => Err(forbidden("set")), - Self::Show(_) => Err(forbidden("show")), - Self::Wrap(_) => Err(forbidden("wrap")), + Self::Set(_) => bail!(forbidden("set")), + Self::Show(_) => bail!(forbidden("show")), + Self::Wrap(_) => bail!(forbidden("wrap")), Self::If(v) => v.eval(vm), Self::While(v) => v.eval(vm), Self::For(v) => v.eval(vm), @@ -303,7 +303,7 @@ impl Eval for Expr { impl Eval for Lit { type Output = Value; - fn eval(&self, _: &mut Vm) -> TypResult { + fn eval(&self, _: &mut Vm) -> SourceResult { Ok(match self.kind() { LitKind::None => Value::None, LitKind::Auto => Value::Auto, @@ -325,7 +325,7 @@ impl Eval for Lit { impl Eval for Ident { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { vm.scopes.get(self).cloned().at(self.span()) } } @@ -333,7 +333,7 @@ impl Eval for Ident { impl Eval for CodeBlock { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { vm.scopes.enter(); let output = eval_code(vm, &mut self.exprs())?; vm.scopes.exit(); @@ -342,7 +342,7 @@ impl Eval for CodeBlock { } /// Evaluate a stream of expressions. -fn eval_code(vm: &mut Vm, exprs: &mut impl Iterator) -> TypResult { +fn eval_code(vm: &mut Vm, exprs: &mut impl Iterator) -> SourceResult { let flow = vm.flow.take(); let mut output = Value::None; @@ -394,7 +394,7 @@ fn eval_code(vm: &mut Vm, exprs: &mut impl Iterator) -> TypResult TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { vm.scopes.enter(); let content = self.body().eval(vm)?; vm.scopes.exit(); @@ -405,7 +405,7 @@ impl Eval for ContentBlock { impl Eval for GroupExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { self.expr().eval(vm) } } @@ -413,7 +413,7 @@ impl Eval for GroupExpr { impl Eval for ArrayExpr { type Output = Array; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let items = self.items(); let mut vec = Vec::with_capacity(items.size_hint().0); @@ -435,7 +435,7 @@ impl Eval for ArrayExpr { impl Eval for DictExpr { type Output = Dict; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let mut map = BTreeMap::new(); for item in self.items() { @@ -465,7 +465,7 @@ impl Eval for DictExpr { impl Eval for UnaryExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let value = self.expr().eval(vm)?; let result = match self.op() { UnOp::Pos => ops::pos(value), @@ -479,7 +479,7 @@ impl Eval for UnaryExpr { impl Eval for BinaryExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { match self.op() { BinOp::Add => self.apply(vm, ops::add), BinOp::Sub => self.apply(vm, ops::sub), @@ -510,7 +510,7 @@ impl BinaryExpr { &self, vm: &mut Vm, op: fn(Value, Value) -> StrResult, - ) -> TypResult { + ) -> SourceResult { let lhs = self.lhs().eval(vm)?; // Short-circuit boolean operations. @@ -529,7 +529,7 @@ impl BinaryExpr { &self, vm: &mut Vm, op: fn(Value, Value) -> StrResult, - ) -> TypResult { + ) -> SourceResult { let rhs = self.rhs().eval(vm)?; let location = self.lhs().access(vm)?; let lhs = std::mem::take(&mut *location); @@ -541,7 +541,7 @@ impl BinaryExpr { impl Eval for FieldAccess { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let object = self.object().eval(vm)?; let span = self.field().span(); let field = self.field().take(); @@ -567,7 +567,7 @@ impl Eval for FieldAccess { impl Eval for FuncCall { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let callee = self.callee().eval(vm)?; let args = self.args().eval(vm)?; @@ -591,7 +591,7 @@ impl Eval for FuncCall { impl Eval for MethodCall { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.span(); let method = self.method(); let point = || Tracepoint::Call(Some(method.to_string())); @@ -613,7 +613,7 @@ impl Eval for MethodCall { impl Eval for CallArgs { type Output = Args; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let mut items = Vec::new(); for arg in self.items() { @@ -662,7 +662,7 @@ impl Eval for CallArgs { impl Eval for ClosureExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { // The closure's name is defined by its let binding if there's one. let name = self.name().map(Ident::take); @@ -709,7 +709,7 @@ impl Eval for ClosureExpr { impl Eval for LetExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let value = match self.init() { Some(expr) => expr.eval(vm)?, None => Value::None, @@ -722,7 +722,7 @@ impl Eval for LetExpr { impl Eval for SetExpr { type Output = StyleMap; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let target = self.target(); let target = target.eval(vm)?.cast::().at(target.span())?; let args = self.args().eval(vm)?; @@ -733,7 +733,7 @@ impl Eval for SetExpr { impl Eval for ShowExpr { type Output = Recipe; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { // Evaluate the target function. let pattern = self.pattern(); let pattern = pattern.eval(vm)?.cast::().at(pattern.span())?; @@ -770,7 +770,7 @@ impl Eval for ShowExpr { impl Eval for IfExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let condition = self.condition(); if condition.eval(vm)?.cast::().at(condition.span())? { self.if_body().eval(vm) @@ -785,7 +785,7 @@ impl Eval for IfExpr { impl Eval for WhileExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let flow = vm.flow.take(); let mut output = Value::None; @@ -817,7 +817,7 @@ impl Eval for WhileExpr { impl Eval for ForExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let flow = vm.flow.take(); let mut output = Value::None; vm.scopes.enter(); @@ -896,7 +896,7 @@ impl Eval for ForExpr { impl Eval for ImportExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.path().span(); let path = self.path().eval(vm)?.cast::().at(span)?; let module = import(vm, &path, span)?; @@ -925,7 +925,7 @@ impl Eval for ImportExpr { impl Eval for IncludeExpr { type Output = Content; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let span = self.path().span(); let path = self.path().eval(vm)?.cast::().at(span)?; let module = import(vm, &path, span)?; @@ -934,14 +934,10 @@ impl Eval for IncludeExpr { } /// Process an import of a module relative to the current location. -fn import(vm: &mut Vm, path: &str, span: Span) -> TypResult { +fn import(vm: &mut Vm, path: &str, span: Span) -> SourceResult { // Load the source file. let full = vm.locate(&path).at(span)?; - let id = vm - .world - .resolve(&full) - .map_err(|err| failed_to_load("source file", &full, err)) - .at(span)?; + let id = vm.world.resolve(&full).at(span)?; // Prevent cyclic importing. if vm.route.contains(&id) { @@ -959,7 +955,7 @@ fn import(vm: &mut Vm, path: &str, span: Span) -> TypResult { impl Eval for BreakExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { if vm.flow.is_none() { vm.flow = Some(Flow::Break(self.span())); } @@ -970,7 +966,7 @@ impl Eval for BreakExpr { impl Eval for ContinueExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { if vm.flow.is_none() { vm.flow = Some(Flow::Continue(self.span())); } @@ -981,7 +977,7 @@ impl Eval for ContinueExpr { impl Eval for ReturnExpr { type Output = Value; - fn eval(&self, vm: &mut Vm) -> TypResult { + fn eval(&self, vm: &mut Vm) -> SourceResult { let value = self.body().map(|body| body.eval(vm)).transpose()?; if vm.flow.is_none() { vm.flow = Some(Flow::Return(self.span(), value)); @@ -993,11 +989,11 @@ impl Eval for ReturnExpr { /// Access an expression mutably. pub trait Access { /// Access the value. - fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value>; + fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value>; } impl Access for Expr { - fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { match self { Expr::Ident(v) => v.access(vm), Expr::FieldAccess(v) => v.access(vm), @@ -1008,13 +1004,13 @@ impl Access for Expr { } impl Access for Ident { - fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { vm.scopes.get_mut(self).at(self.span()) } } impl Access for FieldAccess { - fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { Ok(match self.object().access(vm)? { Value::Dict(dict) => dict.get_mut(self.field().take().into()), v => bail!( @@ -1027,7 +1023,7 @@ impl Access for FieldAccess { } impl Access for FuncCall { - fn access<'a>(&self, vm: &'a mut Vm) -> TypResult<&'a mut Value> { + fn access<'a>(&self, vm: &'a mut Vm) -> SourceResult<&'a mut Value> { let args = self.args().eval(vm)?; Ok(match self.callee().access(vm)? { Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?, diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 1b11e6ea5..1ab7032c6 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use super::{Args, Func, Node, Value, Vm}; -use crate::diag::{StrResult, TypResult}; +use crate::diag::{SourceResult, StrResult}; use crate::util::EcoString; /// A stack of scopes. @@ -78,7 +78,7 @@ impl Scope { pub fn def_fn( &mut self, name: &'static str, - func: fn(&mut Vm, &mut Args) -> TypResult, + func: fn(&mut Vm, &mut Args) -> SourceResult, ) { self.define(name, Func::from_fn(name, func)); } diff --git a/src/eval/vm.rs b/src/eval/vm.rs index 937152cfe..7c8e8e31a 100644 --- a/src/eval/vm.rs +++ b/src/eval/vm.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use super::{Scopes, Value}; -use crate::diag::{StrResult, TypError}; +use crate::diag::{SourceError, StrResult}; use crate::source::SourceId; use crate::syntax::Span; use crate::util::PathExt; @@ -56,7 +56,7 @@ pub enum Flow { impl Flow { /// Return an error stating that this control flow is forbidden. - pub fn forbidden(&self) -> TypError { + pub fn forbidden(&self) -> SourceError { match *self { Self::Break(span) => { error!(span, "cannot break outside of loop") diff --git a/src/export/pdf/image.rs b/src/export/pdf/image.rs index f148bb7d0..e507a6130 100644 --- a/src/export/pdf/image.rs +++ b/src/export/pdf/image.rs @@ -4,7 +4,7 @@ use image::{DynamicImage, GenericImageView, ImageResult, Rgba}; use pdf_writer::{Filter, Finish}; use super::{deflate, PdfContext, RefExt}; -use crate::image::{DecodedImage, ImageFormat}; +use crate::image::{DecodedImage, RasterFormat}; /// Embed all used images into the PDF. pub fn write_images(ctx: &mut PdfContext) { @@ -18,11 +18,9 @@ pub fn write_images(ctx: &mut PdfContext) { // Add the primary image. // TODO: Error if image could not be encoded. match image.decode().unwrap() { - DecodedImage::Raster(dynamic) => { + DecodedImage::Raster(dynamic, format) => { // TODO: Error if image could not be encoded. - let (data, filter, has_color) = - encode_image(image.format(), &dynamic).unwrap(); - + let (data, filter, has_color) = encode_image(format, &dynamic).unwrap(); let mut image = ctx.writer.image_xobject(image_ref, &data); image.filter(filter); image.width(width as i32); @@ -70,19 +68,19 @@ pub fn write_images(ctx: &mut PdfContext) { /// /// Skips the alpha channel as that's encoded separately. fn encode_image( - format: ImageFormat, + format: RasterFormat, dynamic: &DynamicImage, ) -> ImageResult<(Vec, Filter, bool)> { Ok(match (format, dynamic) { // 8-bit gray JPEG. - (ImageFormat::Jpg, DynamicImage::ImageLuma8(_)) => { + (RasterFormat::Jpg, DynamicImage::ImageLuma8(_)) => { let mut data = Cursor::new(vec![]); dynamic.write_to(&mut data, image::ImageFormat::Jpeg)?; (data.into_inner(), Filter::DctDecode, false) } // 8-bit RGB JPEG (CMYK JPEGs get converted to RGB earlier). - (ImageFormat::Jpg, DynamicImage::ImageRgb8(_)) => { + (RasterFormat::Jpg, DynamicImage::ImageRgb8(_)) => { let mut data = Cursor::new(vec![]); dynamic.write_to(&mut data, image::ImageFormat::Jpeg)?; (data.into_inner(), Filter::DctDecode, true) @@ -91,7 +89,7 @@ fn encode_image( // TODO: Encode flate streams with PNG-predictor? // 8-bit gray PNG. - (ImageFormat::Png, DynamicImage::ImageLuma8(luma)) => { + (RasterFormat::Png, DynamicImage::ImageLuma8(luma)) => { let data = deflate(luma.as_raw()); (data, Filter::FlateDecode, false) } diff --git a/src/export/render.rs b/src/export/render.rs index 3fb57ad69..e8702f34f 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -188,10 +188,7 @@ fn render_bitmap_glyph( let size = text.size.to_f32(); let ppem = size * ts.sy; let raster = text.font.ttf().glyph_raster_image(id, ppem as u16)?; - let ext = match raster.format { - ttf_parser::RasterImageFormat::PNG => "png", - }; - let image = Image::new(raster.data.into(), ext).ok()?; + let image = Image::new(raster.data.into(), raster.format.into()).ok()?; // FIXME: Vertical alignment isn't quite right for Apple Color Emoji, // and maybe also for Noto Color Emoji. And: Is the size calculation @@ -342,7 +339,7 @@ fn render_image( let mut pixmap = sk::Pixmap::new(w, h)?; match image.decode().unwrap() { - DecodedImage::Raster(dynamic) => { + DecodedImage::Raster(dynamic, _) => { let downscale = w < image.width(); let filter = if downscale { FilterType::Lanczos3 diff --git a/src/image.rs b/src/image.rs index db10a1627..717b1a938 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,6 +2,7 @@ use std::io; +use crate::diag::StrResult; use crate::util::Buffer; /// A raster or vector image. @@ -19,61 +20,26 @@ pub struct Image { height: u32, } -/// A decoded image. -pub enum DecodedImage { - /// A pixel raster format, like PNG or JPEG. - Raster(image::DynamicImage), - /// An SVG vector graphic. - Svg(usvg::Tree), -} - -/// A raster or vector image format. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum ImageFormat { - /// Raster format for illustrations and transparent graphics. - Png, - /// Lossy raster format suitable for photos. - Jpg, - /// Raster format that is typically used for short animated clips. - Gif, - /// The vector graphics format of the web. - Svg, -} - impl Image { - /// Create an image from a raw buffer and a file extension. + /// Create an image from a buffer and a format. /// - /// The file extension is used to determine the format. - pub fn new(data: Buffer, ext: &str) -> io::Result { - let format = match ext { - "svg" | "svgz" => ImageFormat::Svg, - "png" => ImageFormat::Png, - "jpg" | "jpeg" => ImageFormat::Jpg, - "gif" => ImageFormat::Gif, - _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "unknown image format", - )); - } - }; - + /// Extracts the width and height. + pub fn new(data: Buffer, format: ImageFormat) -> StrResult { let (width, height) = match format { - ImageFormat::Svg => { + ImageFormat::Vector(VectorFormat::Svg) => { let opts = usvg::Options::default(); - let tree = - usvg::Tree::from_data(&data, &opts.to_ref()).map_err(invalid)?; + let tree = usvg::Tree::from_data(&data, &opts.to_ref()) + .map_err(format_usvg_error)?; let size = tree.svg_node().size; let width = size.width().ceil() as u32; let height = size.height().ceil() as u32; (width, height) } - _ => { + ImageFormat::Raster(format) => { let cursor = io::Cursor::new(&data); - let format = convert_format(format); - let reader = image::io::Reader::with_format(cursor, format); - reader.into_dimensions().map_err(invalid)? + let reader = image::io::Reader::with_format(cursor, format.into()); + reader.into_dimensions().map_err(format_image_error)? } }; @@ -101,39 +67,125 @@ impl Image { } /// Decode the image. - pub fn decode(&self) -> io::Result { + pub fn decode(&self) -> StrResult { Ok(match self.format { - ImageFormat::Svg => { + ImageFormat::Vector(VectorFormat::Svg) => { let opts = usvg::Options::default(); - let tree = - usvg::Tree::from_data(&self.data, &opts.to_ref()).map_err(invalid)?; + let tree = usvg::Tree::from_data(&self.data, &opts.to_ref()) + .map_err(format_usvg_error)?; DecodedImage::Svg(tree) } - _ => { + ImageFormat::Raster(format) => { let cursor = io::Cursor::new(&self.data); - let format = convert_format(self.format); - let reader = image::io::Reader::with_format(cursor, format); - let dynamic = reader.decode().map_err(invalid)?; - DecodedImage::Raster(dynamic) + let reader = image::io::Reader::with_format(cursor, format.into()); + let dynamic = reader.decode().map_err(format_image_error)?; + DecodedImage::Raster(dynamic, format) } }) } } -/// Convert a raster image format to the image crate's format. -fn convert_format(format: ImageFormat) -> image::ImageFormat { - match format { - ImageFormat::Png => image::ImageFormat::Png, - ImageFormat::Jpg => image::ImageFormat::Jpeg, - ImageFormat::Gif => image::ImageFormat::Gif, - ImageFormat::Svg => panic!("must be a raster format"), +/// A raster or vector image format. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum ImageFormat { + /// A raster graphics format. + Raster(RasterFormat), + /// A vector graphics format. + Vector(VectorFormat), +} + +/// A raster graphics format. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum RasterFormat { + /// Raster format for illustrations and transparent graphics. + Png, + /// Lossy raster format suitable for photos. + Jpg, + /// Raster format that is typically used for short animated clips. + Gif, +} + +/// A vector graphics format. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum VectorFormat { + /// The vector graphics format of the web. + Svg, +} + +impl From for image::ImageFormat { + fn from(format: RasterFormat) -> Self { + match format { + RasterFormat::Png => image::ImageFormat::Png, + RasterFormat::Jpg => image::ImageFormat::Jpeg, + RasterFormat::Gif => image::ImageFormat::Gif, + } } } -/// Turn any error into an I/O error. -fn invalid(error: E) -> io::Error -where - E: std::error::Error + Send + Sync + 'static, -{ - io::Error::new(io::ErrorKind::InvalidData, error) +impl From for RasterFormat { + fn from(format: ttf_parser::RasterImageFormat) -> Self { + match format { + ttf_parser::RasterImageFormat::PNG => RasterFormat::Png, + } + } +} + +impl From for ImageFormat { + fn from(format: ttf_parser::RasterImageFormat) -> Self { + Self::Raster(format.into()) + } +} + +/// A decoded image. +pub enum DecodedImage { + /// A decoded pixel raster. + Raster(image::DynamicImage, RasterFormat), + /// An decoded SVG tree. + Svg(usvg::Tree), +} + +/// Format the user-facing raster graphic decoding error message. +fn format_image_error(error: image::ImageError) -> String { + match error { + image::ImageError::Limits(_) => "file is too large".into(), + _ => "failed to decode image".into(), + } +} + +/// Format the user-facing SVG decoding error message. +fn format_usvg_error(error: usvg::Error) -> String { + match error { + usvg::Error::NotAnUtf8Str => "file is not valid utf-8".into(), + usvg::Error::MalformedGZip => "file is not compressed correctly".into(), + usvg::Error::ElementsLimitReached => "file is too large".into(), + usvg::Error::InvalidSize => { + "failed to parse svg: width, height, or viewbox is invalid".into() + } + usvg::Error::ParsingFailed(error) => match error { + roxmltree::Error::UnexpectedCloseTag { expected, actual, pos } => { + format!( + "failed to parse svg: found closing tag '{actual}' \ + instead of '{expected}' in line {}", + pos.row + ) + } + roxmltree::Error::UnknownEntityReference(entity, pos) => { + format!( + "failed to parse svg: unknown entity '{entity}' in line {}", + pos.row + ) + } + roxmltree::Error::DuplicatedAttribute(attr, pos) => { + format!( + "failed to parse svg: duplicate attribute '{attr}' in line {}", + pos.row + ) + } + roxmltree::Error::NoRootNode => { + "failed to parse svg: missing root node".into() + } + roxmltree::Error::SizeLimit => "file is too large".into(), + _ => "failed to parse svg".into(), + }, + } } diff --git a/src/lib.rs b/src/lib.rs index 84d91d445..6122ffeda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,10 +49,9 @@ pub mod parse; pub mod source; pub mod syntax; -use std::io; use std::path::{Path, PathBuf}; -use crate::diag::TypResult; +use crate::diag::{FileResult, SourceResult}; use crate::eval::Scope; use crate::font::{Font, FontBook}; use crate::frame::Frame; @@ -65,7 +64,7 @@ use crate::util::Buffer; /// 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(world: &dyn World, main: SourceId) -> TypResult> { +pub fn typeset(world: &dyn World, main: SourceId) -> SourceResult> { let module = eval::evaluate(world, main, vec![])?; model::layout(world, &module.content) } @@ -75,8 +74,8 @@ pub trait World { /// Access the global configuration. fn config(&self) -> &Config; - /// Resolve the unique id of a source file. - fn resolve(&self, path: &Path) -> io::Result; + /// Try to resolve the unique id of a source file. + fn resolve(&self, path: &Path) -> FileResult; /// Access a source file by id. fn source(&self, id: SourceId) -> &Source; @@ -84,11 +83,11 @@ pub trait World { /// Metadata about all known fonts. fn book(&self) -> &FontBook; - /// Access the font with the given id. - fn font(&self, id: usize) -> io::Result; + /// Try to access the font with the given id. + fn font(&self, id: usize) -> Option; - /// Access a file at a path. - fn file(&self, path: &Path) -> io::Result; + /// Try to access a file at a path. + fn file(&self, path: &Path) -> FileResult; } /// The global configuration for typesetting. diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index f2a423ce0..505dd1f67 100644 --- a/src/library/graphics/hide.rs +++ b/src/library/graphics/hide.rs @@ -6,7 +6,7 @@ pub struct HideNode(pub LayoutNode); #[node] impl HideNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::inline(Self(args.expect("body")?))) } } @@ -17,7 +17,7 @@ impl Layout for HideNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut frames = self.0.layout(world, regions, styles)?; for frame in &mut frames { frame.clear(); diff --git a/src/library/graphics/image.rs b/src/library/graphics/image.rs index 1642c7b0b..c0249b3cb 100644 --- a/src/library/graphics/image.rs +++ b/src/library/graphics/image.rs @@ -1,6 +1,6 @@ use std::ffi::OsStr; -use crate::image::Image; +use crate::image::{Image, ImageFormat, RasterFormat, VectorFormat}; use crate::library::prelude::*; use crate::library::text::TextNode; @@ -13,19 +13,22 @@ impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; - fn construct(vm: &mut Vm, args: &mut Args) -> TypResult { + fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = args.expect::>("path to image file")?; let full = vm.locate(&path).at(span)?; + let buffer = vm.world.file(&full).at(span)?; let ext = full.extension().and_then(OsStr::to_str).unwrap_or_default(); - let image = vm - .world - .file(&full) - .and_then(|buffer| Image::new(buffer, ext)) - .map_err(|err| failed_to_load("image", &full, err)) - .at(span)?; + let format = match ext.to_lowercase().as_str() { + "png" => ImageFormat::Raster(RasterFormat::Png), + "jpg" | "jpeg" => ImageFormat::Raster(RasterFormat::Jpg), + "gif" => ImageFormat::Raster(RasterFormat::Gif), + "svg" | "svgz" => ImageFormat::Vector(VectorFormat::Svg), + _ => bail!(span, "unknown image format"), + }; + let image = Image::new(buffer, format).at(span)?; let width = args.named("width")?; let height = args.named("height")?; @@ -41,7 +44,7 @@ impl Layout for ImageNode { _: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let pxw = self.0.width() as f64; let pxh = self.0.height() as f64; let px_ratio = pxw / pxh; diff --git a/src/library/graphics/line.rs b/src/library/graphics/line.rs index 95c3a7099..ebfec1b29 100644 --- a/src/library/graphics/line.rs +++ b/src/library/graphics/line.rs @@ -15,7 +15,7 @@ impl LineNode { #[property(resolve, fold)] pub const STROKE: RawStroke = RawStroke::default(); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let origin = args.named("origin")?.unwrap_or_default(); let delta = match args.named::>>("to")? { @@ -43,7 +43,7 @@ impl Layout for LineNode { _: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let stroke = styles.get(Self::STROKE).unwrap_or_default(); let origin = self diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index ee5e43e8e..d91625572 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -39,7 +39,7 @@ impl ShapeNode { pub const RADIUS: Corners>> = Corners::splat(Relative::zero()); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let size = match S { SQUARE => args.named::("size")?.map(Relative::from), CIRCLE => args.named::("radius")?.map(|r| 2.0 * Relative::from(r)), @@ -81,7 +81,7 @@ impl Layout for ShapeNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut frames; if let Some(child) = &self.0 { let mut inset = styles.get(Self::INSET); diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs index 0a4f5b5f9..34d45bd0c 100644 --- a/src/library/graphics/transform.rs +++ b/src/library/graphics/transform.rs @@ -12,7 +12,7 @@ pub struct MoveNode { #[node] impl MoveNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); Ok(Content::inline(Self { @@ -28,7 +28,7 @@ impl Layout for MoveNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut frames = self.child.layout(world, regions, styles)?; let delta = self.delta.resolve(styles); @@ -62,7 +62,7 @@ impl TransformNode { #[property(resolve)] pub const ORIGIN: Spec> = Spec::default(); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let transform = match T { ROTATE => { let angle = args.named_or_find("angle")?.unwrap_or_default(); @@ -89,7 +89,7 @@ impl Layout for TransformNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); let mut frames = self.child.layout(world, regions, styles)?; diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index 3b1a4aaf8..0c758cf2a 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -12,7 +12,7 @@ pub struct AlignNode { #[node] impl AlignNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let aligns: Spec> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; Ok(match (body, aligns) { @@ -31,7 +31,7 @@ impl Layout for AlignNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // The child only needs to expand along an axis if there's no alignment. let mut pod = regions.clone(); pod.expand &= self.aligns.map_is_none(); diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index bfbbfd8d9..e0163f633 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -17,7 +17,7 @@ impl ColumnsNode { #[property(resolve)] pub const GUTTER: Relative = Ratio::new(0.04).into(); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::block(Self { columns: args.expect("column count")?, child: args.expect("body")?, @@ -31,7 +31,7 @@ impl Layout for ColumnsNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // Separating the infinite space into infinite columns does not make // much sense. if !regions.first.x.is_finite() { @@ -106,7 +106,7 @@ pub struct ColbreakNode; #[node] impl ColbreakNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Colbreak { weak }) } diff --git a/src/library/layout/container.rs b/src/library/layout/container.rs index 66a437517..23556a2e0 100644 --- a/src/library/layout/container.rs +++ b/src/library/layout/container.rs @@ -5,7 +5,7 @@ pub struct BoxNode; #[node] impl BoxNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let width = args.named("width")?; let height = args.named("height")?; let body: LayoutNode = args.eat()?.unwrap_or_default(); @@ -18,7 +18,7 @@ pub struct BlockNode; #[node] impl BlockNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::Block(args.eat()?.unwrap_or_default())) } } diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index 841b80aae..05c10789c 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -28,7 +28,7 @@ impl Layout for FlowNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut layouter = FlowLayouter::new(regions); for (child, map) in self.0.iter() { @@ -152,7 +152,7 @@ impl FlowLayouter { world: &dyn World, node: &LayoutNode, styles: StyleChain, - ) -> TypResult<()> { + ) -> SourceResult<()> { // Don't even try layouting into a full region. if self.regions.is_full() { self.finish_region(); diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 3fde9c10c..cd4fc6b4c 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -13,7 +13,7 @@ pub struct GridNode { #[node] impl GridNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let columns = args.named("columns")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default(); let base_gutter: Vec = args.named("gutter")?.unwrap_or_default(); @@ -36,7 +36,7 @@ impl Layout for GridNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( world, @@ -203,7 +203,7 @@ impl<'a> GridLayouter<'a> { } /// Determines the columns sizes and then layouts the grid row-by-row. - pub fn layout(mut self) -> TypResult> { + pub fn layout(mut self) -> SourceResult> { self.measure_columns()?; for y in 0 .. self.rows.len() { @@ -228,7 +228,7 @@ impl<'a> GridLayouter<'a> { } /// Determine all column sizes. - fn measure_columns(&mut self) -> TypResult<()> { + fn measure_columns(&mut self) -> SourceResult<()> { // Sum of sizes of resolved relative tracks. let mut rel = Length::zero(); @@ -275,7 +275,10 @@ impl<'a> GridLayouter<'a> { } /// Measure the size that is available to auto columns. - fn measure_auto_columns(&mut self, available: Length) -> TypResult<(Length, usize)> { + fn measure_auto_columns( + &mut self, + available: Length, + ) -> SourceResult<(Length, usize)> { let mut auto = Length::zero(); let mut count = 0; @@ -355,7 +358,7 @@ impl<'a> GridLayouter<'a> { /// Layout a row with automatic height. Such a row may break across multiple /// regions. - fn layout_auto_row(&mut self, y: usize) -> TypResult<()> { + fn layout_auto_row(&mut self, y: usize) -> SourceResult<()> { let mut resolved: Vec = vec![]; // Determine the size for each region of the row. @@ -423,7 +426,11 @@ impl<'a> GridLayouter<'a> { /// Layout a row with relative height. Such a row cannot break across /// multiple regions, but it may force a region break. - fn layout_relative_row(&mut self, v: Relative, y: usize) -> TypResult<()> { + fn layout_relative_row( + &mut self, + v: Relative, + y: usize, + ) -> SourceResult<()> { let resolved = v.resolve(self.styles).relative_to(self.regions.base.y); let frame = self.layout_single_row(resolved, y)?; @@ -444,7 +451,7 @@ impl<'a> GridLayouter<'a> { } /// Layout a row with fixed height and return its frame. - fn layout_single_row(&mut self, height: Length, y: usize) -> TypResult { + fn layout_single_row(&mut self, height: Length, y: usize) -> SourceResult { let mut output = Frame::new(Size::new(self.used.x, height)); let mut pos = Point::zero(); @@ -483,7 +490,7 @@ impl<'a> GridLayouter<'a> { &mut self, heights: &[Length], y: usize, - ) -> TypResult> { + ) -> SourceResult> { // Prepare frames. let mut outputs: Vec<_> = heights .iter() @@ -535,7 +542,7 @@ impl<'a> GridLayouter<'a> { } /// Finish rows for one region. - fn finish_region(&mut self) -> TypResult<()> { + fn finish_region(&mut self) -> SourceResult<()> { // Determine the size of the grid in this region, expanding fully if // there are fr rows. let mut size = self.used; diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index 72235ccd2..983bfa111 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -11,7 +11,7 @@ pub struct PadNode { #[node] impl PadNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let all = args.named("rest")?.or(args.find()?); let x = args.named("x")?; let y = args.named("y")?; @@ -31,7 +31,7 @@ impl Layout for PadNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // Layout child into padded regions. let padding = self.padding.resolve(styles); let pod = regions.map(|size| shrink(size, padding)); diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index 6e43c4efc..ba5972630 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -41,7 +41,7 @@ impl PageNode { #[property(referenced)] pub const FOREGROUND: Marginal = Marginal::None; - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::Page(Self(args.expect("body")?))) } @@ -60,7 +60,7 @@ impl PageNode { world: &dyn World, mut page: usize, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // When one of the lengths is infinite the page fits its content along // that axis. let width = styles.get(Self::WIDTH).unwrap_or(Length::inf()); @@ -159,7 +159,7 @@ pub struct PagebreakNode; #[node] impl PagebreakNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Pagebreak { weak }) } @@ -178,7 +178,11 @@ pub enum Marginal { impl Marginal { /// Resolve the marginal based on the page number. - pub fn resolve(&self, world: &dyn World, page: usize) -> TypResult> { + pub fn resolve( + &self, + world: &dyn World, + page: usize, + ) -> SourceResult> { Ok(match self { Self::None => None, Self::Content(content) => Some(content.clone()), diff --git a/src/library/layout/place.rs b/src/library/layout/place.rs index bb3aac2dd..862c969e0 100644 --- a/src/library/layout/place.rs +++ b/src/library/layout/place.rs @@ -7,7 +7,7 @@ pub struct PlaceNode(pub LayoutNode); #[node] impl PlaceNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start))); let dx = args.named("dx")?.unwrap_or_default(); let dy = args.named("dy")?.unwrap_or_default(); @@ -24,7 +24,7 @@ impl Layout for PlaceNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let out_of_flow = self.out_of_flow(); // The pod is the base area of the region because for absolute diff --git a/src/library/layout/spacing.rs b/src/library/layout/spacing.rs index e435e60cf..0c5cbb92d 100644 --- a/src/library/layout/spacing.rs +++ b/src/library/layout/spacing.rs @@ -8,7 +8,7 @@ pub struct HNode; #[node] impl HNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Horizontal { amount, weak }) @@ -20,7 +20,7 @@ pub struct VNode; #[node] impl VNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let amount = args.expect("spacing")?; let weak = args.named("weak")?.unwrap_or(false); Ok(Content::Vertical { amount, weak, generated: false }) diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index d07dc35e0..a9fc16212 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -15,7 +15,7 @@ pub struct StackNode { #[node] impl StackNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::block(Self { dir: args.named("dir")?.unwrap_or(Dir::TTB), spacing: args.named("spacing")?, @@ -30,7 +30,7 @@ impl Layout for StackNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut layouter = StackLayouter::new(self.dir, regions, styles); // Spacing to insert before the next node. @@ -171,7 +171,7 @@ impl<'a> StackLayouter<'a> { world: &dyn World, node: &LayoutNode, styles: StyleChain, - ) -> TypResult<()> { + ) -> SourceResult<()> { if self.regions.is_full() { self.finish_region(); } diff --git a/src/library/math/mod.rs b/src/library/math/mod.rs index ed98ab1cc..d71f69764 100644 --- a/src/library/math/mod.rs +++ b/src/library/math/mod.rs @@ -28,7 +28,7 @@ impl MathNode { #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self { formula: args.expect("formula")?, display: args.named("display")?.unwrap_or(false), @@ -48,7 +48,7 @@ impl Show for MathNode { } } - fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, styles: StyleChain) -> SourceResult { let node = self::rex::RexNode { tex: self.formula.clone(), display: self.display, @@ -67,7 +67,7 @@ impl Show for MathNode { _: &dyn World, styles: StyleChain, mut realized: Content, - ) -> TypResult { + ) -> SourceResult { let mut map = StyleMap::new(); map.set_family(styles.get(Self::FAMILY).clone(), styles); diff --git a/src/library/math/rex.rs b/src/library/math/rex.rs index 7bfdeb7a2..76ba5177b 100644 --- a/src/library/math/rex.rs +++ b/src/library/math/rex.rs @@ -25,13 +25,13 @@ impl Layout for RexNode { world: &dyn World, _: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // Load the font. let span = self.tex.span; let font = world .book() .select(self.family.as_str(), variant(styles)) - .and_then(|id| world.font(id).ok()) + .and_then(|id| world.font(id)) .ok_or("failed to find math font") .at(span)?; diff --git a/src/library/prelude.rs b/src/library/prelude.rs index a69d9791d..48eebaf60 100644 --- a/src/library/prelude.rs +++ b/src/library/prelude.rs @@ -8,9 +8,7 @@ pub use std::sync::Arc; pub use typst_macros::node; -pub use crate::diag::{ - failed_to_load, with_alternative, At, Error, StrResult, TypError, TypResult, -}; +pub use crate::diag::{with_alternative, At, SourceError, SourceResult, StrResult}; pub use crate::eval::{ Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke, Scope, Smart, Value, Vm, diff --git a/src/library/structure/doc.rs b/src/library/structure/doc.rs index cabdb4dc7..ba848b64c 100644 --- a/src/library/structure/doc.rs +++ b/src/library/structure/doc.rs @@ -7,7 +7,11 @@ pub struct DocNode(pub StyleVec); impl DocNode { /// Layout the document into a sequence of frames, one per page. - pub fn layout(&self, world: &dyn World, styles: StyleChain) -> TypResult> { + pub fn layout( + &self, + world: &dyn World, + styles: StyleChain, + ) -> SourceResult> { let mut frames = vec![]; for (page, map) in self.0.iter() { let number = 1 + frames.len(); diff --git a/src/library/structure/heading.rs b/src/library/structure/heading.rs index c177481f9..855c0503c 100644 --- a/src/library/structure/heading.rs +++ b/src/library/structure/heading.rs @@ -60,7 +60,7 @@ impl HeadingNode { /// Whether the heading is numbered. pub const NUMBERED: bool = true; - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self { body: args.expect("body")?, level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), @@ -82,7 +82,7 @@ impl Show for HeadingNode { } } - fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult { Ok(Content::block(self.body.clone())) } @@ -91,7 +91,7 @@ impl Show for HeadingNode { world: &dyn World, styles: StyleChain, mut realized: Content, - ) -> TypResult { + ) -> SourceResult { macro_rules! resolve { ($key:expr) => { styles.get($key).resolve(world, self.level)? @@ -149,7 +149,7 @@ pub enum Leveled { impl Leveled { /// Resolve the value based on the level. - pub fn resolve(&self, world: &dyn World, level: NonZeroUsize) -> TypResult { + pub fn resolve(&self, world: &dyn World, level: NonZeroUsize) -> SourceResult { Ok(match self { Self::Value(value) => value.clone(), Self::Mapping(mapping) => mapping(level), diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs index e9365cd6e..f63374f3e 100644 --- a/src/library/structure/list.rs +++ b/src/library/structure/list.rs @@ -56,7 +56,7 @@ impl ListNode { #[property(resolve)] pub const SPACING: BlockSpacing = Ratio::one().into(); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self { start: args.named("start")?.unwrap_or(1), tight: args.named("tight")?.unwrap_or(true), @@ -100,7 +100,7 @@ impl Show for ListNode { } } - fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult { let mut cells = vec![]; let mut number = self.start; @@ -148,7 +148,7 @@ impl Show for ListNode { _: &dyn World, styles: StyleChain, realized: Content, - ) -> TypResult { + ) -> SourceResult { let mut above = styles.get(Self::ABOVE); let mut below = styles.get(Self::BELOW); @@ -211,7 +211,7 @@ impl Label { world: &dyn World, kind: ListKind, number: usize, - ) -> TypResult { + ) -> SourceResult { Ok(match self { Self::Default => match kind { UNORDERED => Content::Text('•'.into()), diff --git a/src/library/structure/reference.rs b/src/library/structure/reference.rs index 22dbec016..5d1dab38d 100644 --- a/src/library/structure/reference.rs +++ b/src/library/structure/reference.rs @@ -6,7 +6,7 @@ pub struct RefNode(pub EcoString); #[node(showable)] impl RefNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self(args.expect("label")?))) } } @@ -22,7 +22,7 @@ impl Show for RefNode { } } - fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult { Ok(Content::Text(format_eco!("@{}", self.0))) } } diff --git a/src/library/structure/table.rs b/src/library/structure/table.rs index 08fa53864..f1ca7e039 100644 --- a/src/library/structure/table.rs +++ b/src/library/structure/table.rs @@ -30,7 +30,7 @@ impl TableNode { #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let columns = args.named("columns")?.unwrap_or_default(); let rows = args.named("rows")?.unwrap_or_default(); let base_gutter: Vec = args.named("gutter")?.unwrap_or_default(); @@ -72,7 +72,7 @@ impl Show for TableNode { } } - fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult { let fill = styles.get(Self::FILL); let stroke = styles.get(Self::STROKE).map(RawStroke::unwrap_or_default); let padding = styles.get(Self::PADDING); @@ -98,7 +98,7 @@ impl Show for TableNode { Ok(child) }) - .collect::>()?; + .collect::>()?; Ok(Content::block(GridNode { tracks: self.tracks.clone(), @@ -113,7 +113,7 @@ impl Show for TableNode { _: &dyn World, styles: StyleChain, realized: Content, - ) -> TypResult { + ) -> SourceResult { Ok(realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))) } } @@ -129,7 +129,7 @@ pub enum Celled { impl Celled { /// Resolve the value based on the cell position. - pub fn resolve(&self, world: &dyn World, x: usize, y: usize) -> TypResult { + pub fn resolve(&self, world: &dyn World, x: usize, y: usize) -> SourceResult { Ok(match self { Self::Value(value) => value.clone(), Self::Func(func, span) => { diff --git a/src/library/text/deco.rs b/src/library/text/deco.rs index c58148b4c..3d030d457 100644 --- a/src/library/text/deco.rs +++ b/src/library/text/deco.rs @@ -34,7 +34,7 @@ impl DecoNode { /// with the glyphs. Does not apply to strikethrough. pub const EVADE: bool = true; - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -48,7 +48,7 @@ impl Show for DecoNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, styles: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::DECO, Decoration { line: L, stroke: styles.get(Self::STROKE).unwrap_or_default(), diff --git a/src/library/text/link.rs b/src/library/text/link.rs index 7d5c31094..f89bbd674 100644 --- a/src/library/text/link.rs +++ b/src/library/text/link.rs @@ -18,7 +18,7 @@ impl LinkNode { /// Whether to underline the link. pub const UNDERLINE: Smart = Smart::Auto; - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show({ let dest = args.expect::("destination")?; let body = match dest { @@ -64,7 +64,7 @@ impl Show for LinkNode { } } - fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult { Ok(self.body.clone().unwrap_or_else(|| match &self.dest { Destination::Url(url) => { let mut text = url.as_str(); @@ -83,7 +83,7 @@ impl Show for LinkNode { _: &dyn World, styles: StyleChain, mut realized: Content, - ) -> TypResult { + ) -> SourceResult { let mut map = StyleMap::new(); map.set(TextNode::LINK, Some(self.dest.clone())); diff --git a/src/library/text/mod.rs b/src/library/text/mod.rs index be586874e..55b866cb6 100644 --- a/src/library/text/mod.rs +++ b/src/library/text/mod.rs @@ -128,7 +128,7 @@ impl TextNode { #[property(skip, fold)] pub const DECO: Decoration = vec![]; - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { // The text constructor is special: It doesn't create a text node. // Instead, it leaves the passed argument structurally unchanged, but // styles all text in it. @@ -422,17 +422,17 @@ impl Fold for Vec<(Tag, u32)> { } /// Convert a string or content to lowercase. -pub fn lower(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn lower(_: &mut Vm, args: &mut Args) -> SourceResult { case(Case::Lower, args) } /// Convert a string or content to uppercase. -pub fn upper(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn upper(_: &mut Vm, args: &mut Args) -> SourceResult { case(Case::Upper, args) } /// Change the case of text. -fn case(case: Case, args: &mut Args) -> TypResult { +fn case(case: Case, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("string or content")?; Ok(match v { Value::Str(v) => Value::Str(case.apply(&v).into()), @@ -461,7 +461,7 @@ impl Case { } /// Display text in small capitals. -pub fn smallcaps(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult { let body: Content = args.expect("content")?; Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } @@ -493,7 +493,7 @@ pub struct StrongNode(pub Content); #[node(showable)] impl StrongNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -507,7 +507,7 @@ impl Show for StrongNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::BOLD, Toggle)) } } @@ -518,7 +518,7 @@ pub struct EmphNode(pub Content); #[node(showable)] impl EmphNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -532,7 +532,7 @@ impl Show for EmphNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, _: &dyn World, _: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, _: StyleChain) -> SourceResult { Ok(self.0.clone().styled(TextNode::ITALIC, Toggle)) } } diff --git a/src/library/text/par.rs b/src/library/text/par.rs index e8282ef1a..00a1e0346 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -49,7 +49,7 @@ impl ParNode { #[property(resolve)] pub const LINEBREAKS: Smart = Smart::Auto; - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { // The paragraph constructor is special: It doesn't create a paragraph // node. Instead, it just ensures that the passed content lives is in a // separate paragraph and styles it. @@ -67,7 +67,7 @@ impl Layout for ParNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // Collect all text into one string for BiDi analysis. let (text, segments) = collect(self, &styles); @@ -170,7 +170,7 @@ pub struct ParbreakNode; #[node] impl ParbreakNode { - fn construct(_: &mut Vm, _: &mut Args) -> TypResult { + fn construct(_: &mut Vm, _: &mut Args) -> SourceResult { Ok(Content::Parbreak) } } @@ -180,7 +180,7 @@ pub struct LinebreakNode; #[node] impl LinebreakNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { let justified = args.named("justified")?.unwrap_or(false); Ok(Content::Linebreak { justified }) } @@ -502,7 +502,7 @@ fn prepare<'a>( segments: Vec<(Segment<'a>, StyleChain<'a>)>, regions: &Regions, styles: StyleChain<'a>, -) -> TypResult> { +) -> SourceResult> { let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) { Dir::LTR => Some(Level::ltr()), Dir::RTL => Some(Level::rtl()), @@ -1025,7 +1025,7 @@ fn stack( world: &dyn World, lines: &[Line], regions: &Regions, -) -> TypResult> { +) -> SourceResult> { // Determine the paragraph's width: Full width of the region if we // should expand or there's fractional spacing, fit-to-width otherwise. let mut width = regions.first.x; @@ -1076,7 +1076,7 @@ fn commit( line: &Line, regions: &Regions, width: Length, -) -> TypResult { +) -> SourceResult { let mut remaining = width - line.width; let mut offset = Length::zero(); diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index c729fa40d..5bce2a90f 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -35,7 +35,7 @@ impl RawNode { #[property(resolve, shorthand(around))] pub const BELOW: Option = Some(Ratio::one().into()); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self { text: args.expect("text")?, block: args.named("block")?.unwrap_or(false), @@ -59,7 +59,7 @@ impl Show for RawNode { } } - fn realize(&self, _: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, _: &dyn World, styles: StyleChain) -> SourceResult { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings @@ -114,7 +114,7 @@ impl Show for RawNode { _: &dyn World, styles: StyleChain, mut realized: Content, - ) -> TypResult { + ) -> SourceResult { let mut map = StyleMap::new(); map.set_family(styles.get(Self::FAMILY).clone(), styles); map.set(TextNode::OVERHANG, false); diff --git a/src/library/text/repeat.rs b/src/library/text/repeat.rs index c2a0de700..78a210694 100644 --- a/src/library/text/repeat.rs +++ b/src/library/text/repeat.rs @@ -6,7 +6,7 @@ pub struct RepeatNode(pub LayoutNode); #[node] impl RepeatNode { - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::inline(Self(args.expect("body")?))) } } @@ -17,7 +17,7 @@ impl Layout for RepeatNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // The actual repeating happens directly in the paragraph. self.0.layout(world, regions, styles) } diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index 6e5057024..c1d0341b9 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -165,7 +165,7 @@ impl<'a> ShapedText<'a> { if let Some(font) = world .book() .select(family, self.variant) - .and_then(|id| world.font(id).ok()) + .and_then(|id| world.font(id)) { expand(&font); break; @@ -223,7 +223,7 @@ impl<'a> ShapedText<'a> { let font = world .book() .select(family, self.variant) - .and_then(|id| world.font(id).ok())?; + .and_then(|id| world.font(id))?; let ttf = font.ttf(); let glyph_id = ttf.glyph_index('-')?; let x_advance = font.to_em(ttf.glyph_hor_advance(glyph_id)?); @@ -371,7 +371,7 @@ fn shape_segment<'a>( let book = ctx.world.book(); let mut selection = families.find_map(|family| { book.select(family, ctx.variant) - .and_then(|id| ctx.world.font(id).ok()) + .and_then(|id| ctx.world.font(id)) .filter(|font| !ctx.used.contains(font)) }); @@ -380,7 +380,7 @@ fn shape_segment<'a>( let first = ctx.used.first().map(Font::info); selection = book .select_fallback(first, ctx.variant, text) - .and_then(|id| ctx.world.font(id).ok()) + .and_then(|id| ctx.world.font(id)) .filter(|font| !ctx.used.contains(font)); } diff --git a/src/library/text/shift.rs b/src/library/text/shift.rs index 5da36da1c..b359c5ed0 100644 --- a/src/library/text/shift.rs +++ b/src/library/text/shift.rs @@ -28,7 +28,7 @@ impl ShiftNode { /// The font size for synthetic sub- and superscripts. pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); - fn construct(_: &mut Vm, args: &mut Args) -> TypResult { + fn construct(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Content::show(Self(args.expect("body")?))) } } @@ -42,7 +42,7 @@ impl Show for ShiftNode { dict! { "body" => Value::Content(self.0.clone()) } } - fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult { let mut transformed = None; if styles.get(Self::TYPOGRAPHIC) { if let Some(text) = search_text(&self.0, S) { @@ -96,7 +96,7 @@ fn is_shapable(world: &dyn World, text: &str, styles: StyleChain) -> bool { if let Some(font) = world .book() .select(family.as_str(), variant(styles)) - .and_then(|id| world.font(id).ok()) + .and_then(|id| world.font(id)) { return text.chars().all(|c| font.ttf().glyph_index(c).is_some()); } diff --git a/src/library/utility/color.rs b/src/library/utility/color.rs index a7d55d1c4..a5a5704d3 100644 --- a/src/library/utility/color.rs +++ b/src/library/utility/color.rs @@ -3,13 +3,13 @@ use std::str::FromStr; use crate::library::prelude::*; /// Create a grayscale color. -pub fn luma(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn luma(_: &mut Vm, args: &mut Args) -> SourceResult { let Component(luma) = args.expect("gray component")?; Ok(Value::Color(LumaColor::new(luma).into())) } /// Create an RGB(A) color. -pub fn rgb(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn rgb(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Value::Color( if let Some(string) = args.find::>()? { match RgbaColor::from_str(&string.v) { @@ -27,7 +27,7 @@ pub fn rgb(_: &mut Vm, args: &mut Args) -> TypResult { } /// Create a CMYK color. -pub fn cmyk(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn cmyk(_: &mut Vm, args: &mut Args) -> SourceResult { let RatioComponent(c) = args.expect("cyan component")?; let RatioComponent(m) = args.expect("magenta component")?; let RatioComponent(y) = args.expect("yellow component")?; diff --git a/src/library/utility/data.rs b/src/library/utility/data.rs index 59f3d351f..1ae8949aa 100644 --- a/src/library/utility/data.rs +++ b/src/library/utility/data.rs @@ -1,30 +1,43 @@ +use std::fmt::Write; + use crate::library::prelude::*; /// Read structured data from a CSV file. -pub fn csv(vm: &mut Vm, args: &mut Args) -> TypResult { +pub fn csv(vm: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v: path, span } = args.expect::>("path to csv file")?; let path = vm.locate(&path).at(span)?; - let try_load = || -> io::Result { - let data = vm.world.file(&path)?; + let data = vm.world.file(&path).at(span)?; - let mut builder = csv::ReaderBuilder::new(); - builder.has_headers(false); + let mut builder = csv::ReaderBuilder::new(); + builder.has_headers(false); - let mut reader = builder.from_reader(data.as_slice()); - let mut vec = vec![]; + let mut reader = builder.from_reader(data.as_slice()); + let mut vec = vec![]; - for result in reader.records() { - vec.push(Value::Array( - result?.iter().map(|field| Value::Str(field.into())).collect(), - )) - } + for result in reader.records() { + let row = result.map_err(format_csv_error).at(span)?; + let array = row.iter().map(|field| Value::Str(field.into())).collect(); + vec.push(Value::Array(array)) + } - Ok(Value::Array(Array::from_vec(vec))) - }; - - try_load() - .map_err(|err| failed_to_load("csv file", &path, err)) - .at(span) + Ok(Value::Array(Array::from_vec(vec))) +} + +/// Format the user-facing CSV error message. +fn format_csv_error(error: csv::Error) -> String { + match error.kind() { + csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), + csv::ErrorKind::UnequalLengths { pos, expected_len, len } => { + let mut msg = format!( + "failed to parse csv file: found {len} instead of {expected_len} fields" + ); + if let Some(pos) = pos { + write!(msg, " in line {}", pos.line()).unwrap(); + } + msg + } + _ => "failed to parse csv file".into(), + } } diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs index 47648282b..7c3af490f 100644 --- a/src/library/utility/math.rs +++ b/src/library/utility/math.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use crate::library::prelude::*; /// Convert a value to an integer. -pub fn int(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn int(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Int(match v { Value::Bool(v) => v as i64, @@ -18,7 +18,7 @@ pub fn int(_: &mut Vm, args: &mut Args) -> TypResult { } /// Convert a value to a float. -pub fn float(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn float(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Float(match v { Value::Int(v) => v as f64, @@ -32,7 +32,7 @@ pub fn float(_: &mut Vm, args: &mut Args) -> TypResult { } /// The absolute value of a numeric value. -pub fn abs(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn abs(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("numeric value")?; Ok(match v { Value::Int(v) => Value::Int(v.abs()), @@ -48,17 +48,17 @@ pub fn abs(_: &mut Vm, args: &mut Args) -> TypResult { } /// The minimum of a sequence of values. -pub fn min(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn min(_: &mut Vm, args: &mut Args) -> SourceResult { minmax(args, Ordering::Less) } /// The maximum of a sequence of values. -pub fn max(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn max(_: &mut Vm, args: &mut Args) -> SourceResult { minmax(args, Ordering::Greater) } /// Find the minimum or maximum of a sequence of values. -fn minmax(args: &mut Args, goal: Ordering) -> TypResult { +fn minmax(args: &mut Args, goal: Ordering) -> SourceResult { let mut extremum = args.expect::("value")?; for Spanned { v, span } in args.all::>()? { match v.partial_cmp(&extremum) { @@ -79,17 +79,17 @@ fn minmax(args: &mut Args, goal: Ordering) -> TypResult { } /// Whether an integer is even. -pub fn even(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn even(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Value::Bool(args.expect::("integer")? % 2 == 0)) } /// Whether an integer is odd. -pub fn odd(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn odd(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(Value::Bool(args.expect::("integer")? % 2 != 0)) } /// The modulo of two numbers. -pub fn mod_(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn mod_(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; @@ -119,7 +119,7 @@ pub fn mod_(_: &mut Vm, args: &mut Args) -> TypResult { } /// Create a sequence of numbers. -pub fn range(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn range(_: &mut Vm, args: &mut Args) -> SourceResult { let first = args.expect::("end")?; let (start, end) = match args.eat::()? { Some(second) => (first, second), diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs index 40a107ba9..3fc413f76 100644 --- a/src/library/utility/mod.rs +++ b/src/library/utility/mod.rs @@ -15,12 +15,12 @@ use crate::library::prelude::*; use crate::source::Source; /// The name of a value's type. -pub fn type_(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn type_(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.type_name().into()) } /// Ensure that a condition is fulfilled. -pub fn assert(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn assert(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("condition")?; if !v { bail!(span, "assertion failed"); @@ -29,7 +29,7 @@ pub fn assert(_: &mut Vm, args: &mut Args) -> TypResult { } /// Evaluate a string as Typst markup. -pub fn eval(vm: &mut Vm, args: &mut Args) -> TypResult { +pub fn eval(vm: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v: text, span } = args.expect::>("source")?; // Parse the source and set a synthetic span for all nodes. @@ -44,7 +44,7 @@ pub fn eval(vm: &mut Vm, args: &mut Args) -> TypResult { // Handle control flow. if let Some(flow) = sub.flow { - return Err(flow.forbidden()); + bail!(flow.forbidden()); } Ok(Value::Content(result?)) diff --git a/src/library/utility/string.rs b/src/library/utility/string.rs index d825d84bd..91a990a97 100644 --- a/src/library/utility/string.rs +++ b/src/library/utility/string.rs @@ -2,12 +2,12 @@ use crate::eval::Regex; use crate::library::prelude::*; /// The string representation of a value. -pub fn repr(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn repr(_: &mut Vm, args: &mut Args) -> SourceResult { Ok(args.expect::("value")?.repr().into()) } /// Convert a value to a string. -pub fn str(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn str(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect("value")?; Ok(Value::Str(match v { Value::Int(v) => format_str!("{}", v), @@ -18,33 +18,33 @@ pub fn str(_: &mut Vm, args: &mut Args) -> TypResult { } /// Create blind text. -pub fn lorem(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn lorem(_: &mut Vm, args: &mut Args) -> SourceResult { let words: usize = args.expect("number of words")?; Ok(Value::Str(lipsum::lipsum(words).into())) } /// Create a regular expression. -pub fn regex(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn regex(_: &mut Vm, args: &mut Args) -> SourceResult { let Spanned { v, span } = args.expect::>("regular expression")?; Ok(Regex::new(&v).at(span)?.into()) } /// Converts an integer into one or multiple letters. -pub fn letter(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn letter(_: &mut Vm, args: &mut Args) -> SourceResult { numbered(Numbering::Letter, args) } /// Converts an integer into a roman numeral. -pub fn roman(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn roman(_: &mut Vm, args: &mut Args) -> SourceResult { numbered(Numbering::Roman, args) } /// Convert a number into a symbol. -pub fn symbol(_: &mut Vm, args: &mut Args) -> TypResult { +pub fn symbol(_: &mut Vm, args: &mut Args) -> SourceResult { numbered(Numbering::Symbol, args) } -fn numbered(numbering: Numbering, args: &mut Args) -> TypResult { +fn numbered(numbering: Numbering, args: &mut Args) -> SourceResult { let n = args.expect::("non-negative integer")?; Ok(Value::Str(numbering.apply(n).into())) } diff --git a/src/main.rs b/src/main.rs index e5af0d8b8..9a6e367f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use siphasher::sip128::{Hasher128, SipHasher}; use termcolor::{ColorChoice, StandardStream, WriteColor}; use walkdir::WalkDir; -use typst::diag::{failed_to_load, Error, StrResult}; +use typst::diag::{FileError, FileResult, SourceError, StrResult}; use typst::font::{Font, FontBook, FontInfo, FontVariant}; use typst::library::text::THEME; use typst::parse::TokenMode; @@ -209,9 +209,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { } // Create the world that serves sources, fonts and files. - let id = world - .resolve(&command.input) - .map_err(|err| failed_to_load("source file", &command.input, err))?; + let id = world.resolve(&command.input).map_err(|err| err.to_string())?; // Typeset. match typst::typeset(&world, id) { @@ -234,7 +232,7 @@ fn typeset(command: TypesetCommand) -> StrResult<()> { /// Print diagnostic messages to the terminal. fn print_diagnostics( world: &SystemWorld, - errors: Vec, + errors: Vec, ) -> Result<(), codespan_reporting::files::Error> { let mut w = StandardStream::stderr(ColorChoice::Always); let config = term::Config { tab_width: 2, ..Default::default() }; @@ -328,17 +326,13 @@ impl World for SystemWorld { &self.config } - fn resolve(&self, path: &Path) -> io::Result { + fn resolve(&self, path: &Path) -> FileResult { let hash = PathHash::new(path)?; if let Some(&id) = self.nav.borrow().get(&hash) { return Ok(id); } - let data = fs::read(path)?; - let text = String::from_utf8(data).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") - })?; - + let text = fs::read_to_string(path).map_err(|e| FileError::from_io(e, path))?; let id = SourceId::from_raw(self.sources.len() as u16); let source = Source::new(id, path, text); self.sources.push(Box::new(source)); @@ -355,7 +349,7 @@ impl World for SystemWorld { &self.book } - fn font(&self, id: usize) -> io::Result { + fn font(&self, id: usize) -> Option { let slot = &self.fonts[id]; slot.font .get_or_init(|| { @@ -363,14 +357,15 @@ impl World for SystemWorld { Font::new(data, slot.index) }) .clone() - .ok_or_else(|| io::ErrorKind::InvalidData.into()) } - fn file(&self, path: &Path) -> io::Result { + fn file(&self, path: &Path) -> FileResult { let hash = PathHash::new(path)?; Ok(match self.files.borrow_mut().entry(hash) { Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => entry.insert(fs::read(path)?.into()).clone(), + Entry::Vacant(entry) => entry + .insert(fs::read(path).map_err(|e| FileError::from_io(e, path))?.into()) + .clone(), }) } } @@ -380,15 +375,16 @@ impl World for SystemWorld { struct PathHash(u128); impl PathHash { - fn new(path: &Path) -> io::Result { - let file = File::open(path)?; - if file.metadata()?.is_file() { - let handle = Handle::from_file(file)?; + fn new(path: &Path) -> FileResult { + let f = |e| FileError::from_io(e, path); + let file = File::open(path).map_err(f)?; + if file.metadata().map_err(f)?.is_file() { + let handle = Handle::from_file(file).map_err(f)?; let mut state = SipHasher::new(); handle.hash(&mut state); Ok(Self(state.finish128().as_u128())) } else { - Err(io::ErrorKind::NotFound.into()) + Err(FileError::NotFound(path.into())) } } } diff --git a/src/model/content.rs b/src/model/content.rs index 8076eff9f..dbea141cc 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -23,7 +23,7 @@ use crate::World; /// Layout content into a collection of pages. /// /// Relayouts until all pinned locations are converged. -pub fn layout(world: &dyn World, content: &Content) -> TypResult> { +pub fn layout(world: &dyn World, content: &Content) -> SourceResult> { let styles = StyleChain::with_root(&world.config().styles); let scratch = Scratch::default(); @@ -235,7 +235,7 @@ impl Layout for Content { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let scratch = Scratch::default(); let mut builder = Builder::new(world, &scratch, false); builder.accept(self, styles)?; @@ -369,7 +369,7 @@ impl<'a, 'w> Builder<'a, 'w> { fn into_doc( mut self, styles: StyleChain<'a>, - ) -> TypResult<(DocNode, StyleChain<'a>)> { + ) -> SourceResult<(DocNode, StyleChain<'a>)> { self.interrupt(Interruption::Page, styles, true)?; let (pages, shared) = self.doc.unwrap().pages.finish(); Ok((DocNode(pages), shared)) @@ -378,13 +378,17 @@ impl<'a, 'w> Builder<'a, 'w> { fn into_flow( mut self, styles: StyleChain<'a>, - ) -> TypResult<(FlowNode, StyleChain<'a>)> { + ) -> SourceResult<(FlowNode, StyleChain<'a>)> { self.interrupt(Interruption::Par, styles, false)?; let (children, shared) = self.flow.0.finish(); Ok((FlowNode(children), shared)) } - fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> TypResult<()> { + fn accept( + &mut self, + content: &'a Content, + styles: StyleChain<'a>, + ) -> SourceResult<()> { match content { Content::Empty => return Ok(()), Content::Text(text) => { @@ -430,7 +434,7 @@ impl<'a, 'w> Builder<'a, 'w> { Ok(()) } - fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> { + fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> SourceResult<()> { if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? { let mut map = StyleMap::new(); let barrier = Barrier::new(node.id()); @@ -447,7 +451,7 @@ impl<'a, 'w> Builder<'a, 'w> { &mut self, (content, map): &'a (Content, StyleMap), styles: StyleChain<'a>, - ) -> TypResult<()> { + ) -> SourceResult<()> { let stored = self.scratch.styles.alloc(styles); let styles = map.chain(stored); let intr = map.interruption(); @@ -470,7 +474,7 @@ impl<'a, 'w> Builder<'a, 'w> { intr: Interruption, styles: StyleChain<'a>, keep: bool, - ) -> TypResult<()> { + ) -> SourceResult<()> { if intr >= Interruption::List && !self.list.is_empty() { mem::take(&mut self.list).finish(self)?; } @@ -493,7 +497,11 @@ impl<'a, 'w> Builder<'a, 'w> { Ok(()) } - fn sequence(&mut self, seq: &'a [Content], styles: StyleChain<'a>) -> TypResult<()> { + fn sequence( + &mut self, + seq: &'a [Content], + styles: StyleChain<'a>, + ) -> SourceResult<()> { for content in seq { self.accept(content, styles)?; } @@ -738,7 +746,7 @@ impl<'a> ListBuilder<'a> { true } - fn finish(self, parent: &mut Builder<'a, '_>) -> TypResult<()> { + fn finish(self, parent: &mut Builder<'a, '_>) -> SourceResult<()> { let (items, shared) = self.items.finish(); let kind = match items.items().next() { Some(item) => item.kind, diff --git a/src/model/layout.rs b/src/model/layout.rs index 911cb4d5d..68847471d 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -6,7 +6,7 @@ use std::hash::Hash; use std::sync::Arc; use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry}; -use crate::diag::TypResult; +use crate::diag::SourceResult; use crate::eval::{RawAlign, RawLength}; use crate::frame::{Element, Frame}; use crate::geom::{ @@ -27,7 +27,7 @@ pub trait Layout: 'static { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult>; + ) -> SourceResult>; /// Convert to a packed node. fn pack(self) -> LayoutNode @@ -219,7 +219,7 @@ impl Layout for LayoutNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let barrier = StyleEntry::Barrier(Barrier::new(self.id())); let styles = barrier.chain(&styles); @@ -288,7 +288,7 @@ impl Layout for EmptyNode { _: &dyn World, regions: &Regions, _: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { Ok(vec![Frame::new( regions.expand.select(regions.first, Size::zero()), )]) @@ -310,7 +310,7 @@ impl Layout for SizedNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { // The "pod" is the region into which the child will be layouted. let pod = { // Resolve the sizing to a concrete size. @@ -357,7 +357,7 @@ impl Layout for FillNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut frames = self.child.layout(world, regions, styles)?; for frame in &mut frames { let shape = Geometry::Rect(frame.size()).filled(self.fill); @@ -382,7 +382,7 @@ impl Layout for StrokeNode { world: &dyn World, regions: &Regions, styles: StyleChain, - ) -> TypResult> { + ) -> SourceResult> { let mut frames = self.child.layout(world, regions, styles)?; for frame in &mut frames { let shape = Geometry::Rect(frame.size()).stroked(self.stroke); diff --git a/src/model/recipe.rs b/src/model/recipe.rs index f5ca2cb96..980d939b9 100644 --- a/src/model/recipe.rs +++ b/src/model/recipe.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter}; use super::{Content, Interruption, NodeId, Show, ShowNode, StyleChain, StyleEntry}; -use crate::diag::TypResult; +use crate::diag::SourceResult; use crate::eval::{Args, Func, Regex, Value}; use crate::library::structure::{EnumNode, ListNode}; use crate::syntax::Spanned; @@ -33,7 +33,7 @@ impl Recipe { styles: StyleChain, sel: Selector, target: Target, - ) -> TypResult> { + ) -> SourceResult> { let content = match (target, &self.pattern) { (Target::Node(node), &Pattern::Node(id)) if node.id() == id => { let node = node.unguard(sel); @@ -75,7 +75,7 @@ impl Recipe { } /// Call the recipe function, with the argument if desired. - fn call(&self, world: &dyn World, arg: F) -> TypResult + fn call(&self, world: &dyn World, arg: F) -> SourceResult where F: FnOnce() -> Value, { diff --git a/src/model/show.rs b/src/model/show.rs index e8d279778..56fb29ba4 100644 --- a/src/model/show.rs +++ b/src/model/show.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use std::sync::Arc; use super::{Content, NodeId, Selector, StyleChain}; -use crate::diag::TypResult; +use crate::diag::SourceResult; use crate::eval::Dict; use crate::util::Prehashed; use crate::World; @@ -18,7 +18,7 @@ pub trait Show: 'static { /// The base recipe for this node that is executed if there is no /// user-defined show rule. - fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult; + fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult; /// Finalize this node given the realization of a base or user recipe. Use /// this for effects that should work even in the face of a user-defined @@ -33,7 +33,7 @@ pub trait Show: 'static { world: &dyn World, styles: StyleChain, realized: Content, - ) -> TypResult { + ) -> SourceResult { Ok(realized) } @@ -74,7 +74,7 @@ impl Show for ShowNode { self.0.encode(styles) } - fn realize(&self, world: &dyn World, styles: StyleChain) -> TypResult { + fn realize(&self, world: &dyn World, styles: StyleChain) -> SourceResult { self.0.realize(world, styles) } @@ -83,7 +83,7 @@ impl Show for ShowNode { world: &dyn World, styles: StyleChain, realized: Content, - ) -> TypResult { + ) -> SourceResult { self.0.finalize(world, styles, realized) } diff --git a/src/model/styles.rs b/src/model/styles.rs index 53ef926c9..b61bd5358 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -4,7 +4,7 @@ use std::iter; use std::marker::PhantomData; use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target}; -use crate::diag::TypResult; +use crate::diag::SourceResult; use crate::frame::Role; use crate::library::text::{FontFamily, TextNode}; use crate::util::ReadableTypeId; @@ -277,7 +277,11 @@ impl<'a> StyleChain<'a> { } /// Apply show recipes in this style chain to a target. - pub fn apply(self, world: &dyn World, target: Target) -> TypResult> { + pub fn apply( + self, + world: &dyn World, + target: Target, + ) -> SourceResult> { // Find out how many recipes there any and whether any of their patterns // match. let mut n = 0; diff --git a/src/source.rs b/src/source.rs index c3648e11d..b78d90522 100644 --- a/src/source.rs +++ b/src/source.rs @@ -5,13 +5,13 @@ use std::path::{Path, PathBuf}; use unscanny::Scanner; -use crate::diag::TypResult; +use crate::diag::SourceResult; use crate::parse::{is_newline, parse, reparse}; use crate::syntax::ast::Markup; use crate::syntax::{Span, SyntaxNode}; use crate::util::{PathExt, StrExt}; -/// A single source file. +/// A source file. /// /// _Note_: All line and column indices start at zero, just like byte indices. /// Only for user-facing display, you should add 1 to them. @@ -63,10 +63,10 @@ impl Source { } /// The root node of the file's typed abstract syntax tree. - pub fn ast(&self) -> TypResult { + pub fn ast(&self) -> SourceResult { let errors = self.root.errors(); if errors.is_empty() { - Ok(self.root.cast().unwrap()) + Ok(self.root.cast().expect("root node must be markup")) } else { Err(Box::new(errors)) } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index c2b06efe5..89937f2cf 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -13,7 +13,7 @@ pub use highlight::*; pub use span::*; use self::ast::{MathNode, RawNode, TypedNode, Unit}; -use crate::diag::Error; +use crate::diag::SourceError; use crate::source::SourceId; use crate::util::EcoString; @@ -67,14 +67,14 @@ impl SyntaxNode { } /// The error messages for this node and its descendants. - pub fn errors(&self) -> Vec { + pub fn errors(&self) -> Vec { if !self.erroneous() { return vec![]; } match self.kind() { &NodeKind::Error(pos, ref message) => { - vec![Error::new(self.span().with_pos(pos), message)] + vec![SourceError::new(self.span().with_pos(pos), message)] } _ => self .children() diff --git a/tests/res/bad.svg b/tests/res/bad.svg new file mode 100644 index 000000000..b7828a619 --- /dev/null +++ b/tests/res/bad.svg @@ -0,0 +1,5 @@ + + + diff --git a/tests/typ/code/import.typ b/tests/typ/code/import.typ index 2c27f1352..0c0b6c086 100644 --- a/tests/typ/code/import.typ +++ b/tests/typ/code/import.typ @@ -47,7 +47,7 @@ --- // Some non-text stuff. -// Error: 16-37 failed to load source file (file is not valid utf-8) +// Error: 16-37 file is not valid utf-8 #import * from "../../res/rhino.png" --- diff --git a/tests/typ/graphics/image.typ b/tests/typ/graphics/image.typ index e64b6c450..3bb5bdd12 100644 --- a/tests/typ/graphics/image.typ +++ b/tests/typ/graphics/image.typ @@ -55,5 +55,9 @@ A #image("/res/tiger.jpg", height: 1cm, width: 80%) B #image("path/does/not/exist") --- -// Error: 8-21 failed to load image (unknown image format) +// Error: 8-21 unknown image format #image("./image.typ") + +--- +// Error: 8-22 failed to parse svg: found closing tag 'g' instead of 'style' in line 4 +#image("/res/bad.svg") diff --git a/tests/typ/utility/csv.typ b/tests/typ/utility/csv.typ index ab955ab07..146cafae4 100644 --- a/tests/typ/utility/csv.typ +++ b/tests/typ/utility/csv.typ @@ -11,5 +11,5 @@ #csv("nope.csv") --- -// Error: 6-20 failed to load csv file (CSV error: record 2 (line: 3, byte: 8): found record with 3 fields, but the previous record has 2 fields) +// Error: 6-20 failed to parse csv file: found 3 instead of 2 fields in line 3 #csv("/res/bad.csv") diff --git a/tests/typeset.rs b/tests/typeset.rs index 3ac353dbc..d3d365c3f 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -4,7 +4,6 @@ use std::env; use std::ffi::OsStr; use std::fs::{self, File}; use std::hash::Hash; -use std::io; use std::ops::Range; use std::path::{Path, PathBuf}; @@ -15,6 +14,7 @@ use tiny_skia as sk; use unscanny::Scanner; use walkdir::WalkDir; +use typst::diag::{FileError, FileResult}; use typst::eval::{Smart, Value}; use typst::font::{Font, FontBook}; use typst::frame::{Element, Frame}; @@ -238,17 +238,13 @@ impl World for TestWorld { &self.config } - fn resolve(&self, path: &Path) -> io::Result { + fn resolve(&self, path: &Path) -> FileResult { let hash = PathHash::new(path)?; if let Some(&id) = self.nav.borrow().get(&hash) { return Ok(id); } - let data = fs::read(path)?; - let text = String::from_utf8(data).map_err(|_| { - io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8") - })?; - + let text = fs::read_to_string(path).map_err(|e| FileError::from_io(e, path))?; let id = SourceId::from_raw(self.sources.len() as u16); let source = Source::new(id, path, text); self.sources.push(Box::new(source)); @@ -265,15 +261,17 @@ impl World for TestWorld { &self.book } - fn font(&self, id: usize) -> io::Result { - Ok(self.fonts[id].clone()) + fn font(&self, id: usize) -> Option { + Some(self.fonts[id].clone()) } - fn file(&self, path: &Path) -> io::Result { + fn file(&self, path: &Path) -> FileResult { let hash = PathHash::new(path)?; Ok(match self.files.borrow_mut().entry(hash) { Entry::Occupied(entry) => entry.get().clone(), - Entry::Vacant(entry) => entry.insert(fs::read(path)?.into()).clone(), + Entry::Vacant(entry) => entry + .insert(fs::read(path).map_err(|e| FileError::from_io(e, path))?.into()) + .clone(), }) } } @@ -283,15 +281,16 @@ impl World for TestWorld { struct PathHash(u128); impl PathHash { - fn new(path: &Path) -> io::Result { - let file = File::open(path)?; - if file.metadata()?.is_file() { - let handle = Handle::from_file(file)?; + fn new(path: &Path) -> FileResult { + let f = |e| FileError::from_io(e, path); + let file = File::open(path).map_err(f)?; + if file.metadata().map_err(f)?.is_file() { + let handle = Handle::from_file(file).map_err(f)?; let mut state = SipHasher::new(); handle.hash(&mut state); Ok(Self(state.finish128().as_u128())) } else { - Err(io::ErrorKind::NotFound.into()) + Err(FileError::NotFound(path.into())) } } }