Add file information to spans

This commit is contained in:
Laurenz 2021-08-13 12:21:14 +02:00
parent d002cdf451
commit 144f208821
14 changed files with 355 additions and 358 deletions

View File

@ -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<T> = Result<T, Box<Vec<Error>>>;
@ -14,14 +24,29 @@ pub type StrResult<T> = Result<T, String>;
/// 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<Spanned<Tracepoint>>,
}
impl Error {
/// Create a new, bare error.
pub fn new(span: Span, message: impl Into<String>) -> 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<String>) -> Box<Vec<Self>> {
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<Span>,
message: impl Into<String>,
) -> Self {
Self {
source,
span: span.into(),
trace: vec![],
message: message.into(),
}
}
/// Convert a [`StrResult`] to a [`TypResult`] by adding span information.
pub trait At<T> {
/// Add the span information.
fn at(self, span: Span) -> TypResult<T>;
}
/// 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<Span>,
message: impl Into<String>,
) -> Box<Vec<Self>> {
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<S: Into<String>>(
source: SourceId,
span: impl Into<Span>,
) -> impl FnOnce(S) -> Box<Vec<Self>> {
move |message| Self::boxed(source, span, message)
impl<T> At<T> for StrResult<T> {
fn at(self, span: Span) -> TypResult<T> {
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<T> {
/// Add the tracepoint to all errors that lie outside the `span`.
fn trace<F>(self, make_point: F, span: Span) -> Self
where
F: Fn() -> Tracepoint;
}
impl<T> Trace<T> for TypResult<T> {
fn trace<F>(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
})
}
}

View File

@ -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)

View File

@ -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<SourceId>,
/// 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<Self::Output> {
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<Value>,
{
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<Self::Output> {
Ok(FuncArgs {
source: ctx.source,
span: self.span,
items: self
.items
@ -444,9 +425,6 @@ impl Eval for ClosureExpr {
default: Option<Value>,
}
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 &params {
let value = match &param.default {
None => args.expect::<Value>(&param.name)?,
Some(default) => args
.named::<Value>(&param.name)?
.unwrap_or_else(|| default.clone()),
};
// Parse the arguments according to the parameter list.
for param in &params {
let value = match &param.default {
None => args.expect::<Value>(&param.name)?,
Some(default) => args
.named::<Value>(&param.name)?
.unwrap_or_else(|| default.clone()),
};
ctx.scopes.def_mut(&param.name, value);
}
ctx.scopes.def_mut(&param.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<Self::Output> {
let callee = self
.callee
.eval(ctx)?
.cast::<Function>()
.map_err(Error::at(ctx.source, self.callee.span()))?;
let callee = self.callee.eval(ctx)?.cast::<Function>().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<Self::Output> {
let condition = self
.condition
.eval(ctx)?
.cast::<bool>()
.map_err(Error::at(ctx.source, self.condition.span()))?;
let condition =
self.condition.eval(ctx)?.cast::<bool>().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<Self::Output> {
let mut output = Value::None;
while self
.condition
.eval(ctx)?
.cast::<bool>()
.map_err(Error::at(ctx.source, self.condition.span()))?
{
while self.condition.eval(ctx)?.cast::<bool>().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<Self::Output> {
let path = self
.path
.eval(ctx)?
.cast::<EcoString>()
.map_err(Error::at(ctx.source, self.path.span()))?;
let path = self.path.eval(ctx)?.cast::<EcoString>().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<Self::Output> {
let path = self
.path
.eval(ctx)?
.cast::<EcoString>()
.map_err(Error::at(ctx.source, self.path.span()))?;
let path = self.path.eval(ctx)?.cast::<EcoString>().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<RefMut<'a, Value>> {
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<RefMut<'a, Value>> {
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(),

View File

@ -17,7 +17,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
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),
})

View File

@ -6,7 +6,7 @@ use crate::paper::{Paper, PaperClass};
pub fn page(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
let paper = match args.eat::<Spanned<EcoString>>() {
Some(name) => match Paper::from_name(&name.v) {
None => bail!(args.source, name.span, "invalid paper name"),
None => bail!(name.span, "invalid paper name"),
paper => paper,
},
None => None,

View File

@ -132,7 +132,7 @@ pub fn lang(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
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)

View File

@ -25,7 +25,7 @@ pub fn len(_: &mut EvalContext, args: &mut FuncArgs) -> TypResult<Value> {
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<Value> {
if let Some(string) = args.eat::<Spanned<EcoString>>() {
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<Value> {
/// Find the minimum or maximum of a sequence of values.
fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult<Value> {
let &mut FuncArgs { source, span, .. } = args;
let span = args.span;
let mut extremum = args.expect::<Value>("value")?;
for value in args.all::<Value>() {
@ -71,7 +71,6 @@ fn minmax(args: &mut FuncArgs, goal: Ordering) -> TypResult<Value> {
}
}
None => bail!(
source,
span,
"cannot compare {} with {}",
extremum.type_name(),

View File

@ -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)?;
}

View File

@ -549,7 +549,7 @@ fn call(p: &mut Parser, callee: Expr) -> Option<Expr> {
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![],
},
_ => {

View File

@ -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<Error>,
@ -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<Token<'s>>,
/// 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<Span>) -> &'s str {
self.tokens.scanner().get(span.into().to_range())
pub fn get(&self, range: Range<usize>) -> &'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<Pos>) -> 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<Span>, message: impl Into<String>) {
self.errors.push(Error::new(self.source.id(), span, message));
pub fn error(&mut self, span: impl IntoSpan, message: impl Into<String>) {
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())
}
}

View File

@ -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);

View File

@ -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<Pos>,
line_starts: Vec<usize>,
}
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<Span>) -> Option<&str> {
self.src.get(span.into().to_range())
pub fn get(&self, range: Range<usize>) -> 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<usize> {
(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<usize> {
(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<usize> {
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<usize> {
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<Pos> {
pub fn line_to_byte(&self, line_idx: usize) -> Option<usize> {
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<Span> {
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<Range<usize>> {
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<Pos> {
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<usize> {
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<usize, files::Error> {
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<std::ops::Range<usize>, 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<usize, files::Error> {
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);
}
}

View File

@ -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<T> {
/// The spanned value.
@ -19,11 +21,6 @@ impl<T> Spanned<T> {
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<T>` to `Spanned<&T>`
pub fn as_ref(&self) -> Spanned<&T> {
Spanned { v: &self.v, span: self.span }
@ -51,9 +48,11 @@ impl<T: Debug> Debug for Spanned<T> {
}
/// 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<Pos>, end: impl Into<Pos>) -> Self {
Self { start: start.into(), end: end.into() }
pub fn new(source: SourceId, start: impl Into<Pos>, end: impl Into<Pos>) -> Self {
Self {
source,
start: start.into(),
end: end.into(),
}
}
/// Create a span including just a single position.
pub fn at(pos: impl Into<Pos> + Copy) -> Self {
Self::new(pos, pos)
pub fn at(source: SourceId, pos: impl Into<Pos> + 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<Pos>) -> Self {
Self { start: start.into(), ..self }
}
/// Create a span with a different end position.
pub fn with_end(self, end: impl Into<Pos>) -> 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<usize>` for indexing.
/// Convert to a `Range<Pos>` for indexing.
pub fn to_range(self) -> Range<usize> {
self.start.to_usize() .. self.end.to_usize()
}
}
impl<T> From<T> for Span
where
T: Into<Pos> + Copy,
{
fn from(pos: T) -> Self {
Self::at(pos)
}
}
impl<T> From<Range<T>> for Span
where
T: Into<Pos>,
{
fn from(range: Range<T>) -> 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<usize> {
fn into_span(self, source: SourceId) -> Span {
Span::new(source, self.start, self.end)
}
}

View File

@ -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::<Value>("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<bool>, Vec<Error>) {
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