Merge pull request #5 from typst/problems-and-error-macro

Rename errors to problems and make error! macro more ergonomic
This commit is contained in:
Laurenz 2020-07-26 17:41:07 +02:00 committed by GitHub
commit e7ffdde43d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 266 additions and 233 deletions

View File

@ -1,73 +0,0 @@
//! Errors in source code.
//!
//! There are no fatal errors in _Typst_. The document will always compile and
//! yield a layout. However, this is a best effort process and bad things will
//! still generate errors and warnings.
use serde::Serialize;
use crate::syntax::span::SpanVec;
/// A spanned list of errors.
pub type Errors = SpanVec<Error>;
/// An error that arose in parsing or layouting.
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Error {
/// An error message describing the problem.
pub message: String,
/// How severe / important the error is.
pub severity: Severity,
}
/// How severe / important an error is.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Severity {
/// Something in the code is not good.
Warning,
/// Something in the code is wrong!
Error,
}
impl Error {
/// Create a new error from message and severity.
pub fn new(message: impl Into<String>, severity: Severity) -> Error {
Error { message: message.into(), severity }
}
}
/// Construct an error with formatted message and optionally severity and / or
/// span.
///
/// # Examples
/// ```
/// # use typstc::err;
/// # use typstc::syntax::span::Span;
/// # let span = Span::ZERO;
/// # let value = 0;
///
/// // With span and default severity `Error`.
/// err!(span; "the wrong {}", value);
///
/// // With no span and severity `Warning`.
/// err!(@Warning: span; "non-fatal!");
///
/// // Without span and default severity.
/// err!("no spans here ...");
/// ```
#[macro_export]
macro_rules! err {
(@$severity:ident: $span:expr; $($args:tt)*) => {
$crate::syntax::span::Spanned { v: err!(@$severity: $($args)*), span: $span }
};
(@$severity:ident: $($args:tt)*) => {
$crate::error::Error {
message: format!($($args)*),
severity: $crate::error::Severity::$severity,
}
};
($($tts:tt)*) => { err!(@Error: $($tts)*) };
}

View File

