mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Add file information to spans
This commit is contained in:
parent
d002cdf451
commit
144f208821
110
src/diag.rs
110
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<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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
207
src/eval/mod.rs
207
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<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 ¶ms {
|
||||
let value = match ¶m.default {
|
||||
None => args.expect::<Value>(¶m.name)?,
|
||||
Some(default) => args
|
||||
.named::<Value>(¶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::<Value>(¶m.name)?,
|
||||
Some(default) => args
|
||||
.named::<Value>(¶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<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(),
|
||||
|
@ -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),
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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(),
|
||||
|
16
src/main.rs
16
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)?;
|
||||
}
|
||||
|
@ -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![],
|
||||
},
|
||||
_ => {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
111
src/source.rs
111
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<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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user