Improve error handling

This commit is contained in:
Martin Haug 2021-10-31 16:22:33 +01:00
parent 1c0ac793d2
commit c569e14c07
8 changed files with 122 additions and 159 deletions

View File

@ -44,7 +44,13 @@ fn bench_scan(iai: &mut Iai) {
} }
fn bench_tokenize(iai: &mut Iai) { fn bench_tokenize(iai: &mut Iai) {
iai.run(|| Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count()); iai.run(|| {
Tokens::new(
black_box(&SourceFile::detached(SRC)),
black_box(TokenMode::Markup),
)
.count()
});
} }
fn bench_parse(iai: &mut Iai) { fn bench_parse(iai: &mut Iai) {
@ -53,7 +59,7 @@ fn bench_parse(iai: &mut Iai) {
fn bench_eval(iai: &mut Iai) { fn bench_eval(iai: &mut Iai) {
let (mut ctx, id) = context(); let (mut ctx, id) = context();
let ast = ctx.parse(id).unwrap(); let ast = ctx.sources.get(id).ast().unwrap();
iai.run(|| eval(&mut ctx, id, &ast).unwrap()); iai.run(|| eval(&mut ctx, id, &ast).unwrap());
} }

View File

@ -16,7 +16,7 @@ pub trait Walk {
impl Walk for Markup { impl Walk for Markup {
fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> {
for node in self.iter() { for node in self.nodes() {
node.walk(ctx)?; node.walk(ctx)?;
} }
Ok(()) Ok(())

View File

@ -138,8 +138,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) {
NodeKind::LeftBracket => template(p), NodeKind::LeftBracket => template(p),
// Comments. // Comments.
NodeKind::LineComment | NodeKind::BlockComment => p.eat(), NodeKind::LineComment | NodeKind::BlockComment | NodeKind::Error(_, _) => p.eat(),
NodeKind::Error(t, e) if t != &ErrorPosition::Full || e.contains(' ') => p.eat(),
_ => { _ => {
*at_start = false; *at_start = false;
@ -319,7 +318,7 @@ fn primary(p: &mut Parser, atomic: bool) {
Some(NodeKind::Import) => import_expr(p), Some(NodeKind::Import) => import_expr(p),
Some(NodeKind::Include) => include_expr(p), Some(NodeKind::Include) => include_expr(p),
Some(NodeKind::Error(t, e)) if t != &ErrorPosition::Full || e.contains(' ') => { Some(NodeKind::Error(_, _)) => {
p.eat(); p.eat();
} }
@ -333,13 +332,9 @@ fn primary(p: &mut Parser, atomic: bool) {
/// Parse a literal. /// Parse a literal.
fn literal(p: &mut Parser) -> bool { fn literal(p: &mut Parser) -> bool {
let peeked = match p.peek() { match p.peek() {
Some(x) => x.clone(),
None => return false,
};
match peeked {
// Basic values. // Basic values.
Some(
NodeKind::None NodeKind::None
| NodeKind::Auto | NodeKind::Auto
| NodeKind::Int(_) | NodeKind::Int(_)
@ -349,12 +344,14 @@ fn literal(p: &mut Parser) -> bool {
| NodeKind::Length(_, _) | NodeKind::Length(_, _)
| NodeKind::Angle(_, _) | NodeKind::Angle(_, _)
| NodeKind::Percentage(_) | NodeKind::Percentage(_)
| NodeKind::Str(_) => p.eat(), | NodeKind::Str(_),
) => {
_ => return false, p.eat();
true
} }
true _ => false,
}
} }
/// Parse something that starts with a parenthesis, which can be either of: /// Parse something that starts with a parenthesis, which can be either of:
@ -395,11 +392,11 @@ fn parenthesized(p: &mut Parser) {
// Find out which kind of collection this is. // Find out which kind of collection this is.
match kind { match kind {
CollectionKind::Group => p.end(NodeKind::Group), CollectionKind::Group => p.end(NodeKind::Group),
CollectionKind::PositionalCollection => { CollectionKind::Positional => {
p.lift(); p.lift();
array(p, token_count); array(p, token_count);
} }
CollectionKind::NamedCollection => { CollectionKind::Named => {
p.lift(); p.lift();
dict(p, token_count); dict(p, token_count);
} }
@ -413,9 +410,9 @@ enum CollectionKind {
Group, Group,
/// The collection starts with a positional and has more items or a trailing /// The collection starts with a positional and has more items or a trailing
/// comma. /// comma.
PositionalCollection, Positional,
/// The collection starts with a named item. /// The collection starts with a named item.
NamedCollection, Named,
} }
/// Parse a collection. /// Parse a collection.
@ -424,20 +421,19 @@ enum CollectionKind {
/// commas. /// commas.
fn collection(p: &mut Parser) -> (CollectionKind, usize) { fn collection(p: &mut Parser) -> (CollectionKind, usize) {
let mut items = 0; let mut items = 0;
let mut kind = CollectionKind::PositionalCollection; let mut kind = CollectionKind::Positional;
let mut seen_spread = false;
let mut has_comma = false; let mut has_comma = false;
let mut missing_coma = None; let mut missing_coma = None;
while !p.eof() { while !p.eof() {
let item_kind = item(p); let item_kind = item(p);
if p.success() { if p.success() {
if items == 0 && item_kind == CollectionItemKind::Named { if items == 0 && item_kind == NodeKind::Named {
kind = CollectionKind::NamedCollection; kind = CollectionKind::Named;
} }
if item_kind == CollectionItemKind::ParameterSink { if item_kind == NodeKind::ParameterSink {
seen_spread = true; has_comma = true;
} }
items += 1; items += 1;
@ -458,42 +454,27 @@ fn collection(p: &mut Parser) -> (CollectionKind, usize) {
} }
} }
if !has_comma if !has_comma && items == 1 && kind == CollectionKind::Positional {
&& items == 1
&& !seen_spread
&& kind == CollectionKind::PositionalCollection
{
kind = CollectionKind::Group; kind = CollectionKind::Group;
} }
(kind, items) (kind, items)
} }
/// What kind of item is this?
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum CollectionItemKind {
/// A named item.
Named,
/// An unnamed item.
Unnamed,
/// A parameter sink.
ParameterSink,
}
/// Parse an expression or a named pair. Returns if this is a named pair. /// Parse an expression or a named pair. Returns if this is a named pair.
fn item(p: &mut Parser) -> CollectionItemKind { fn item(p: &mut Parser) -> NodeKind {
p.start(); p.start();
if p.eat_if(&NodeKind::Dots) { if p.eat_if(&NodeKind::Dots) {
expr(p); expr(p);
p.end_or_abort(NodeKind::ParameterSink); p.end_or_abort(NodeKind::ParameterSink);
return CollectionItemKind::ParameterSink; return NodeKind::ParameterSink;
} }
expr(p); expr(p);
if p.may_lift_abort() { if p.may_lift_abort() {
return CollectionItemKind::Unnamed; return NodeKind::None;
} }
if p.eat_if(&NodeKind::Colon) { if p.eat_if(&NodeKind::Colon) {
@ -512,10 +493,10 @@ fn item(p: &mut Parser) -> CollectionItemKind {
p.unsuccessful(); p.unsuccessful();
} }
CollectionItemKind::Named NodeKind::Named
} else { } else {
p.lift(); p.lift();
CollectionItemKind::Unnamed p.last_child().unwrap().kind().clone()
} }
} }

View File

@ -91,7 +91,7 @@ impl<'s> Iterator for Tokens<'s> {
'/' if self.s.eat_if('*') => self.block_comment(), '/' if self.s.eat_if('*') => self.block_comment(),
'/' if !self.maybe_in_url() && self.s.eat_if('/') => self.line_comment(), '/' if !self.maybe_in_url() && self.s.eat_if('/') => self.line_comment(),
'*' if self.s.eat_if('/') => { '*' if self.s.eat_if('/') => {
NodeKind::Error(ErrorPosition::Full, self.s.eaten_from(start).into()) NodeKind::Unknown(self.s.eaten_from(start).into())
} }
// Other things. // Other things.
@ -173,7 +173,7 @@ impl<'s> Tokens<'s> {
// Strings. // Strings.
'"' => self.string(), '"' => self.string(),
_ => NodeKind::Error(ErrorPosition::Full, self.s.eaten_from(start).into()), _ => NodeKind::Unknown(self.s.eaten_from(start).into()),
} }
} }
@ -398,10 +398,10 @@ impl<'s> Tokens<'s> {
} else { } else {
NodeKind::Error( NodeKind::Error(
ErrorPosition::End, ErrorPosition::End,
if display { if !display || (!escaped && dollar) {
"expected closing dollar sign" "expected closing dollar sign"
} else { } else {
"expected display math closure sequence" "expected closing bracket and dollar sign"
} }
.into(), .into(),
) )
@ -466,11 +466,11 @@ impl<'s> Tokens<'s> {
"deg" => NodeKind::Angle(f, AngularUnit::Deg), "deg" => NodeKind::Angle(f, AngularUnit::Deg),
"rad" => NodeKind::Angle(f, AngularUnit::Rad), "rad" => NodeKind::Angle(f, AngularUnit::Rad),
_ => { _ => {
return NodeKind::Error(ErrorPosition::Full, all.into()); return NodeKind::Unknown(all.into());
} }
} }
} else { } else {
NodeKind::Error(ErrorPosition::Full, all.into()) NodeKind::Unknown(all.into())
} }
} }
@ -575,45 +575,31 @@ mod tests {
text: &str, text: &str,
lang: Option<&str>, lang: Option<&str>,
backticks_left: u8, backticks_left: u8,
backticks_right: u8, err_msg: Option<&str>,
block: bool, block: bool,
) -> NodeKind { ) -> NodeKind {
if backticks_left == backticks_right { match err_msg {
NodeKind::Raw(Rc::new(RawToken { None => NodeKind::Raw(Rc::new(RawToken {
text: text.into(), text: text.into(),
lang: lang.map(Into::into), lang: lang.map(Into::into),
backticks: backticks_left, backticks: backticks_left,
block, block,
})) })),
} else { Some(msg) => {
let remaining = backticks_left - backticks_right; NodeKind::Error(ErrorPosition::End, format!("expected {}", msg).into())
let noun = if remaining == 1 { "backtick" } else { "backticks" };
NodeKind::Error(
ErrorPosition::End,
if backticks_right == 0 {
format!("expected {} {}", remaining, noun)
} else {
format!("expected {} more {}", remaining, noun)
} }
.into(),
)
} }
} }
fn Math(formula: &str, display: bool, terminated: bool) -> NodeKind { fn Math(formula: &str, display: bool, err_msg: Option<&str>) -> NodeKind {
if terminated { match err_msg {
None => {
NodeKind::Math(Rc::new(MathToken { formula: formula.into(), display })) NodeKind::Math(Rc::new(MathToken { formula: formula.into(), display }))
} else {
NodeKind::Error(
ErrorPosition::End,
if display {
"expected closing dollar sign"
} else {
"expected display math closure sequence"
} }
.into(), Some(msg) => NodeKind::Error(
) ErrorPosition::End,
format!("expected closing {}", msg).into(),
),
} }
} }
@ -634,7 +620,7 @@ mod tests {
} }
fn Invalid(invalid: &str) -> NodeKind { fn Invalid(invalid: &str) -> NodeKind {
NodeKind::Error(ErrorPosition::Full, invalid.into()) NodeKind::Unknown(invalid.into())
} }
/// Building blocks for suffix testing. /// Building blocks for suffix testing.
@ -687,7 +673,7 @@ mod tests {
('/', None, "//", LineComment), ('/', None, "//", LineComment),
('/', None, "/**/", BlockComment), ('/', None, "/**/", BlockComment),
('/', Some(Markup), "*", Strong), ('/', Some(Markup), "*", Strong),
('/', Some(Markup), "$ $", Math(" ", false, true)), ('/', Some(Markup), "$ $", Math(" ", false, None)),
('/', Some(Markup), r"\\", Text("\\")), ('/', Some(Markup), r"\\", Text("\\")),
('/', Some(Markup), "#let", Let), ('/', Some(Markup), "#let", Let),
('/', Some(Code), "(", LeftParen), ('/', Some(Code), "(", LeftParen),
@ -908,42 +894,42 @@ mod tests {
#[test] #[test]
fn test_tokenize_raw_blocks() { fn test_tokenize_raw_blocks() {
// Test basic raw block. // Test basic raw block.
t!(Markup: "``" => Raw("", None, 1, 1, false)); t!(Markup: "``" => Raw("", None, 1, None, false));
t!(Markup: "`raw`" => Raw("raw", None, 1, 1, false)); t!(Markup: "`raw`" => Raw("raw", None, 1, None, false));
t!(Markup[""]: "`]" => Raw("]", None, 1, 0, false)); t!(Markup[""]: "`]" => Raw("]", None, 1, Some("1 backtick"), false));
// Test special symbols in raw block. // Test special symbols in raw block.
t!(Markup: "`[brackets]`" => Raw("[brackets]", None, 1, 1, false)); t!(Markup: "`[brackets]`" => Raw("[brackets]", None, 1, None, false));
t!(Markup[""]: r"`\`` " => Raw(r"\", None, 1, 1, false), Raw(" ", None, 1, 0, false)); t!(Markup[""]: r"`\`` " => Raw(r"\", None, 1, None, false), Raw(" ", None, 1, Some("1 backtick"), false));
// Test separated closing backticks. // Test separated closing backticks.
t!(Markup: "```not `y`e`t```" => Raw("`y`e`t", Some("not"), 3, 3, false)); t!(Markup: "```not `y`e`t```" => Raw("`y`e`t", Some("not"), 3, None, false));
// Test more backticks. // Test more backticks.
t!(Markup: "``nope``" => Raw("", None, 1, 1, false), Text("nope"), Raw("", None, 1, 1, false)); t!(Markup: "``nope``" => Raw("", None, 1, None, false), Text("nope"), Raw("", None, 1, None, false));
t!(Markup: "````🚀````" => Raw("", Some("🚀"), 4, 4, false)); t!(Markup: "````🚀````" => Raw("", Some("🚀"), 4, None, false));
t!(Markup[""]: "`````👩‍🚀````noend" => Raw("````noend", Some("👩‍🚀"), 5, 0, false)); t!(Markup[""]: "`````👩‍🚀````noend" => Raw("````noend", Some("👩‍🚀"), 5, Some("5 backticks"), false));
t!(Markup[""]: "````raw``````" => Raw("", Some("raw"), 4, 4, false), Raw("", None, 1, 1, false)); t!(Markup[""]: "````raw``````" => Raw("", Some("raw"), 4, None, false), Raw("", None, 1, None, false));
} }
#[test] #[test]
fn test_tokenize_math_formulas() { fn test_tokenize_math_formulas() {
// Test basic formula. // Test basic formula.
t!(Markup: "$$" => Math("", false, true)); t!(Markup: "$$" => Math("", false, None));
t!(Markup: "$x$" => Math("x", false, true)); t!(Markup: "$x$" => Math("x", false, None));
t!(Markup: r"$\\$" => Math(r"\\", false, true)); t!(Markup: r"$\\$" => Math(r"\\", false, None));
t!(Markup: "$[x + y]$" => Math("x + y", true, true)); t!(Markup: "$[x + y]$" => Math("x + y", true, None));
t!(Markup: r"$[\\]$" => Math(r"\\", true, true)); t!(Markup: r"$[\\]$" => Math(r"\\", true, None));
// Test unterminated. // Test unterminated.
t!(Markup[""]: "$x" => Math("x", false, false)); t!(Markup[""]: "$x" => Math("x", false, Some("dollar sign")));
t!(Markup[""]: "$[x" => Math("x", true, false)); t!(Markup[""]: "$[x" => Math("x", true, Some("bracket and dollar sign")));
t!(Markup[""]: "$[x]\n$" => Math("x]\n$", true, false)); t!(Markup[""]: "$[x]\n$" => Math("x]\n$", true, Some("bracket and dollar sign")));
// Test escape sequences. // Test escape sequences.
t!(Markup: r"$\$x$" => Math(r"\$x", false, true)); t!(Markup: r"$\$x$" => Math(r"\$x", false, None));
t!(Markup: r"$[\\\]$]$" => Math(r"\\\]$", true, true)); t!(Markup: r"$[\\\]$]$" => Math(r"\\\]$", true, None));
t!(Markup[""]: r"$[ ]\\$" => Math(r" ]\\$", true, false)); t!(Markup[""]: r"$[ ]\\$" => Math(r" ]\\$", true, Some("bracket and dollar sign")));
} }
#[test] #[test]

View File

@ -87,32 +87,24 @@ impl Expr {
impl TypedNode for Expr { impl TypedNode for Expr {
fn cast_from(node: RedRef) -> Option<Self> { fn cast_from(node: RedRef) -> Option<Self> {
match node.kind() { match node.kind() {
NodeKind::Ident(_) => Some(Self::Ident(Ident::cast_from(node).unwrap())), NodeKind::Ident(_) => node.cast().map(Self::Ident),
NodeKind::Array => Some(Self::Array(ArrayExpr::cast_from(node).unwrap())), NodeKind::Array => node.cast().map(Self::Array),
NodeKind::Dict => Some(Self::Dict(DictExpr::cast_from(node).unwrap())), NodeKind::Dict => node.cast().map(Self::Dict),
NodeKind::Template => { NodeKind::Template => node.cast().map(Self::Template),
Some(Self::Template(TemplateExpr::cast_from(node).unwrap())) NodeKind::Group => node.cast().map(Self::Group),
} NodeKind::Block => node.cast().map(Self::Block),
NodeKind::Group => Some(Self::Group(GroupExpr::cast_from(node).unwrap())), NodeKind::Unary => node.cast().map(Self::Unary),
NodeKind::Block => Some(Self::Block(BlockExpr::cast_from(node).unwrap())), NodeKind::Binary => node.cast().map(Self::Binary),
NodeKind::Unary => Some(Self::Unary(UnaryExpr::cast_from(node).unwrap())), NodeKind::Call => node.cast().map(Self::Call),
NodeKind::Binary => Some(Self::Binary(BinaryExpr::cast_from(node).unwrap())), NodeKind::Closure => node.cast().map(Self::Closure),
NodeKind::Call => Some(Self::Call(CallExpr::cast_from(node).unwrap())), NodeKind::WithExpr => node.cast().map(Self::With),
NodeKind::Closure => { NodeKind::LetExpr => node.cast().map(Self::Let),
Some(Self::Closure(ClosureExpr::cast_from(node).unwrap())) NodeKind::IfExpr => node.cast().map(Self::If),
} NodeKind::WhileExpr => node.cast().map(Self::While),
NodeKind::WithExpr => Some(Self::With(WithExpr::cast_from(node).unwrap())), NodeKind::ForExpr => node.cast().map(Self::For),
NodeKind::LetExpr => Some(Self::Let(LetExpr::cast_from(node).unwrap())), NodeKind::ImportExpr => node.cast().map(Self::Import),
NodeKind::IfExpr => Some(Self::If(IfExpr::cast_from(node).unwrap())), NodeKind::IncludeExpr => node.cast().map(Self::Include),
NodeKind::WhileExpr => Some(Self::While(WhileExpr::cast_from(node).unwrap())), _ => node.cast().map(Self::Lit),
NodeKind::ForExpr => Some(Self::For(ForExpr::cast_from(node).unwrap())),
NodeKind::ImportExpr => {
Some(Self::Import(ImportExpr::cast_from(node).unwrap()))
}
NodeKind::IncludeExpr => {
Some(Self::Include(IncludeExpr::cast_from(node).unwrap()))
}
_ => Some(Self::Lit(Lit::cast_from(node)?)),
} }
} }
} }

View File

@ -3,17 +3,14 @@ use crate::node;
use crate::util::EcoString; use crate::util::EcoString;
use std::fmt::Write; use std::fmt::Write;
node! {
/// The syntactical root capable of representing a full parsed document. /// The syntactical root capable of representing a full parsed document.
pub type Markup = Vec<MarkupNode>; Markup
impl TypedNode for Markup {
fn cast_from(node: RedRef) -> Option<Self> {
if node.kind() != &NodeKind::Markup {
return None;
} }
let children = node.children().filter_map(TypedNode::cast_from).collect(); impl Markup {
Some(children) pub fn nodes<'a>(&'a self) -> impl Iterator<Item = MarkupNode> + 'a {
self.0.children().filter_map(RedRef::cast)
} }
} }
@ -66,14 +63,12 @@ impl TypedNode for MarkupNode {
NodeKind::NonBreakingSpace => { NodeKind::NonBreakingSpace => {
Some(MarkupNode::Text(EcoString::from("\u{00A0}"))) Some(MarkupNode::Text(EcoString::from("\u{00A0}")))
} }
NodeKind::Raw(_) => Some(MarkupNode::Raw(RawNode::cast_from(node).unwrap())), NodeKind::Raw(_) => node.cast().map(MarkupNode::Raw),
NodeKind::Heading => { NodeKind::Heading => node.cast().map(MarkupNode::Heading),
Some(MarkupNode::Heading(HeadingNode::cast_from(node).unwrap())) NodeKind::List => node.cast().map(MarkupNode::List),
} NodeKind::Enum => node.cast().map(MarkupNode::Enum),
NodeKind::List => Some(MarkupNode::List(ListNode::cast_from(node).unwrap())),
NodeKind::Enum => Some(MarkupNode::Enum(EnumNode::cast_from(node).unwrap())),
NodeKind::Error(_, _) => None, NodeKind::Error(_, _) => None,
_ => Some(MarkupNode::Expr(Expr::cast_from(node)?)), _ => node.cast().map(MarkupNode::Expr),
} }
} }
} }

View File

@ -162,6 +162,8 @@ pub enum NodeKind {
BlockComment, BlockComment,
/// Tokens that appear in the wrong place. /// Tokens that appear in the wrong place.
Error(ErrorPosition, EcoString), Error(ErrorPosition, EcoString),
/// Unknown character sequences.
Unknown(EcoString),
/// Template markup. /// Template markup.
Markup, Markup,
/// A forced line break: `\`. /// A forced line break: `\`.
@ -375,10 +377,11 @@ impl NodeKind {
Self::ImportExpr => "import expression", Self::ImportExpr => "import expression",
Self::ImportItems => "import items", Self::ImportItems => "import items",
Self::IncludeExpr => "include expression", Self::IncludeExpr => "include expression",
Self::Error(_, src) => match src.as_str() { Self::Unknown(src) => match src.as_str() {
"*/" => "end of block comment", "*/" => "end of block comment",
_ => "invalid token", _ => "invalid token",
}, },
Self::Error(_, _) => "parse error",
} }
} }
} }

View File

@ -82,7 +82,7 @@ impl Write for Printer {
impl Pretty for Markup { impl Pretty for Markup {
fn pretty(&self, p: &mut Printer) { fn pretty(&self, p: &mut Printer) {
for node in self { for node in self.nodes() {
node.pretty(p); node.pretty(p);
} }
} }