@ -7,7 +7,7 @@ use crate::syntax::span::Spanned;
/// Types that are useful for creating your own functions.
pub mod prelude {
pub use crate::{function, body, err};
pub use crate::{function, body, error, warning};
pub use crate::layout::prelude::*;
pub use crate::layout::Command::{self, *};
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
@ -55,8 +55,8 @@ pub trait ParseFunc {
///
/// parse(header, body, ctx, f) {
/// let body = body!(opt: body, ctx, f);
/// let hidden = header.args.pos.get::<bool>(&mut f.errors)
/// .or_missing(&mut f.errors, header.name.span, "hidden")
/// let hidden = header.args.pos.get::<bool>(&mut f.problems)
/// .or_missing(&mut f.problems, header.name.span, "hidden")
/// .unwrap_or(false);
///
/// HiderFunc { body: if hidden { None } else { body } }
@ -132,7 +132,7 @@ macro_rules! function {
let func = $code;
for arg in header.args.into_iter() {
feedback.errors.push(err!(arg.span; "unexpected argument"));
error!(@feedback, arg.span, "unexpected argument");
}
$crate::Pass::new(func, feedback)
@ -189,7 +189,7 @@ macro_rules! body {
(nope: $body:expr, $feedback:expr) => {
if let Some(body) = $body {
$feedback.errors.push($crate::err!(body.span; "unexpected body"));
error!(@$feedback, body.span, "unexpected body");
}
};
}

View File

@ -245,8 +245,10 @@ impl<'a> ModelLayouter<'a> {
BreakParagraph => self.layout_paragraph(),
BreakPage => {
if self.ctx.nested {
self.feedback.errors.push(err!(model_span;
"page break cannot be issued from nested context"));
error!(
@self.feedback, model_span,
"page break cannot be issued from nested context",
);
} else {
self.layouter.finish_space(true)
}
@ -258,8 +260,10 @@ impl<'a> ModelLayouter<'a> {
}
SetPageStyle(style) => {
if self.ctx.nested {
self.feedback.errors.push(err!(model_span;
"page style cannot be changed from nested context"));
error!(
@self.feedback, model_span,
"page style cannot be changed from nested context",
);
} else {
self.style.page = style;

View File

@ -27,7 +27,7 @@ use toddle::{Font, OwnedData};
use toddle::query::{FontLoader, SharedFontLoader};
use toddle::query::{FontProvider, FontIndex, FontDescriptor};
use crate::error::Error;
use crate::problem::Problems;
use crate::layout::MultiLayout;
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::{SyntaxModel, Scope, Decoration, ParseContext, parse};
@ -43,7 +43,7 @@ macro_rules! pub_use_mod {
}
#[macro_use]
pub mod error;
pub mod problem;
pub mod export;
#[macro_use]
pub mod func;
@ -175,8 +175,8 @@ impl<T> Pass<T> {
/// User feedback data accumulated during a compilation pass.
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Feedback {
/// Errors in the source.
pub errors: SpanVec<Error>,
/// Problems in the source code.
pub problems: Problems,
/// Decorations of the source code for semantic syntax highlighting.
pub decos: SpanVec<Decoration>,
}
@ -185,7 +185,7 @@ impl Feedback {
/// Create a new feedback instance without errors and decos.
pub fn new() -> Feedback {
Feedback {
errors: vec![],
problems: vec![],
decos: vec![],
}
}
@ -198,14 +198,14 @@ impl Feedback {
/// Add other feedback data to this feedback.
pub fn extend(&mut self, other: Feedback) {
self.errors.extend(other.errors);
self.problems.extend(other.problems);
self.decos.extend(other.decos);
}
/// Add more feedback whose spans are local and need to be offset by an
/// `offset` to be correct for this feedbacks context.
pub fn extend_offset(&mut self, offset: Position, other: Feedback) {
self.errors.extend(offset_spans(offset, other.errors));
self.problems.extend(offset_spans(offset, other.problems));
self.decos.extend(offset_spans(offset, other.decos));
}
}

View File

@ -13,17 +13,17 @@ function! {
}
parse(header, body, ctx, f) {
let list = header.args.pos.get_all::<StringLike>(&mut f.errors)
let list = header.args.pos.get_all::<StringLike>(&mut f.problems)
.map(|s| s.0.to_lowercase())
.collect();
let tuples: Vec<_> = header.args.key
.get_all::<String, Tuple>(&mut f.errors)
.get_all::<String, Tuple>(&mut f.problems)
.collect();
let classes = tuples.into_iter()
.map(|(class, mut tuple)| {
let fallback = tuple.get_all::<StringLike>(&mut f.errors)
let fallback = tuple.get_all::<StringLike>(&mut f.problems)
.map(|s| s.0.to_lowercase())
.collect();
(class.to_lowercase(), fallback)
@ -37,7 +37,7 @@ function! {
}
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
styled(&self.body, ctx, Some(()),
|s, _| {
if !self.list.is_empty() {
@ -64,12 +64,12 @@ function! {
parse(header, body, ctx, f) {
FontStyleFunc {
body: body!(opt: body, ctx, f),
style: header.args.pos.get::<FontStyle>(&mut f.errors)
.or_missing(&mut f.errors, header.name.span, "style"),
style: header.args.pos.get::<FontStyle>(&mut f.problems)
.or_missing(&mut f.problems, header.name.span, "style"),
}
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
styled(&self.body, ctx, self.style, |t, s| t.variant.style = s)
}
}
@ -84,22 +84,24 @@ function! {
parse(header, body, ctx, f) {
let body = body!(opt: body, ctx, f);
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(&mut f.errors)
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(&mut f.problems)
.map(|Spanned { v: (weight, is_clamped), span }| {
if is_clamped {
f.errors.push(err!(@Warning: span;
"weight should be between \
100 and 900, clamped to {}", weight.0));
warning!(
@f, span,
"weight should be between 100 and 900, clamped to {}",
weight.0,
);
}
weight
})
.or_missing(&mut f.errors, header.name.span, "weight");
.or_missing(&mut f.problems, header.name.span, "weight");
FontWeightFunc { body, weight }
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
styled(&self.body, ctx, self.weight, |t, w| t.variant.weight = w)
}
}
@ -115,12 +117,12 @@ function! {
parse(header, body, ctx, f) {
FontSizeFunc {
body: body!(opt: body, ctx, f),
size: header.args.pos.get::<FSize>(&mut f.errors)
.or_missing(&mut f.errors, header.name.span, "size")
size: header.args.pos.get::<FSize>(&mut f.problems)
.or_missing(&mut f.problems, header.name.span, "size")
}
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
styled(&self.body, ctx, self.size, |t, s| {
match s {
FSize::Absolute(size) => {

View File

@ -13,14 +13,14 @@ function! {
parse(header, body, ctx, f) {
AlignFunc {
body: body!(opt: body, ctx, f),
map: PosAxisMap::parse::<AxisKey>(&mut f.errors, &mut header.args),
map: PosAxisMap::parse::<AxisKey>(&mut f.problems, &mut header.args),
}
}
layout(self, ctx, f) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(&mut f.errors, ctx.axes, |alignment| {
let map = self.map.dedup(&mut f.problems, ctx.axes, |alignment| {
alignment.axis().map(|s| s.to_generic(ctx.axes))
});
@ -29,8 +29,10 @@ function! {
if let Some(generic) = alignment.to_generic(ctx.axes, axis) {
*ctx.alignment.get_mut(axis) = generic;
} else {
f.errors.push(err!(span;
"invalid alignment `{}` for {} axis", alignment, axis));
error!(
@f, span,
"invalid alignment `{}` for {} axis", alignment, axis,
);
}
}
}
@ -59,14 +61,14 @@ function! {
DirectionFunc {
name_span: header.name.span,
body: body!(opt: body, ctx, f),
map: PosAxisMap::parse::<AxisKey>(&mut f.errors, &mut header.args),
map: PosAxisMap::parse::<AxisKey>(&mut f.problems, &mut header.args),
}
}
layout(self, ctx, f) {
ctx.base = ctx.spaces[0].dimensions;
let map = self.map.dedup(&mut f.errors, ctx.axes, |direction| {
let map = self.map.dedup(&mut f.problems, ctx.axes, |direction| {
Some(direction.axis().to_generic(ctx.axes))
});
@ -76,9 +78,11 @@ function! {
map.with(Secondary, |&dir| axes.secondary = dir);
if axes.primary.axis() == axes.secondary.axis() {
f.errors.push(err!(self.name_span;
error!(
@f, self.name_span,
"invalid aligned primary and secondary axes: `{}`, `{}`",
ctx.axes.primary, ctx.axes.secondary));
ctx.axes.primary, ctx.axes.secondary,
);
} else {
ctx.axes = axes;
}
@ -106,8 +110,8 @@ function! {
parse(header, body, ctx, f) {
BoxFunc {
body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()),
extents: AxisMap::parse::<ExtentKey>(&mut f.errors, &mut header.args.key),
debug: header.args.key.get::<bool>(&mut f.errors, "debug"),
extents: AxisMap::parse::<ExtentKey>(&mut f.problems, &mut header.args.key),
debug: header.args.key.get::<bool>(&mut f.problems, "debug"),
}
}
@ -119,7 +123,7 @@ function! {
ctx.debug = debug;
}
let map = self.extents.dedup(&mut f.errors, ctx.axes);
let map = self.extents.dedup(&mut f.problems, ctx.axes);
for &axis in &[Horizontal, Vertical] {
if let Some(psize) = map.get(axis) {
let size = psize.scaled(ctx.base.get(axis));

View File

@ -59,7 +59,7 @@ function! {
ValFunc { body: body!(opt: body, ctx, f) }
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
match &self.body {
Some(model) => vec![LayoutSyntaxModel(model)],
None => vec![],

View File

@ -15,9 +15,9 @@ function! {
parse(header, body, ctx, f) {
body!(nope: body, f);
PageSizeFunc {
paper: header.args.pos.get::<Paper>(&mut f.errors),
extents: AxisMap::parse::<ExtentKey>(&mut f.errors, &mut header.args.key),
flip: header.args.key.get::<bool>(&mut f.errors, "flip").unwrap_or(false),
paper: header.args.pos.get::<Paper>(&mut f.problems),
extents: AxisMap::parse::<ExtentKey>(&mut f.problems, &mut header.args.key),
flip: header.args.key.get::<bool>(&mut f.problems, "flip").unwrap_or(false),
}
}
@ -31,7 +31,7 @@ function! {
style.class = PaperClass::Custom;
}
let map = self.extents.dedup(&mut f.errors, ctx.axes);
let map = self.extents.dedup(&mut f.problems, ctx.axes);
map.with(Horizontal, |&width| style.dimensions.x = width);
map.with(Vertical, |&height| style.dimensions.y = height);
@ -53,13 +53,13 @@ function! {
parse(header, body, ctx, f) {
body!(nope: body, f);
PageMarginsFunc {
padding: PaddingMap::parse(&mut f.errors, &mut header.args),
padding: PaddingMap::parse(&mut f.problems, &mut header.args),
}
}
layout(self, ctx, f) {
let mut style = ctx.style.page;
self.padding.apply(&mut f.errors, ctx.axes, &mut style.margins);
self.padding.apply(&mut f.problems, ctx.axes, &mut style.margins);
vec![SetPageStyle(style)]
}
}

View File

@ -11,7 +11,7 @@ function! {
pub struct LineBreakFunc;
parse(default)
layout(self, ctx, errors) { vec![BreakLine] }
layout(self, ctx, f) { vec![BreakLine] }
}
function! {
@ -22,7 +22,7 @@ function! {
pub struct ParBreakFunc;
parse(default)
layout(self, ctx, errors) { vec![BreakParagraph] }
layout(self, ctx, f) { vec![BreakParagraph] }
}
function! {
@ -31,7 +31,7 @@ function! {
pub struct PageBreakFunc;
parse(default)
layout(self, ctx, errors) { vec![BreakPage] }
layout(self, ctx, f) { vec![BreakPage] }
}
function! {
@ -50,13 +50,13 @@ function! {
ContentSpacingFunc {
body: body!(opt: body, ctx, f),
content: meta,
spacing: header.args.pos.get::<f64>(&mut f.errors)
spacing: header.args.pos.get::<f64>(&mut f.problems)
.map(|num| num as f32)
.or_missing(&mut f.errors, header.name.span, "spacing"),
.or_missing(&mut f.problems, header.name.span, "spacing"),
}
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
styled(&self.body, ctx, self.spacing, |t, s| match self.content {
Word => t.word_spacing_scale = s,
Line => t.line_spacing_scale = s,
@ -88,15 +88,15 @@ function! {
body!(nope: body, f);
SpacingFunc {
spacing: if let Some(axis) = meta {
header.args.pos.get::<FSize>(&mut f.errors)
header.args.pos.get::<FSize>(&mut f.problems)
.map(|s| (AxisKey::Specific(axis), s))
} else {
header.args.key.get_with_key::<AxisKey, FSize>(&mut f.errors)
}.or_missing(&mut f.errors, header.name.span, "spacing"),
header.args.key.get_with_key::<AxisKey, FSize>(&mut f.problems)
}.or_missing(&mut f.problems, header.name.span, "spacing"),
}
}
layout(self, ctx, errors) {
layout(self, ctx, f) {
if let Some((axis, spacing)) = self.spacing {
let axis = axis.to_generic(ctx.axes);
let spacing = spacing.scaled(ctx.style.text.font_size());

94
src/problem.rs Normal file
View File

@ -0,0 +1,94 @@
//! Problems (errors / warnings) in _Typst_ documents.
//!
//! There are no fatal errors in _Typst_. The document will always compile and
//! yield a layout. However, this is a best effort process and bad things will
//! still generate errors and warnings.
use serde::Serialize;
use crate::syntax::span::SpanVec;
/// A list of spanned problems.
pub type Problems = SpanVec<Problem>;
/// A problem that arose in parsing or layouting.
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Problem {
/// How severe / important the problem is.
pub severity: Severity,
/// A message describing the problem.
pub message: String,
}
/// How severe / important a problem is.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum Severity {
/// Something in the code is not good.
Warning,
/// Something in the code is wrong!
Error,
}
impl Problem {
/// Create a new problem from message and severity.
pub fn new(message: impl Into<String>, severity: Severity) -> Self {
Self { message: message.into(), severity }
}
}
/// Construct a problem with `Error` severity.
///
/// ```
/// # use typstc::error;
/// # use typstc::syntax::span::Span;
/// # use typstc::Feedback;
/// # let span = Span::ZERO;
/// # let mut feedback = Feedback::new();
/// # let name = "";
/// // Create formatted error values.
/// let error = error!("expected {}", name);
///
/// // Create spanned errors.
/// let spanned = error!(span, "there is an error here");
///
/// // Create an error and directly add it to existing feedback.
/// error!(@feedback, span, "oh no!");
/// ```
#[macro_export]
macro_rules! error {
($($tts:tt)*) => {
$crate::__impl_problem!($crate::problem::Severity::Error; $($tts)*)
};
}
/// Construct a problem with `Warning` severity.
///
/// This works exactly like `error!`. See its documentation for more
/// information.
#[macro_export]
macro_rules! warning {
($($tts:tt)*) => {
$crate::__impl_problem!($crate::problem::Severity::Warning; $($tts)*)
};
}
/// Backs the `error!` and `warning!` macros.
#[macro_export]
#[doc(hidden)]
macro_rules! __impl_problem {
($severity:expr; @$feedback:expr, $($tts:tt)*) => {
$feedback.problems.push($crate::__impl_problem!($severity; $($tts)*));
};
($severity:expr; $fmt:literal $($tts:tt)*) => {
$crate::problem::Problem::new(format!($fmt $($tts)*), $severity)
};
($severity:expr; $span:expr, $fmt:literal $($tts:tt)*) => {
$crate::syntax::span::Spanned::new(
$crate::__impl_problem!($severity; $fmt $($tts)*),
$span,
)
};
}

View File

@ -6,7 +6,7 @@ use std::ops::Deref;
use std::str::FromStr;
use std::u8;
use crate::error::Errors;
use crate::problem::Problems;
use crate::size::Size;
use super::func::{Key, Value};
use super::span::{Span, Spanned};
@ -258,28 +258,28 @@ impl Tuple {
}
/// Extract (and remove) the first matching value and remove and generate
/// errors for all previous items that did not match.
pub fn get<V: Value>(&mut self, errors: &mut Errors) -> Option<V> {
/// problems for all previous items that did not match.
pub fn get<V: Value>(&mut self, problems: &mut Problems) -> Option<V> {
while !self.items.is_empty() {
let expr = self.items.remove(0);
let span = expr.span;
match V::parse(expr) {
Ok(output) => return Some(output),
Err(err) => errors.push(Spanned { v: err, span }),
Err(v) => problems.push(Spanned { v, span }),
}
}
None
}
/// Extract and return an iterator over all values that match and generate
/// errors for all items that do not match.
pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors)
/// problems for all items that do not match.
pub fn get_all<'a, V: Value>(&'a mut self, problems: &'a mut Problems)
-> impl Iterator<Item=V> + 'a {
self.items.drain(..).filter_map(move |expr| {
let span = expr.span;
match V::parse(expr) {
Ok(output) => Some(output),
Err(err) => { errors.push(Spanned { v: err, span }); None }
Err(v) => { problems.push(Spanned { v, span }); None }
}
})
}
@ -400,9 +400,9 @@ impl Object {
///
/// Inserts an error if the value does not match. If the key is not
/// contained, no error is inserted.
pub fn get<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V> {
pub fn get<V: Value>(&mut self, problems: &mut Problems, key: &str) -> Option<V> {
let index = self.pairs.iter().position(|pair| pair.v.key.v.as_str() == key)?;
self.get_index::<V>(errors, index)
self.get_index::<V>(problems, index)
}
/// Extract (and remove) a pair with a matching key and value.
@ -411,12 +411,12 @@ impl Object {
/// found, no error is inserted.
pub fn get_with_key<K: Key, V: Value>(
&mut self,
errors: &mut Errors,
problems: &mut Problems,
) -> Option<(K, V)> {
for (index, pair) in self.pairs.iter().enumerate() {
let key = Spanned { v: pair.v.key.v.as_str(), span: pair.v.key.span };
if let Some(key) = K::parse(key) {
return self.get_index::<V>(errors, index).map(|value| (key, value));
return self.get_index::<V>(problems, index).map(|value| (key, value));
}
}
None
@ -427,7 +427,7 @@ impl Object {
/// Inserts errors for values that do not match.
pub fn get_all<'a, K: Key, V: Value>(
&'a mut self,
errors: &'a mut Errors,
problems: &'a mut Problems,
) -> impl Iterator<Item=(K, V)> + 'a {
let mut index = 0;
std::iter::from_fn(move || {
@ -436,7 +436,7 @@ impl Object {
let key = Spanned { v: key.v.as_str(), span: key.span };
Some(if let Some(key) = K::parse(key) {
self.get_index::<V>(errors, index).map(|v| (key, v))
self.get_index::<V>(problems, index).map(|v| (key, v))
} else {
index += 1;
None
@ -456,20 +456,20 @@ impl Object {
/// ```
pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>(
&'a mut self,
errors: &'a mut Errors,
problems: &'a mut Problems,
) -> impl Iterator<Item=Spanned<(K, V)>> + 'a {
self.get_all::<Spanned<K>, Spanned<V>>(errors)
self.get_all::<Spanned<K>, Spanned<V>>(problems)
.map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span)))
}
/// Extract the argument at the given index and insert an error if the value
/// does not match.
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V> {
fn get_index<V: Value>(&mut self, problems: &mut Problems, index: usize) -> Option<V> {
let expr = self.pairs.remove(index).v.value;
let span = expr.span;
match V::parse(expr) {
Ok(output) => Some(output),
Err(err) => { errors.push(Spanned { v: err, span }); None }
Err(v) => { problems.push(Spanned { v, span }); None }
}
}

View File

@ -1,6 +1,6 @@
//! Deduplicating maps and keys for argument parsing.
use crate::error::Errors;
use crate::problem::Problems;
use crate::layout::prelude::*;
use crate::size::{PSize, ValueBox};
use crate::syntax::span::Spanned;
@ -12,7 +12,7 @@ use super::*;
/// A map which deduplicates redundant arguments.
///
/// Whenever a duplicate argument is inserted into the map, through the
/// functions `from_iter`, `insert` or `extend` an errors is added to the error
/// functions `from_iter`, `insert` or `extend` an problems is added to the error
/// list that needs to be passed to those functions.
///
/// All entries need to have span information to enable the error reporting.
@ -28,27 +28,27 @@ impl<K, V> DedupMap<K, V> where K: Eq {
}
/// Create a new map from an iterator of spanned keys and values.
pub fn from_iter<I>(errors: &mut Errors, iter: I) -> DedupMap<K, V>
pub fn from_iter<I>(problems: &mut Problems, iter: I) -> DedupMap<K, V>
where I: IntoIterator<Item=Spanned<(K, V)>> {
let mut map = DedupMap::new();
map.extend(errors, iter);
map.extend(problems, iter);
map
}
/// Add a spanned key-value pair.
pub fn insert(&mut self, errors: &mut Errors, entry: Spanned<(K, V)>) {
pub fn insert(&mut self, problems: &mut Problems, entry: Spanned<(K, V)>) {
if self.map.iter().any(|e| e.v.0 == entry.v.0) {
errors.push(err!(entry.span; "duplicate argument"));
problems.push(error!(entry.span, "duplicate argument"));
} else {
self.map.push(entry);
}
}
/// Add multiple spanned key-value pairs.
pub fn extend<I>(&mut self, errors: &mut Errors, items: I)
pub fn extend<I>(&mut self, problems: &mut Problems, items: I)
where I: IntoIterator<Item=Spanned<(K, V)>> {
for item in items.into_iter() {
self.insert(errors, item);
self.insert(problems, item);
}
}
@ -71,15 +71,15 @@ impl<K, V> DedupMap<K, V> where K: Eq {
}
/// Create a new map where keys and values are mapped to new keys and
/// values. When the mapping introduces new duplicates, errors are
/// values. When the mapping introduces new duplicates, problems are
/// generated.
pub fn dedup<F, K2, V2>(&self, errors: &mut Errors, mut f: F) -> DedupMap<K2, V2>
pub fn dedup<F, K2, V2>(&self, problems: &mut Problems, mut f: F) -> DedupMap<K2, V2>
where F: FnMut(&K, &V) -> (K2, V2), K2: Eq {
let mut map = DedupMap::new();
for Spanned { v: (key, value), span } in self.map.iter() {
let (key, value) = f(key, value);
map.insert(errors, Spanned { v: (key, value), span: *span });
map.insert(problems, Spanned { v: (key, value), span: *span });
}
map
@ -98,21 +98,21 @@ pub struct AxisMap<V>(DedupMap<AxisKey, V>);
impl<V: Value> AxisMap<V> {
/// Parse an axis map from the object.
pub fn parse<K>(
errors: &mut Errors,
problems: &mut Problems,
object: &mut Object,
) -> AxisMap<V> where K: Key + Into<AxisKey> {
let values: Vec<_> = object
.get_all_spanned::<K, V>(errors)
.get_all_spanned::<K, V>(problems)
.map(|s| s.map(|(k, v)| (k.into(), v)))
.collect();
AxisMap(DedupMap::from_iter(errors, values))
AxisMap(DedupMap::from_iter(problems, values))
}
/// Deduplicate from specific or generic to just specific axes.
pub fn dedup(&self, errors: &mut Errors, axes: LayoutAxes) -> DedupMap<SpecificAxis, V>
pub fn dedup(&self, problems: &mut Problems, axes: LayoutAxes) -> DedupMap<SpecificAxis, V>
where V: Clone {
self.0.dedup(errors, |key, val| (key.to_specific(axes), val.clone()))
self.0.dedup(problems, |key, val| (key.to_specific(axes), val.clone()))
}
}
@ -124,23 +124,23 @@ pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
impl<V: Value> PosAxisMap<V> {
/// Parse a positional/axis map from the function arguments.
pub fn parse<K>(
errors: &mut Errors,
problems: &mut Problems,
args: &mut FuncArgs,
) -> PosAxisMap<V> where K: Key + Into<AxisKey> {
let mut map = DedupMap::new();
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(errors) {
map.insert(errors, Spanned { v: (key, v), span })
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<V>>(problems) {
map.insert(problems, Spanned { v: (key, v), span })
}
}
let keywords: Vec<_> = args.key
.get_all_spanned::<K, V>(errors)
.get_all_spanned::<K, V>(problems)
.map(|s| s.map(|(k, v)| (PosAxisKey::Keyword(k.into()), v)))
.collect();
map.extend(errors, keywords);
map.extend(problems, keywords);
PosAxisMap(map)
}
@ -149,7 +149,7 @@ impl<V: Value> PosAxisMap<V> {
/// or specific axes to just generic axes.
pub fn dedup<F>(
&self,
errors: &mut Errors,
problems: &mut Problems,
axes: LayoutAxes,
mut f: F,
) -> DedupMap<GenericAxis, V>
@ -157,7 +157,7 @@ impl<V: Value> PosAxisMap<V> {
F: FnMut(&V) -> Option<GenericAxis>,
V: Clone,
{
self.0.dedup(errors, |key, val| {
self.0.dedup(problems, |key, val| {
(match key {
PosAxisKey::First => f(val).unwrap_or(GenericAxis::Primary),
PosAxisKey::Second => f(val).unwrap_or(GenericAxis::Secondary),
@ -175,20 +175,20 @@ pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<PSize>>);
impl PaddingMap {
/// Parse a padding map from the function arguments.
pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap {
pub fn parse(problems: &mut Problems, args: &mut FuncArgs) -> PaddingMap {
let mut map = DedupMap::new();
let all = args.pos.get::<Spanned<Defaultable<PSize>>>(errors);
let all = args.pos.get::<Spanned<Defaultable<PSize>>>(problems);
if let Some(Spanned { v, span }) = all {
map.insert(errors, Spanned { v: (PaddingKey::All, v.into()), span });
map.insert(problems, Spanned { v: (PaddingKey::All, v.into()), span });
}
let paddings: Vec<_> = args.key
.get_all_spanned::<PaddingKey<AxisKey>, Defaultable<PSize>>(errors)
.get_all_spanned::<PaddingKey<AxisKey>, Defaultable<PSize>>(problems)
.map(|s| s.map(|(k, v)| (k, v.into())))
.collect();
map.extend(errors, paddings);
map.extend(problems, paddings);
PaddingMap(map)
}
@ -196,13 +196,13 @@ impl PaddingMap {
/// Apply the specified padding on a value box of optional, scalable sizes.
pub fn apply(
&self,
errors: &mut Errors,
problems: &mut Problems,
axes: LayoutAxes,
padding: &mut ValueBox<Option<PSize>>
) {
use PaddingKey::*;
let map = self.0.dedup(errors, |key, &val| {
let map = self.0.dedup(problems, |key, &val| {
(match key {
All => All,
Both(axis) => Both(axis.to_specific(axes)),

View File

@ -1,7 +1,7 @@
//! Primitives for argument parsing in library functions.
use std::iter::FromIterator;
use crate::error::{Error, Errors};
use crate::problem::{Problem, Problems};
use super::expr::{Expr, Ident, Tuple, Object, Pair};
use super::span::{Span, Spanned};
@ -84,13 +84,13 @@ pub enum FuncArg {
pub trait OptionExt: Sized {
/// Add an error about a missing argument `arg` with the given span if the
/// option is `None`.
fn or_missing(self, errors: &mut Errors, span: Span, arg: &str) -> Self;
fn or_missing(self, problems: &mut Problems, span: Span, arg: &str) -> Self;
}
impl<T> OptionExt for Option<T> {
fn or_missing(self, errors: &mut Errors, span: Span, arg: &str) -> Self {
fn or_missing(self, problems: &mut Problems, span: Span, arg: &str) -> Self {
if self.is_none() {
errors.push(err!(span; "missing argument: {}", arg));
problems.push(error!(span, "missing argument: {}", arg));
}
self
}

View File

@ -23,20 +23,20 @@ use self::AlignmentValue::*;
/// # Example implementation
/// An implementation for `bool` might look as follows:
/// ```
/// # use typstc::err;
/// # use typstc::error::Error;
/// # use typstc::error;
/// # use typstc::problem::Problem;
/// # use typstc::syntax::expr::Expr;
/// # use typstc::syntax::func::Value;
/// # use typstc::syntax::span::Spanned;
/// # struct Bool; /*
/// impl Value for bool {
/// # */ impl Value for Bool {
/// fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
/// fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
/// match expr.v {
/// # /*
/// Expr::Bool(b) => Ok(b),
/// # */ Expr::Bool(_) => Ok(Bool),
/// other => Err(err!("expected bool, found {}", other.name())),
/// other => Err(error!("expected bool, found {}", other.name())),
/// }
/// }
/// }
@ -44,11 +44,11 @@ use self::AlignmentValue::*;
pub trait Value: Sized {
/// Parse an expression into this value or return an error if the expression
/// is valid for this value type.
fn parse(expr: Spanned<Expr>) -> Result<Self, Error>;
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem>;
}
impl<V: Value> Value for Spanned<V> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
let span = expr.span;
V::parse(expr).map(|v| Spanned { v, span })
}
@ -58,12 +58,13 @@ impl<V: Value> Value for Spanned<V> {
macro_rules! value {
($type:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
impl Value for $type {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
#[allow(unreachable_patterns)]
match expr.v {
$($p => Ok($r)),*,
other => Err(err!("expected {}, found {}",
$name, other.name())),
other => Err(
error!("expected {}, found {}", $name, other.name())
),
}
}
}
@ -120,7 +121,7 @@ impl From<StringLike> for String {
pub struct Defaultable<V>(pub Option<V>);
impl<V: Value> Value for Defaultable<V> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
Ok(Defaultable(match expr.v {
Expr::Ident(ident) if ident.as_str() == "default" => None,
_ => Some(V::parse(expr)?)
@ -135,16 +136,16 @@ impl<V> From<Defaultable<V>> for Option<V> {
}
impl Value for FontStyle {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
FontStyle::from_name(Ident::parse(expr)?.as_str())
.ok_or_else(|| err!("invalid font style"))
.ok_or_else(|| error!("invalid font style"))
}
}
/// The additional boolean specifies whether a number was clamped into the range
/// 100 - 900 to make it a valid font weight.
impl Value for (FontWeight, bool) {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
match expr.v {
Expr::Number(weight) => {
let weight = weight.round();
@ -158,30 +159,31 @@ impl Value for (FontWeight, bool) {
}
Expr::Ident(id) => {
FontWeight::from_name(id.as_str())
.ok_or_else(|| err!("invalid font weight"))
.ok_or_else(|| error!("invalid font weight"))
.map(|weight| (weight, false))
}
other => Err(err!("expected identifier or number, \
found {}", other.name())),
other => Err(
error!("expected identifier or number, found {}", other.name())
),
}
}
}
impl Value for Paper {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
Paper::from_name(Ident::parse(expr)?.as_str())
.ok_or_else(|| err!("invalid paper type"))
.ok_or_else(|| error!("invalid paper type"))
}
}
impl Value for Direction {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
Ok(match Ident::parse(expr)?.as_str() {
"left-to-right" | "ltr" | "LTR" => LeftToRight,
"right-to-left" | "rtl" | "RTL" => RightToLeft,
"top-to-bottom" | "ttb" | "TTB" => TopToBottom,
"bottom-to-top" | "btt" | "BTT" => BottomToTop,
_ => return Err(err!("invalid direction"))
_ => return Err(error!("invalid direction"))
})
}
}
@ -248,7 +250,7 @@ impl AlignmentValue {
}
impl Value for AlignmentValue {
fn parse(expr: Spanned<Expr>) -> Result<Self, Error> {
fn parse(expr: Spanned<Expr>) -> Result<Self, Problem> {
Ok(match Ident::parse(expr)?.as_str() {
"origin" => Align(Origin),
"center" => Align(Center),
@ -257,7 +259,7 @@ impl Value for AlignmentValue {
"top" => Top,
"right" => Right,
"bottom" => Bottom,
_ => return Err(err!("invalid alignment"))
_ => return Err(error!("invalid alignment"))
})
}
}

View File

@ -49,8 +49,7 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Pass<SyntaxModel>
feedback.extend_offset(span.start, parsed.feedback);
if !terminated {
feedback.errors.push(err!(Span::at(span.end);
"expected closing bracket"));
error!(@feedback, Span::at(span.end), "expected closing bracket");
}
parsed.output
@ -62,8 +61,7 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Pass<SyntaxModel>
Token::Raw { raw, terminated } => {
if !terminated {
feedback.errors.push(err!(Span::at(span.end);
"expected backtick"));
error!(@feedback, Span::at(span.end), "expected backtick");
}
Node::Raw(unescape_raw(raw))
@ -72,7 +70,7 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Pass<SyntaxModel>
Token::Text(text) => Node::Text(text.to_string()),
other => {
feedback.errors.push(err!(span; "unexpected {}", other.name()));
error!(@feedback, span, "unexpected {}", other.name());
continue;
}
};
@ -129,7 +127,7 @@ impl<'s> FuncParser<'s> {
// The fallback parser was returned. Invalid function.
Err(parser) => {
self.feedback.errors.push(err!(header.name.span; "unknown function"));
error!(@self.feedback, header.name.span, "unknown function");
(parser, Decoration::InvalidFuncName)
}
};
@ -270,10 +268,10 @@ impl<'s> FuncParser<'s> {
let expr = binop(Box::new(o1), Box::new(o2));
return Some(Spanned::new(expr, span));
} else {
self.feedback.errors.push(err!(
Span::merge(next.span, o1.span);
error!(
@self.feedback, Span::merge(next.span, o1.span),
"missing right {}", operand_name,
));
);
}
}
}
@ -292,7 +290,7 @@ impl<'s> FuncParser<'s> {
let span = Span::merge(first.span, factor.span);
Some(Spanned::new(Expr::Neg(Box::new(factor)), span))
} else {
self.feedback.errors.push(err!(first.span; "dangling minus"));
error!(@self.feedback, first.span, "dangling minus");
None
}
} else {
@ -333,7 +331,7 @@ impl<'s> FuncParser<'s> {
take!(Expr::Color(color))
} else {
// Heal color by assuming black
self.feedback.errors.push(err!(first.span; "invalid color"));
error!(@self.feedback, first.span, "invalid color");
take!(Expr::Color(RgbaColor::new_healed(0, 0, 0, 255)))
}
},
@ -517,14 +515,16 @@ impl<'s> FuncParser<'s> {
/// Add an error about an expected `thing` which was not found, showing
/// what was found instead.
fn expected_found(&mut self, thing: &str, found: Spanned<Token>) {
self.feedback.errors.push(err!(found.span;
"expected {}, found {}", thing, found.v.name()));
error!(
@self.feedback, found.span,
"expected {}, found {}", thing, found.v.name(),
);
}
/// Add an error about an `thing` which was expected but not found at the
/// given position.
fn expected_at(&mut self, thing: &str, pos: Position) {
self.feedback.errors.push(err!(Span::at(pos); "expected {}", thing));
error!(@self.feedback, Span::at(pos), "expected {}", thing);
}
/// Add a expected-found-error if `found` is `Some` and an expected-error
@ -726,7 +726,7 @@ mod tests {
p!($source => [$($model)*], []);
};
($source:expr => [$($model:tt)*], [$($errors:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
($source:expr => [$($model:tt)*], [$($problems:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
let mut scope = Scope::new::<DebugFn>();
scope.add::<DebugFn>("f");
scope.add::<DebugFn>("n");
@ -740,12 +740,12 @@ mod tests {
let (exp, cmp) = spanned![vec $($model)*];
check($source, exp, pass.output.nodes, cmp);
// Test errors
let (exp, cmp) = spanned![vec $($errors)*];
// Test problems
let (exp, cmp) = spanned![vec $($problems)*];
let exp = exp.into_iter()
.map(|s: Spanned<&str>| s.map(|e| e.to_string()))
.collect::<Vec<_>>();
let found = pass.feedback.errors.into_iter()
let found = pass.feedback.problems.into_iter()
.map(|s| s.map(|e| e.message))
.collect::<Vec<_>>();
check($source, exp, found, cmp);

View File

@ -76,7 +76,7 @@ pub struct Span {
}
impl Span {
/// A dummy span.
/// The zero span.
pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO };
/// Create a new span from start and end positions.

View File

@ -29,7 +29,7 @@ where T: Debug + PartialEq + SpanlessEq {
/// spanned![(0:0, 0:5, "hello"), (0:5, 0:3, "world")]
/// ```
/// The span information can simply be omitted to create a vector with items
/// that are spanned with dummy zero spans.
/// that are spanned with zero spans.
macro_rules! spanned {
(item ($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => ({
#[allow(unused_imports)]
@ -80,7 +80,7 @@ function! {
}
}
layout(self, ctx, errors) { vec![] }
layout(self, ctx, f) { vec![] }
}
/// Compares elements by only looking at values and ignoring spans.

View File

@ -123,14 +123,14 @@ fn test(name: &str, src: &str) -> DynResult<()> {
fn compile(typesetter: &Typesetter, src: &str) -> MultiLayout {
if cfg!(debug_assertions) {
let typeset = block_on(typesetter.typeset(src));
let errors = typeset.feedback.errors;
let problems = typeset.feedback.problems;
if !errors.is_empty() {
for error in errors {
if !problems.is_empty() {
for problem in problems {
println!(" {:?} {:?}: {}",
error.v.severity,
error.span,
error.v.message
problem.v.severity,
problem.span,
problem.v.message
);
}
}