From 144f20882136ef81b79d77bd8a68f42b76c66676 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 13 Aug 2021 12:21:14 +0200 Subject: [PATCH] Add file information to spans --- src/diag.rs | 110 +++++++++++---------- src/eval/function.rs | 21 ++-- src/eval/mod.rs | 207 ++++++++++++++-------------------------- src/library/elements.rs | 2 +- src/library/layout.rs | 2 +- src/library/text.rs | 2 +- src/library/utility.rs | 7 +- src/main.rs | 16 ++-- src/parse/mod.rs | 2 +- src/parse/parser.rs | 99 ++++++++++--------- src/parse/resolve.rs | 10 +- src/source.rs | 111 ++++++++++----------- src/syntax/span.rs | 104 +++++++++++++------- tests/typeset.rs | 20 ++-- 14 files changed, 355 insertions(+), 358 deletions(-) diff --git a/src/diag.rs b/src/diag.rs index 1905d4807..154de24b8 100644 --- a/src/diag.rs +++ b/src/diag.rs @@ -2,8 +2,18 @@ use serde::{Deserialize, Serialize}; -use crate::source::SourceId; -use crate::syntax::Span; +use crate::syntax::{Span, Spanned}; + +/// Early-return with a vec-boxed [`Error`]. +macro_rules! bail { + ($span:expr, $message:expr $(,)?) => { + return Err($crate::diag::Error::boxed($span, $message,)); + }; + + ($span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => { + bail!($span, format!($fmt, $($arg),+)); + }; +} /// The result type for typesetting and all its subpasses. pub type TypResult = Result>>; @@ -14,14 +24,29 @@ pub type StrResult = Result; /// An error in a source file. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub struct Error { - /// The id of the source file that contains the error. - pub source: SourceId, /// The erroneous location in the source code. pub span: Span, /// A diagnostic message describing the problem. pub message: String, /// The trace of function calls leading to the error. - pub trace: Vec<(SourceId, Span, Tracepoint)>, + pub trace: Vec>, +} + +impl Error { + /// Create a new, bare error. + pub fn new(span: Span, message: impl Into) -> Self { + Self { + span, + trace: vec![], + message: message.into(), + } + } + + /// Create a boxed vector containing one error. The return value is suitable + /// as the `Err` variant of a [`TypResult`]. + pub fn boxed(span: Span, message: impl Into) -> Box> { + Box::new(vec![Self::new(span, message)]) + } } /// A part of an error's [trace](Error::trace). @@ -33,53 +58,38 @@ pub enum Tracepoint { Import, } -impl Error { - /// Create a new, bare error. - pub fn new( - source: SourceId, - span: impl Into, - message: impl Into, - ) -> Self { - Self { - source, - span: span.into(), - trace: vec![], - message: message.into(), - } - } +/// Convert a [`StrResult`] to a [`TypResult`] by adding span information. +pub trait At { + /// Add the span information. + fn at(self, span: Span) -> TypResult; +} - /// Create a boxed vector containing one error. The return value is suitable - /// as the `Err` variant of a [`TypResult`]. - pub fn boxed( - source: SourceId, - span: impl Into, - message: impl Into, - ) -> Box> { - Box::new(vec![Self::new(source, span, message)]) - } - - /// Create a closure that contains the positional information for an error - /// and just needs the message to yield a vec-boxed error. - /// - /// This is useful in to convert from [`StrResult`] to a [`TypResult`] using - /// [`map_err`](Result::map_err). - pub fn at>( - source: SourceId, - span: impl Into, - ) -> impl FnOnce(S) -> Box> { - move |message| Self::boxed(source, span, message) +impl At for StrResult { + fn at(self, span: Span) -> TypResult { + self.map_err(|message| Error::boxed(span, message)) } } -/// Early-return with a vec-boxed [`Error`]. -macro_rules! bail { - ($source:expr, $span:expr, $message:expr $(,)?) => { - return Err(Box::new(vec![$crate::diag::Error::new( - $source, $span, $message, - )])); - }; - - ($source:expr, $span:expr, $fmt:expr, $($arg:expr),+ $(,)?) => { - bail!($source, $span, format!($fmt, $($arg),+)); - }; +/// Enrich a [`TypResult`] with a tracepoint. +pub trait Trace { + /// Add the tracepoint to all errors that lie outside the `span`. + fn trace(self, make_point: F, span: Span) -> Self + where + F: Fn() -> Tracepoint; +} + +impl Trace for TypResult { + fn trace(self, make_point: F, span: Span) -> Self + where + F: Fn() -> Tracepoint, + { + self.map_err(|mut errors| { + for error in errors.iter_mut() { + if !span.contains(error.span) { + error.trace.push(Spanned::new(make_point(), span)); + } + } + errors + }) + } } diff --git a/src/eval/function.rs b/src/eval/function.rs index be7894f43..d2ec1bb92 100644 --- a/src/eval/function.rs +++ b/src/eval/function.rs @@ -3,8 +3,7 @@ use std::ops::Deref; use std::rc::Rc; use super::{Cast, EvalContext, Value}; -use crate::diag::{Error, TypResult}; -use crate::source::SourceId; +use crate::diag::{At, TypResult}; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -59,8 +58,6 @@ impl PartialEq for Function { /// Evaluated arguments to a function. #[derive(Debug, Clone, PartialEq)] pub struct FuncArgs { - /// The id of the source file in which the function was called. - pub source: SourceId, /// The span of the whole argument list. pub span: Span, /// The positional arguments. @@ -103,7 +100,7 @@ impl FuncArgs { { match self.eat() { Some(found) => Ok(found), - None => bail!(self.source, self.span, "missing argument: {}", what), + None => bail!(self.span, "missing argument: {}", what), } } @@ -134,14 +131,14 @@ impl FuncArgs { let value = self.items.remove(index).value; let span = value.span; - T::cast(value).map(Some).map_err(Error::at(self.source, span)) + T::cast(value).map(Some).at(span) } /// Return an "unexpected argument" error if there is any remaining /// argument. pub fn finish(self) -> TypResult<()> { if let Some(arg) = self.items.first() { - bail!(self.source, arg.span, "unexpected argument"); + bail!(arg.span, "unexpected argument"); } Ok(()) } @@ -165,19 +162,17 @@ impl FuncArgs { { let mut iter = self.items.into_iter(); let value = match iter.next() { + Some(FuncArg { name: None, value, .. }) => value.v.cast().at(value.span)?, None => { - bail!(self.source, self.span, "missing {}", what); + bail!(self.span, "missing {}", what); } Some(FuncArg { name: Some(_), span, .. }) => { - bail!(self.source, span, "named pair is not allowed here"); - } - Some(FuncArg { name: None, value, .. }) => { - value.v.cast().map_err(Error::at(self.source, value.span))? + bail!(span, "named pair is not allowed here"); } }; if let Some(arg) = iter.next() { - bail!(self.source, arg.span, "only one {} is allowed", what); + bail!(arg.span, "only one {} is allowed", what); } Ok(value) diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 1b851a9bc..0932dc716 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -27,7 +27,7 @@ use std::mem; use std::path::PathBuf; use std::rc::Rc; -use crate::diag::{Error, StrResult, Tracepoint, TypResult}; +use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; use crate::image::ImageStore; use crate::loading::Loader; @@ -71,8 +71,6 @@ pub struct EvalContext<'a> { pub images: &'a mut ImageStore, /// Caches evaluated modules. pub modules: &'a mut ModuleCache, - /// The id of the currently evaluated source file. - pub source: SourceId, /// The stack of imported files that led to evaluation of the current file. pub route: Vec, /// The active scopes. @@ -89,8 +87,7 @@ impl<'a> EvalContext<'a> { sources: &mut ctx.sources, images: &mut ctx.images, modules: &mut ctx.modules, - source, - route: vec![], + route: vec![source], scopes: Scopes::new(Some(&ctx.std)), map: ExprMap::new(), } @@ -101,15 +98,15 @@ impl<'a> EvalContext<'a> { // Load the source file. let full = self.make_path(path); let id = self.sources.load(&full).map_err(|err| { - Error::boxed(self.source, span, match err.kind() { + Error::boxed(span, match err.kind() { io::ErrorKind::NotFound => "file not found".into(), _ => format!("failed to load source file ({})", err), }) })?; // Prevent cyclic importing. - if self.source == id || self.route.contains(&id) { - bail!(self.source, span, "cyclic import"); + if self.route.contains(&id) { + bail!(span, "cyclic import"); } // Check whether the module was already loaded. @@ -124,23 +121,14 @@ impl<'a> EvalContext<'a> { // Prepare the new context. let new_scopes = Scopes::new(self.scopes.base); let old_scopes = mem::replace(&mut self.scopes, new_scopes); - self.route.push(self.source); - self.source = id; + self.route.push(id); // Evaluate the module. - let result = Rc::new(ast).eval(self); + let template = Rc::new(ast).eval(self).trace(|| Tracepoint::Import, span)?; // Restore the old context. let new_scopes = mem::replace(&mut self.scopes, old_scopes); - self.source = self.route.pop().unwrap(); - - // Add a tracepoint to the errors. - let template = result.map_err(|mut errors| { - for error in errors.iter_mut() { - error.trace.push((self.source, span, Tracepoint::Import)); - } - errors - })?; + self.route.pop().unwrap(); // Save the evaluated module. let module = Module { scope: new_scopes.top, template }; @@ -152,11 +140,13 @@ impl<'a> EvalContext<'a> { /// Complete a user-entered path (relative to the source file) to be /// relative to the compilation environment's root. pub fn make_path(&self, path: &str) -> PathBuf { - if let Some(dir) = self.sources.get(self.source).path().parent() { - dir.join(path) - } else { - path.into() + if let Some(&id) = self.route.last() { + if let Some(dir) = self.sources.get(id).path().parent() { + return dir.join(path); + } } + + path.into() } } @@ -198,10 +188,7 @@ impl Eval for Expr { Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)), Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)), Self::Str(_, ref v) => Value::Str(v.clone()), - Self::Ident(ref v) => match ctx.scopes.get(&v) { - Some(slot) => slot.borrow().clone(), - None => bail!(ctx.source, v.span, "unknown variable"), - }, + Self::Ident(ref v) => v.eval(ctx)?, Self::Array(ref v) => Value::Array(v.eval(ctx)?), Self::Dict(ref v) => Value::Dict(v.eval(ctx)?), Self::Template(ref v) => Value::Template(v.eval(ctx)?), @@ -222,6 +209,17 @@ impl Eval for Expr { } } +impl Eval for Ident { + type Output = Value; + + fn eval(&self, ctx: &mut EvalContext) -> TypResult { + match ctx.scopes.get(self) { + Some(slot) => Ok(slot.borrow().clone()), + None => bail!(self.span, "unknown variable"), + } + } +} + impl Eval for ArrayExpr { type Output = Array; @@ -268,8 +266,7 @@ impl Eval for BlockExpr { let mut output = Value::None; for expr in &self.exprs { let value = expr.eval(ctx)?; - output = - ops::join(output, value).map_err(Error::at(ctx.source, expr.span()))?; + output = ops::join(output, value).at(expr.span())?; } if self.scoping { @@ -290,7 +287,7 @@ impl Eval for UnaryExpr { UnOp::Neg => ops::neg(value), UnOp::Not => ops::not(value), }; - result.map_err(Error::at(ctx.source, self.span)) + result.at(self.span) } } @@ -337,7 +334,7 @@ impl BinaryExpr { } let rhs = self.rhs.eval(ctx)?; - op(lhs, rhs).map_err(Error::at(ctx.source, self.span)) + op(lhs, rhs).at(self.span) } /// Apply an assignment operation. @@ -345,11 +342,10 @@ impl BinaryExpr { where F: FnOnce(Value, Value) -> StrResult, { - let source = ctx.source; let rhs = self.rhs.eval(ctx)?; let mut target = self.lhs.access(ctx)?; let lhs = mem::take(&mut *target); - *target = op(lhs, rhs).map_err(Error::at(source, self.span))?; + *target = op(lhs, rhs).at(self.span)?; Ok(Value::None) } } @@ -362,36 +358,22 @@ impl Eval for CallExpr { let mut args = self.args.eval(ctx)?; match callee { - Value::Array(array) => array - .get(args.into_index()?) - .map(Value::clone) - .map_err(Error::at(ctx.source, self.span)), + Value::Array(array) => { + array.get(args.into_index()?).map(Value::clone).at(self.span) + } - Value::Dict(dict) => dict - .get(&args.into_key()?) - .map(Value::clone) - .map_err(Error::at(ctx.source, self.span)), + Value::Dict(dict) => { + dict.get(&args.into_key()?).map(Value::clone).at(self.span) + } Value::Func(func) => { - let returned = func(ctx, &mut args).map_err(|mut errors| { - for error in errors.iter_mut() { - // Skip errors directly related to arguments. - if error.source != ctx.source || !self.span.contains(error.span) { - error.trace.push(( - ctx.source, - self.span, - Tracepoint::Call(func.name().map(Into::into)), - )); - } - } - errors - })?; + let point = || Tracepoint::Call(func.name().map(Into::into)); + let value = func(ctx, &mut args).trace(point, self.span)?; args.finish()?; - Ok(returned) + Ok(value) } v => bail!( - ctx.source, self.callee.span(), "expected function or collection, found {}", v.type_name(), @@ -405,7 +387,6 @@ impl Eval for CallArgs { fn eval(&self, ctx: &mut EvalContext) -> TypResult { Ok(FuncArgs { - source: ctx.source, span: self.span, items: self .items @@ -444,9 +425,6 @@ impl Eval for ClosureExpr { default: Option, } - let file = ctx.source; - let name = self.name.as_ref().map(|name| name.string.clone()); - // Evaluate default values for named parameters. let params: Vec<_> = self .params @@ -472,36 +450,31 @@ impl Eval for ClosureExpr { // Clone the body expression so that we don't have a lifetime // dependence on the AST. let body = Rc::clone(&self.body); + let name = self.name.as_ref().map(|name| name.string.clone()); // Define the actual function. let func = Function::new(name, move |ctx, args| { - let prev_file = mem::replace(&mut ctx.source, file); - // Don't leak the scopes from the call site. Instead, we use the // scope of captured variables we collected earlier. let prev_scopes = mem::take(&mut ctx.scopes); ctx.scopes.top = captured.clone(); - let mut try_eval = || { - // Parse the arguments according to the parameter list. - for param in ¶ms { - let value = match ¶m.default { - None => args.expect::(¶m.name)?, - Some(default) => args - .named::(¶m.name)? - .unwrap_or_else(|| default.clone()), - }; + // Parse the arguments according to the parameter list. + for param in ¶ms { + let value = match ¶m.default { + None => args.expect::(¶m.name)?, + Some(default) => args + .named::(¶m.name)? + .unwrap_or_else(|| default.clone()), + }; - ctx.scopes.def_mut(¶m.name, value); - } + ctx.scopes.def_mut(¶m.name, value); + } - body.eval(ctx) - }; + let value = body.eval(ctx)?; - let result = try_eval(); ctx.scopes = prev_scopes; - ctx.source = prev_file; - result + Ok(value) }); Ok(Value::Func(func)) @@ -512,11 +485,7 @@ impl Eval for WithExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let callee = self - .callee - .eval(ctx)? - .cast::() - .map_err(Error::at(ctx.source, self.callee.span()))?; + let callee = self.callee.eval(ctx)?.cast::().at(self.callee.span())?; let name = callee.name().cloned(); let applied = self.args.eval(ctx)?; @@ -562,11 +531,8 @@ impl Eval for IfExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let condition = self - .condition - .eval(ctx)? - .cast::() - .map_err(Error::at(ctx.source, self.condition.span()))?; + let condition = + self.condition.eval(ctx)?.cast::().at(self.condition.span())?; if condition { self.if_body.eval(ctx) @@ -584,15 +550,9 @@ impl Eval for WhileExpr { fn eval(&self, ctx: &mut EvalContext) -> TypResult { let mut output = Value::None; - while self - .condition - .eval(ctx)? - .cast::() - .map_err(Error::at(ctx.source, self.condition.span()))? - { + while self.condition.eval(ctx)?.cast::().at(self.condition.span())? { let value = self.body.eval(ctx)?; - output = ops::join(output, value) - .map_err(Error::at(ctx.source, self.body.span()))?; + output = ops::join(output, value).at(self.body.span())?; } Ok(output) @@ -614,7 +574,7 @@ impl Eval for ForExpr { let value = self.body.eval(ctx)?; output = ops::join(output, value) - .map_err(Error::at(ctx.source, self.body.span()))?; + .at(self.body.span())?; } ctx.scopes.exit(); @@ -640,14 +600,11 @@ impl Eval for ForExpr { iter!(for (k => key, v => value) in dict.into_iter()) } (ForPattern::KeyValue(_, _), Value::Str(_)) => { - bail!(ctx.source, self.pattern.span(), "mismatched pattern"); + bail!(self.pattern.span(), "mismatched pattern"); + } + (_, iter) => { + bail!(self.iter.span(), "cannot loop over {}", iter.type_name()); } - (_, iter) => bail!( - ctx.source, - self.iter.span(), - "cannot loop over {}", - iter.type_name(), - ), } } } @@ -656,11 +613,7 @@ impl Eval for ImportExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let path = self - .path - .eval(ctx)? - .cast::() - .map_err(Error::at(ctx.source, self.path.span()))?; + let path = self.path.eval(ctx)?.cast::().at(self.path.span())?; let file = ctx.import(&path, self.path.span())?; let module = &ctx.modules[&file]; @@ -676,7 +629,7 @@ impl Eval for ImportExpr { if let Some(slot) = module.scope.get(&ident) { ctx.scopes.def_mut(ident.as_str(), slot.borrow().clone()); } else { - bail!(ctx.source, ident.span, "unresolved import"); + bail!(ident.span, "unresolved import"); } } } @@ -690,11 +643,7 @@ impl Eval for IncludeExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> TypResult { - let path = self - .path - .eval(ctx)? - .cast::() - .map_err(Error::at(ctx.source, self.path.span()))?; + let path = self.path.eval(ctx)?.cast::().at(self.path.span())?; let file = ctx.import(&path, self.path.span())?; let module = &ctx.modules[&file]; @@ -753,42 +702,34 @@ impl Access for Expr { match self { Expr::Ident(ident) => ident.access(ctx), Expr::Call(call) => call.access(ctx), - _ => bail!( - ctx.source, - self.span(), - "cannot access this expression mutably", - ), + _ => bail!(self.span(), "cannot access this expression mutably"), } } } impl Access for Ident { fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult> { - ctx.scopes - .get(self) - .ok_or("unknown variable") - .and_then(|slot| { - slot.try_borrow_mut().map_err(|_| "cannot mutate a constant") - }) - .map_err(Error::at(ctx.source, self.span)) + match ctx.scopes.get(self) { + Some(slot) => match slot.try_borrow_mut() { + Ok(guard) => Ok(guard), + Err(_) => bail!(self.span, "cannot mutate a constant"), + }, + None => bail!(self.span, "unknown variable"), + } } } impl Access for CallExpr { fn access<'a>(&self, ctx: &'a mut EvalContext) -> TypResult> { - let source = ctx.source; let args = self.args.eval(ctx)?; let guard = self.callee.access(ctx)?; RefMut::try_map(guard, |value| match value { - Value::Array(array) => array - .get_mut(args.into_index()?) - .map_err(Error::at(source, self.span)), + Value::Array(array) => array.get_mut(args.into_index()?).at(self.span), Value::Dict(dict) => Ok(dict.get_mut(args.into_key()?)), v => bail!( - source, self.callee.span(), "expected collection, found {}", v.type_name(), diff --git a/src/library/elements.rs b/src/library/elements.rs index 98165204d..f0e36994b 100644 --- a/src/library/elements.rs +++ b/src/library/elements.rs @@ -17,7 +17,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult { let full = ctx.make_path(&path.v); let id = ctx.images.load(&full).map_err(|err| { - Error::boxed(args.source, path.span, match err.kind() { + Error::boxed(path.span, match err.kind() { io::ErrorKind::NotFound => "file not found".into(), _ => format!("failed to load image ({})", err), }) diff --git a/src/library/layout.rs b/src/library/layout.rs index 0d7782069..53e3a450e 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -6,7 +6,7 @@ use crate::paper::{Paper, PaperClass}; pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { let paper = match args.eat::>() { Some(name) => match Paper::from_name(&name.v) { - None => bail!(args.source, name.span, "invalid paper name"), + None => bail!(name.span, "invalid paper name"), paper => paper, }, None => None, diff --git a/src/library/text.rs b/src/library/text.rs index cd97691c4..55cabd15d 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -132,7 +132,7 @@ pub fn lang(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { if dir.v.axis() == SpecAxis::Horizontal { Some(dir.v) } else { - bail!(args.source, dir.span, "must be horizontal"); + bail!(dir.span, "must be horizontal"); } } else { iso.as_deref().map(lang_dir) diff --git a/src/library/utility.rs b/src/library/utility.rs index 22bde3a1a..20d10830a 100644 --- a/src/library/utility.rs +++ b/src/library/utility.rs @@ -25,7 +25,7 @@ pub fn len(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { Value::Str(v) => Value::Int(v.len() as i64), Value::Array(v) => Value::Int(v.len()), Value::Dict(v) => Value::Int(v.len()), - _ => bail!(args.source, span, "expected string, array or dictionary"), + _ => bail!(span, "expected string, array or dictionary"), }) } @@ -35,7 +35,7 @@ pub fn rgb(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { if let Some(string) = args.eat::>() { match RgbaColor::from_str(&string.v) { Ok(color) => color, - Err(_) => bail!(args.source, string.span, "invalid color"), + Err(_) => bail!(string.span, "invalid color"), } } else { let r = args.expect("red component")?; @@ -60,7 +60,7 @@ pub fn max(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult { /// Find the minimum or maximum of a sequence of values. fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult { - let &mut FuncArgs { source, span, .. } = args; + let span = args.span; let mut extremum = args.expect::("value")?; for value in args.all::() { @@ -71,7 +71,6 @@ fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult { } } None => bail!( - source, span, "cannot compare {} with {}", extremum.type_name(), diff --git a/src/main.rs b/src/main.rs index f3a97d510..9fa89e424 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,15 +113,15 @@ fn print_diagnostics( for error in errors { // The main diagnostic. - let main = Diagnostic::error() - .with_message(error.message) - .with_labels(vec![Label::primary(error.source, error.span.to_range())]); + let main = Diagnostic::error().with_message(error.message).with_labels(vec![ + Label::primary(error.span.source, error.span.to_range()), + ]); term::emit(&mut writer, &config, sources, &main)?; // Stacktrace-like helper diagnostics. - for (file, span, point) in error.trace { - let message = match point { + for point in error.trace { + let message = match point.v { Tracepoint::Call(Some(name)) => { format!("error occured in this call of function `{}`", name) } @@ -129,9 +129,9 @@ fn print_diagnostics( Tracepoint::Import => "error occured while importing this module".into(), }; - let help = Diagnostic::help() - .with_message(message) - .with_labels(vec![Label::primary(file, span.to_range())]); + let help = Diagnostic::help().with_message(message).with_labels(vec![ + Label::primary(point.span.source, point.span.to_range()), + ]); term::emit(&mut writer, &config, sources, &help)?; } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 9678b1293..03026df4a 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -549,7 +549,7 @@ fn call(p: &mut Parser, callee: Expr) -> Option { let mut args = match p.peek_direct() { Some(Token::LeftParen) => args(p), Some(Token::LeftBracket) => CallArgs { - span: Span::at(callee.span().end), + span: Span::at(p.id(), callee.span().end), items: vec![], }, _ => { diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 4f2e59c60..83e77a6a2 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -1,13 +1,14 @@ use std::fmt::{self, Debug, Formatter}; +use std::ops::Range; use super::{TokenMode, Tokens}; use crate::diag::Error; -use crate::source::SourceFile; -use crate::syntax::{Pos, Span, Token}; +use crate::source::{SourceFile, SourceId}; +use crate::syntax::{IntoSpan, Pos, Span, Token}; /// A convenient token-based parser. pub struct Parser<'s> { - /// The id of the parsed file. + /// The parsed file. source: &'s SourceFile, /// Parsing errors. errors: Vec, @@ -20,18 +21,18 @@ pub struct Parser<'s> { /// The peeked token. /// (Same as `next` except if we are at the end of group, then `None`). peeked: Option>, - /// The end position of the last (non-whitespace if in code mode) token. - prev_end: Pos, - /// The start position of the peeked token. - next_start: Pos, + /// The end index of the last (non-whitespace if in code mode) token. + prev_end: usize, + /// The start index of the peeked token. + next_start: usize, } /// A logical group of tokens, e.g. `[...]`. #[derive(Debug, Copy, Clone)] struct GroupEntry { - /// The start position of the group. Used by `Parser::end_group` to return - /// The group's full span. - pub start: Pos, + /// The start index of the group. Used by `Parser::end_group` to return the + /// group's full span. + pub start: usize, /// The kind of group this is. This decides which tokens will end the group. /// For example, a [`Group::Paren`] will be ended by /// [`Token::RightParen`]. @@ -69,8 +70,8 @@ impl<'s> Parser<'s> { groups: vec![], next, peeked: next, - prev_end: Pos::ZERO, - next_start: Pos::ZERO, + prev_end: 0, + next_start: 0, } } @@ -79,6 +80,11 @@ impl<'s> Parser<'s> { self.errors } + /// The id of the parsed source file. + pub fn id(&self) -> SourceId { + self.source.id() + } + /// Whether the end of the source string or group is reached. pub fn eof(&self) -> bool { self.peek().is_none() @@ -95,7 +101,7 @@ impl<'s> Parser<'s> { pub fn eat_span(&mut self) -> Span { let start = self.next_start(); self.eat(); - Span::new(start, self.prev_end()) + Span::new(self.id(), start, self.prev_end()) } /// Consume the next token if it is the given one. @@ -166,12 +172,12 @@ impl<'s> Parser<'s> { /// /// Has length zero if `peek()` returns `None`. pub fn peek_span(&self) -> Span { - Span::new(self.next_start(), self.next_end()) + Span::new(self.id(), self.next_start(), self.next_end()) } /// Peek at the source of the next token. pub fn peek_src(&self) -> &'s str { - self.get(self.peek_span()) + self.get(self.next_start() .. self.next_end()) } /// Checks whether the next token fulfills a condition. @@ -184,39 +190,39 @@ impl<'s> Parser<'s> { self.peek().map_or(false, f) } - /// The byte position at which the last token ended. + /// The byte index at which the last token ended. /// /// Refers to the end of the last _non-whitespace_ token in code mode. - pub fn prev_end(&self) -> Pos { - self.prev_end.into() + pub fn prev_end(&self) -> usize { + self.prev_end } - /// The byte position at which the next token starts. - pub fn next_start(&self) -> Pos { - self.next_start.into() + /// The byte index at which the next token starts. + pub fn next_start(&self) -> usize { + self.next_start } - /// The byte position at which the next token will end. + /// The byte index at which the next token will end. /// /// Is the same as [`next_start()`][Self::next_start] if `peek()` returns /// `None`. - pub fn next_end(&self) -> Pos { - self.tokens.index().into() + pub fn next_end(&self) -> usize { + self.tokens.index() } - /// The span from `start` to [`self.prev_end()`](Self::prev_end). - pub fn span_from(&self, start: Pos) -> Span { - Span::new(start, self.prev_end()) - } - - /// Determine the column index for the given byte position. - pub fn column(&self, pos: Pos) -> usize { - self.source.pos_to_column(pos).unwrap() + /// Determine the column index for the given byte index. + pub fn column(&self, index: usize) -> usize { + self.source.byte_to_column(index).unwrap() } /// Slice out part of the source string. - pub fn get(&self, span: impl Into) -> &'s str { - self.tokens.scanner().get(span.into().to_range()) + pub fn get(&self, range: Range) -> &'s str { + self.source.get(range).unwrap() + } + + /// The span from `start` to [`self.prev_end()`](Self::prev_end). + pub fn span_from(&self, start: impl Into) -> Span { + Span::new(self.id(), start, self.prev_end()) } /// Continue parsing in a group. @@ -271,17 +277,20 @@ impl<'s> Parser<'s> { self.bump(); rescan = false; } else if required { - self.error(self.next_start(), format!("expected {}", end.name())); + self.error( + self.next_start() .. self.next_start(), + format!("expected {}", end.name()), + ); } } // Rescan the peeked token if the mode changed. if rescan { - self.tokens.jump(self.prev_end().to_usize()); + self.tokens.jump(self.prev_end()); self.bump(); } - Span::new(group.start, self.prev_end()) + Span::new(self.id(), group.start, self.prev_end()) } /// The tokenization mode outside of the current group. @@ -296,8 +305,13 @@ impl<'s> Parser<'s> { } /// Add an error with location and message. - pub fn error(&mut self, span: impl Into, message: impl Into) { - self.errors.push(Error::new(self.source.id(), span, message)); + pub fn error(&mut self, span: impl IntoSpan, message: impl Into) { + self.errors.push(Error::new(span.into_span(self.id()), message)); + } + + /// Add an error that `what` was expected at the given span. + pub fn expected_at(&mut self, span: impl IntoSpan, what: &str) { + self.error(span, format!("expected {}", what)); } /// Eat the next token and add an error that it is not the expected `thing`. @@ -314,11 +328,6 @@ impl<'s> Parser<'s> { } } - /// Add an error that `what` was expected at the given position. - pub fn expected_at(&mut self, pos: Pos, what: &str) { - self.error(pos, format!("expected {}", what)); - } - /// Eat the next token and add an error that it is unexpected. pub fn unexpected(&mut self) { let before = self.next_start(); @@ -386,7 +395,7 @@ impl<'s> Parser<'s> { impl Debug for Parser<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { let mut s = self.tokens.scanner(); - s.jump(self.next_start().to_usize()); + s.jump(self.next_start()); write!(f, "Parser({}|{})", s.eaten(), s.rest()) } } diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index 7bd160f9a..7ceac1289 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -52,8 +52,12 @@ pub fn resolve_raw(span: Span, text: &str, backticks: usize) -> RawNode { if backticks > 1 { let (tag, inner) = split_at_lang_tag(text); let (text, block) = trim_and_split_raw(inner); - let lang = Ident::new(tag, span.start .. span.start + tag.len()); - RawNode { span, lang, text: text.into(), block } + RawNode { + span, + lang: Ident::new(tag, span.with_end(span.start + tag.len())), + text: text.into(), + block, + } } else { RawNode { span, @@ -176,7 +180,7 @@ mod tests { text: &str, block: bool, ) { - let node = resolve_raw(Span::ZERO, raw, backticks); + let node = resolve_raw(Span::detached(), raw, backticks); assert_eq!(node.lang.as_deref(), lang); assert_eq!(node.text, text); assert_eq!(node.block, block); diff --git a/src/source.rs b/src/source.rs index 0a5a28609..0f1637a96 100644 --- a/src/source.rs +++ b/src/source.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::io; +use std::ops::Range; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -11,7 +12,6 @@ use serde::{Deserialize, Serialize}; use crate::loading::{FileHash, Loader}; use crate::parse::{is_newline, Scanner}; -use crate::syntax::{Pos, Span}; use crate::util::PathExt; /// A unique identifier for a loaded source file. @@ -119,13 +119,13 @@ pub struct SourceFile { id: SourceId, path: PathBuf, src: String, - line_starts: Vec, + line_starts: Vec, } impl SourceFile { /// Create a new source file. pub fn new(id: SourceId, path: &Path, src: String) -> Self { - let mut line_starts = vec![Pos::ZERO]; + let mut line_starts = vec![0]; let mut s = Scanner::new(&src); while let Some(c) = s.eat() { @@ -133,7 +133,7 @@ impl SourceFile { if c == '\r' { s.eat_if('\n'); } - line_starts.push(s.index().into()); + line_starts.push(s.index()); } } @@ -166,8 +166,8 @@ impl SourceFile { } /// Slice out the part of the source code enclosed by the span. - pub fn get(&self, span: impl Into) -> Option<&str> { - self.src.get(span.into().to_range()) + pub fn get(&self, range: Range) -> Option<&str> { + self.src.get(range) } /// Get the length of the file in bytes. @@ -180,10 +180,10 @@ impl SourceFile { self.line_starts.len() } - /// Return the index of the line that contains the given byte position. - pub fn pos_to_line(&self, byte_pos: Pos) -> Option { - (byte_pos.to_usize() <= self.src.len()).then(|| { - match self.line_starts.binary_search(&byte_pos) { + /// Return the index of the line that contains the given byte index. + pub fn byte_to_line(&self, byte_idx: usize) -> Option { + (byte_idx <= self.src.len()).then(|| { + match self.line_starts.binary_search(&byte_idx) { Ok(i) => i, Err(i) => i - 1, } @@ -193,38 +193,42 @@ impl SourceFile { /// Return the index of the column at the byte index. /// /// The column is defined as the number of characters in the line before the - /// byte position. - pub fn pos_to_column(&self, byte_pos: Pos) -> Option { - let line = self.pos_to_line(byte_pos)?; - let start = self.line_to_pos(line)?; - let head = self.get(Span::new(start, byte_pos))?; + /// byte index. + pub fn byte_to_column(&self, byte_idx: usize) -> Option { + let line = self.byte_to_line(byte_idx)?; + let start = self.line_to_byte(line)?; + let head = self.get(start .. byte_idx)?; Some(head.chars().count()) } /// Return the byte position at which the given line starts. - pub fn line_to_pos(&self, line_idx: usize) -> Option { + pub fn line_to_byte(&self, line_idx: usize) -> Option { self.line_starts.get(line_idx).copied() } - /// Return the span which encloses the given line. - pub fn line_to_span(&self, line_idx: usize) -> Option { - let start = self.line_to_pos(line_idx)?; - let end = self.line_to_pos(line_idx + 1).unwrap_or(self.src.len().into()); - Some(Span::new(start, end)) + /// Return the range which encloses the given line. + pub fn line_to_range(&self, line_idx: usize) -> Option> { + let start = self.line_to_byte(line_idx)?; + let end = self.line_to_byte(line_idx + 1).unwrap_or(self.src.len()); + Some(start .. end) } - /// Return the byte position of the given (line, column) pair. + /// Return the byte index of the given (line, column) pair. /// /// The column defines the number of characters to go beyond the start of /// the line. - pub fn line_column_to_pos(&self, line_idx: usize, column_idx: usize) -> Option { - let span = self.line_to_span(line_idx)?; - let line = self.get(span)?; + pub fn line_column_to_byte( + &self, + line_idx: usize, + column_idx: usize, + ) -> Option { + let range = self.line_to_range(line_idx)?; + let line = self.get(range.clone())?; let mut chars = line.chars(); for _ in 0 .. column_idx { chars.next(); } - Some(span.start + (line.len() - chars.as_str().len())) + Some(range.start + (line.len() - chars.as_str().len())) } } @@ -251,7 +255,7 @@ impl<'a> Files<'a> for SourceStore { fn line_index(&'a self, id: SourceId, given: usize) -> Result { let source = self.get(id); source - .pos_to_line(given.into()) + .byte_to_line(given) .ok_or_else(|| files::Error::IndexTooLarge { given, max: source.len_bytes() }) } @@ -262,8 +266,7 @@ impl<'a> Files<'a> for SourceStore { ) -> Result, files::Error> { let source = self.get(id); source - .line_to_span(given) - .map(Span::to_range) + .line_to_range(given) .ok_or_else(|| files::Error::LineTooLarge { given, max: source.len_lines() }) } @@ -274,7 +277,7 @@ impl<'a> Files<'a> for SourceStore { given: usize, ) -> Result { let source = self.get(id); - source.pos_to_column(given.into()).ok_or_else(|| { + source.byte_to_column(given).ok_or_else(|| { let max = source.len_bytes(); if given <= max { files::Error::InvalidCharBoundary { given } @@ -294,47 +297,47 @@ mod tests { #[test] fn test_source_file_new() { let source = SourceFile::detached(TEST); - assert_eq!(source.line_starts, vec![Pos(0), Pos(7), Pos(15), Pos(18)]); + assert_eq!(source.line_starts, vec![0, 7, 15, 18]); } #[test] fn test_source_file_pos_to_line() { let source = SourceFile::detached(TEST); - assert_eq!(source.pos_to_line(Pos(0)), Some(0)); - assert_eq!(source.pos_to_line(Pos(2)), Some(0)); - assert_eq!(source.pos_to_line(Pos(6)), Some(0)); - assert_eq!(source.pos_to_line(Pos(7)), Some(1)); - assert_eq!(source.pos_to_line(Pos(8)), Some(1)); - assert_eq!(source.pos_to_line(Pos(12)), Some(1)); - assert_eq!(source.pos_to_line(Pos(21)), Some(3)); - assert_eq!(source.pos_to_line(Pos(22)), None); + assert_eq!(source.byte_to_line(0), Some(0)); + assert_eq!(source.byte_to_line(2), Some(0)); + assert_eq!(source.byte_to_line(6), Some(0)); + assert_eq!(source.byte_to_line(7), Some(1)); + assert_eq!(source.byte_to_line(8), Some(1)); + assert_eq!(source.byte_to_line(12), Some(1)); + assert_eq!(source.byte_to_line(21), Some(3)); + assert_eq!(source.byte_to_line(22), None); } #[test] fn test_source_file_pos_to_column() { let source = SourceFile::detached(TEST); - assert_eq!(source.pos_to_column(Pos(0)), Some(0)); - assert_eq!(source.pos_to_column(Pos(2)), Some(1)); - assert_eq!(source.pos_to_column(Pos(6)), Some(5)); - assert_eq!(source.pos_to_column(Pos(7)), Some(0)); - assert_eq!(source.pos_to_column(Pos(8)), Some(1)); - assert_eq!(source.pos_to_column(Pos(12)), Some(2)); + assert_eq!(source.byte_to_column(0), Some(0)); + assert_eq!(source.byte_to_column(2), Some(1)); + assert_eq!(source.byte_to_column(6), Some(5)); + assert_eq!(source.byte_to_column(7), Some(0)); + assert_eq!(source.byte_to_column(8), Some(1)); + assert_eq!(source.byte_to_column(12), Some(2)); } #[test] fn test_source_file_roundtrip() { #[track_caller] - fn roundtrip(source: &SourceFile, byte_pos: Pos) { - let line = source.pos_to_line(byte_pos).unwrap(); - let column = source.pos_to_column(byte_pos).unwrap(); - let result = source.line_column_to_pos(line, column).unwrap(); - assert_eq!(result, byte_pos); + fn roundtrip(source: &SourceFile, byte_idx: usize) { + let line = source.byte_to_line(byte_idx).unwrap(); + let column = source.byte_to_column(byte_idx).unwrap(); + let result = source.line_column_to_byte(line, column).unwrap(); + assert_eq!(result, byte_idx); } let source = SourceFile::detached(TEST); - roundtrip(&source, Pos(0)); - roundtrip(&source, Pos(7)); - roundtrip(&source, Pos(12)); - roundtrip(&source, Pos(21)); + roundtrip(&source, 0); + roundtrip(&source, 7); + roundtrip(&source, 12); + roundtrip(&source, 21); } } diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 800cca198..cad9c5d53 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -3,8 +3,10 @@ use std::ops::{Add, Range}; use serde::{Deserialize, Serialize}; +use crate::source::SourceId; + /// A value with the span it corresponds to in the source code. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Serialize, Deserialize)] pub struct Spanned { /// The spanned value. @@ -19,11 +21,6 @@ impl Spanned { Self { v, span: span.into() } } - /// Create a new instance from a value with the zero span. - pub fn zero(v: T) -> Self { - Self { v, span: Span::ZERO } - } - /// Convert from `&Spanned` to `Spanned<&T>` pub fn as_ref(&self) -> Spanned<&T> { Spanned { v: &self.v, span: self.span } @@ -51,9 +48,11 @@ impl Debug for Spanned { } /// Bounds of a slice of source code. -#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Serialize, Deserialize)] pub struct Span { + /// The id of the source file. + pub source: SourceId, /// The inclusive start position. pub start: Pos, /// The inclusive end position. @@ -61,22 +60,46 @@ pub struct Span { } impl Span { - /// The zero span. - pub const ZERO: Self = Self { start: Pos::ZERO, end: Pos::ZERO }; - /// Create a new span from start and end positions. - pub fn new(start: impl Into, end: impl Into) -> Self { - Self { start: start.into(), end: end.into() } + pub fn new(source: SourceId, start: impl Into, end: impl Into) -> Self { + Self { + source, + start: start.into(), + end: end.into(), + } } /// Create a span including just a single position. - pub fn at(pos: impl Into + Copy) -> Self { - Self::new(pos, pos) + pub fn at(source: SourceId, pos: impl Into + Copy) -> Self { + Self::new(source, pos, pos) + } + + /// Create a span without real location information, usually for testing. + pub fn detached() -> Self { + Self { + source: SourceId::from_raw(0), + start: Pos::ZERO, + end: Pos::ZERO, + } + } + + /// Create a span with a different start position. + pub fn with_start(self, start: impl Into) -> Self { + Self { start: start.into(), ..self } + } + + /// Create a span with a different end position. + pub fn with_end(self, end: impl Into) -> Self { + Self { end: end.into(), ..self } } /// Create a new span with the earlier start and later end position. + /// + /// This panics if the spans come from different files. pub fn join(self, other: Self) -> Self { + debug_assert_eq!(self.source, other.source); Self { + source: self.source, start: self.start.min(other.start), end: self.end.max(other.end), } @@ -89,33 +112,15 @@ impl Span { /// Test whether one span complete contains the other span. pub fn contains(self, other: Self) -> bool { - self.start <= other.start && self.end >= other.end + self.source == other.source && self.start <= other.start && self.end >= other.end } - /// Convert to a `Range` for indexing. + /// Convert to a `Range` for indexing. pub fn to_range(self) -> Range { self.start.to_usize() .. self.end.to_usize() } } -impl From for Span -where - T: Into + Copy, -{ - fn from(pos: T) -> Self { - Self::at(pos) - } -} - -impl From> for Span -where - T: Into, -{ - fn from(range: Range) -> Self { - Self::new(range.start, range.end) - } -} - impl Debug for Span { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{:?}-{:?}", self.start, self.end) @@ -165,3 +170,34 @@ where Pos(self.0 + rhs.into().0) } } + +/// Convert a position or range into a span. +pub trait IntoSpan { + /// Convert into a span by providing the source id. + fn into_span(self, source: SourceId) -> Span; +} + +impl IntoSpan for Span { + fn into_span(self, source: SourceId) -> Span { + debug_assert_eq!(self.source, source); + self + } +} + +impl IntoSpan for Pos { + fn into_span(self, source: SourceId) -> Span { + Span::new(source, self, self) + } +} + +impl IntoSpan for usize { + fn into_span(self, source: SourceId) -> Span { + Span::new(source, self, self) + } +} + +impl IntoSpan for Range { + fn into_span(self, source: SourceId) -> Span { + Span::new(source, self.start, self.end) + } +} diff --git a/tests/typeset.rs b/tests/typeset.rs index 650780fda..a489a3ec0 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -19,7 +19,7 @@ use typst::layout::{layout, Element, Frame, Geometry, LayoutTree, Paint, Text}; use typst::loading::FsLoader; use typst::parse::{parse, Scanner}; use typst::source::{SourceFile, SourceId}; -use typst::syntax::Pos; +use typst::syntax::{Pos, Span}; use typst::Context; const TYP_DIR: &str = "./typ"; @@ -71,7 +71,6 @@ fn main() { let rhs = args.expect::("right-hand side")?; if lhs != rhs { return Err(Error::boxed( - args.source, args.span, format!("Assertion failed: {:?} != {:?}", lhs, rhs), )); @@ -244,7 +243,7 @@ fn test_part( }; // TODO: Also handle errors from other files. - errors.retain(|error| error.source == id); + errors.retain(|error| error.span.source == id); for error in &mut errors { error.trace.clear(); } @@ -258,7 +257,7 @@ fn test_part( let source = ctx.sources.get(id); for error in errors.iter() { - if error.source == id && !ref_errors.contains(error) { + if error.span.source == id && !ref_errors.contains(error) { print!(" Not annotated | "); print_error(&source, line, error); } @@ -362,24 +361,25 @@ fn parse_metadata(source: &SourceFile) -> (Option, Vec) { let (delta, column) = if s.eat_if(':') { (first, num(s) - 1) } else { (0, first) }; let line = (i + comments) + delta; - source.line_column_to_pos(line, column).unwrap() + source.line_column_to_byte(line, column).unwrap().into() }; let mut s = Scanner::new(rest); let start = pos(&mut s); let end = if s.eat_if('-') { pos(&mut s) } else { start }; + let span = Span::new(source.id(), start, end); - errors.push(Error::new(source.id(), start .. end, s.rest().trim())); + errors.push(Error::new(span, s.rest().trim())); } (compare_ref, errors) } fn print_error(source: &SourceFile, line: usize, error: &Error) { - let start_line = 1 + line + source.pos_to_line(error.span.start).unwrap(); - let start_col = 1 + source.pos_to_column(error.span.start).unwrap(); - let end_line = 1 + line + source.pos_to_line(error.span.end).unwrap(); - let end_col = 1 + source.pos_to_column(error.span.end).unwrap(); + let start_line = 1 + line + source.byte_to_line(error.span.start.to_usize()).unwrap(); + let start_col = 1 + source.byte_to_column(error.span.start.to_usize()).unwrap(); + let end_line = 1 + line + source.byte_to_line(error.span.end.to_usize()).unwrap(); + let end_col = 1 + source.byte_to_column(error.span.end.to_usize()).unwrap(); println!( "Error: {}:{}-{}:{}: {}", start_line, start_col, end_line, end_col, error.message