From cf2e527a026e81269ef716b4d6675ae6d981d681 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Fri, 5 Nov 2021 12:53:52 +0100 Subject: [PATCH] Code Review: No Patrick, question marks are not an instrument --- src/eval/walk.rs | 2 +- src/parse/mod.rs | 417 ++++++++++++++++------------------- src/parse/parser.rs | 133 +++++------ src/syntax/ast.rs | 6 +- src/syntax/mod.rs | 15 +- tests/ref/markup/heading.png | Bin 6611 -> 6406 bytes tests/typ/markup/heading.typ | 4 +- 7 files changed, 260 insertions(+), 317 deletions(-) diff --git a/src/eval/walk.rs b/src/eval/walk.rs index ff73f9f90..1656929b8 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -76,7 +76,7 @@ impl Walk for HeadingNode { ctx.template.save(); ctx.template.modify(move |style| { let text = style.text_mut(); - let upscale = 1.6 - 0.1 * level as f64; + let upscale = (1.6 - 0.1 * level as f64).max(0.75); text.size *= upscale; text.strong = true; }); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 92220eaab..21ca303ed 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -14,9 +14,8 @@ use std::rc::Rc; use crate::syntax::ast::{Associativity, BinOp, UnOp}; use crate::syntax::{ErrorPosition, GreenNode, NodeKind}; -use crate::util::EcoString; -type ParseResult = Result<(), ()>; +type ParseResult = Result; /// Parse a source file. pub fn parse(source: &str) -> Rc { @@ -52,12 +51,11 @@ fn markup_while(p: &mut Parser, mut at_start: bool, f: &mut F) where F: FnMut(&mut Parser) -> bool, { - p.start(); - while !p.eof() && f(p) { - markup_node(p, &mut at_start); - } - - p.end(NodeKind::Markup); + p.perform(NodeKind::Markup, |p| { + while !p.eof() && f(p) { + markup_node(p, &mut at_start).ok(); + } + }); } /// Parse a markup node. @@ -91,7 +89,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) -> ParseResult { | NodeKind::Raw(_) | NodeKind::UnicodeEscape(_) => { p.eat(); - Ok(()) } NodeKind::Eq if *at_start => heading(p), @@ -101,7 +98,6 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) -> ParseResult { // Line-based markup that is not currently at the start of the line. NodeKind::Eq | NodeKind::ListBullet | NodeKind::EnumNumbering(_) => { p.convert(NodeKind::Text(p.peek_src().into())); - Ok(()) } // Hashtag + keyword / identifier. @@ -120,7 +116,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) -> ParseResult { if stmt && res.is_ok() && !p.eof() { p.expected_at("semicolon or line break"); } - p.end_group() + p.end_group(); } // Block and template. @@ -135,58 +131,46 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) -> ParseResult { NodeKind::Error(_, _) => { p.eat(); - Ok(()) } _ => { p.unexpected(); - Err(()) + return Err(()); } - }?; + }; *at_start = false; Ok(()) } /// Parse a heading. -fn heading(p: &mut Parser) -> ParseResult { - p.start(); - p.eat_assert(&NodeKind::Eq); +fn heading(p: &mut Parser) { + p.perform(NodeKind::Heading, |p| { + p.eat_assert(&NodeKind::Eq); - // Count depth. - let mut level: usize = 1; - while p.eat_if(&NodeKind::Eq) { - level += 1; - } + while p.eat_if(&NodeKind::Eq) {} - if level > 6 { - p.end(NodeKind::Text(EcoString::from('=').repeat(level))); - } else { let column = p.column(p.prev_end()); markup_indented(p, column); - p.end(NodeKind::Heading); - } - Ok(()) + }); } /// Parse a single list item. -fn list_node(p: &mut Parser) -> ParseResult { - p.start(); - p.eat_assert(&NodeKind::ListBullet); - let column = p.column(p.prev_end()); - markup_indented(p, column); - p.end(NodeKind::List); - Ok(()) +fn list_node(p: &mut Parser) { + p.perform(NodeKind::List, |p| { + p.eat_assert(&NodeKind::ListBullet); + let column = p.column(p.prev_end()); + markup_indented(p, column); + }); } /// Parse a single enum item. -fn enum_node(p: &mut Parser) -> ParseResult { - p.start(); - p.eat(); - let column = p.column(p.prev_end()); - markup_indented(p, column); - p.end(NodeKind::Enum); - Ok(()) +fn enum_node(p: &mut Parser) { + p.perform(NodeKind::Enum, |p| { + p.eat(); + let column = p.column(p.prev_end()); + markup_indented(p, column); + }); } /// Parse an expression. @@ -224,7 +208,7 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { p.peek_direct(), Some(NodeKind::LeftParen | NodeKind::LeftBracket) ) { - call(p, &marker); + call(p, &marker)?; continue; } @@ -255,19 +239,14 @@ fn expr_with(p: &mut Parser, atomic: bool, min_prec: usize) -> ParseResult { Associativity::Right => {} } - if expr_with(p, atomic, prec).is_err() { - break Ok(()); - } - - marker.end(p, NodeKind::Binary); + marker.perform(p, NodeKind::Binary, |p| expr_with(p, atomic, prec))?; } } /// Parse a primary expression. fn primary(p: &mut Parser, atomic: bool) -> ParseResult { - let lit = literal(p); - if lit.is_ok() { - return lit; + if literal(p) { + return Ok(()); } match p.peek() { @@ -282,9 +261,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { marker.end(p, NodeKind::ClosureParams); p.eat(); - let e = expr(p); - marker.end(p, NodeKind::Closure); - e + marker.perform(p, NodeKind::Closure, expr) } else { Ok(()) } @@ -292,8 +269,14 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { // Structures. Some(NodeKind::LeftParen) => parenthesized(p), - Some(NodeKind::LeftBracket) => template(p), - Some(NodeKind::LeftBrace) => block(p), + Some(NodeKind::LeftBracket) => { + template(p); + Ok(()) + } + Some(NodeKind::LeftBrace) => { + block(p); + Ok(()) + } // Keywords. Some(NodeKind::Let) => let_expr(p), @@ -317,7 +300,7 @@ fn primary(p: &mut Parser, atomic: bool) -> ParseResult { } /// Parse a literal. -fn literal(p: &mut Parser) -> ParseResult { +fn literal(p: &mut Parser) -> bool { match p.peek() { // Basic values. Some( @@ -333,10 +316,10 @@ fn literal(p: &mut Parser) -> ParseResult { | NodeKind::Str(_), ) => { p.eat(); - Ok(()) + true } - _ => Err(()), + _ => false, } } @@ -364,10 +347,7 @@ fn parenthesized(p: &mut Parser) -> ParseResult { p.eat_assert(&NodeKind::Arrow); - let r = expr(p); - - marker.end(p, NodeKind::Closure); - return r; + return marker.perform(p, NodeKind::Closure, expr); } // Find out which kind of collection this is. @@ -439,37 +419,35 @@ fn collection(p: &mut Parser) -> (CollectionKind, usize) { } /// Parse an expression or a named pair. Returns if this is a named pair. -fn item(p: &mut Parser) -> Result { +fn item(p: &mut Parser) -> ParseResult { let marker = p.marker(); if p.eat_if(&NodeKind::Dots) { - let r = expr(p); - - marker.end(p, NodeKind::Spread); - return r.map(|_| NodeKind::Spread); + return marker + .perform(p, NodeKind::Spread, |p| expr(p).map(|_| NodeKind::Spread)); } let ident_marker = p.marker(); - if expr(p).is_err() { - return Err(()); - } + expr(p)?; if p.peek() == Some(&NodeKind::Colon) { - let r = if matches!(p.child(0).unwrap().kind(), &NodeKind::Ident(_)) { - p.eat(); - expr(p) - } else { - ident_marker.end( - p, - NodeKind::Error(ErrorPosition::Full, "expected identifier".into()), - ); - p.eat(); + marker.perform(p, NodeKind::Named, |p| { + if matches!( + ident_marker.child_at(p).unwrap().kind(), + &NodeKind::Ident(_) + ) { + p.eat(); + expr(p).map(|_| NodeKind::Named) + } else { + ident_marker.end( + p, + NodeKind::Error(ErrorPosition::Full, "expected identifier".into()), + ); + p.eat(); - expr(p); - Err(()) - }; - - marker.end(p, NodeKind::Named); - r.map(|_| NodeKind::Named) + expr(p).ok(); + Err(()) + } + }) } else { Ok(p.last_child().unwrap().kind().clone()) } @@ -478,23 +456,16 @@ fn item(p: &mut Parser) -> Result { /// Convert a collection into an array, producing errors for anything other than /// expressions. fn array(p: &mut Parser, marker: &Marker) -> ParseResult { - marker.filter_children( - p, - |x| match x.kind() { - NodeKind::Named | NodeKind::Spread => false, - _ => true, - }, - |kind| match kind { - NodeKind::Named => ( - ErrorPosition::Full, - "expected expression, found named pair".into(), - ), - NodeKind::Spread => { - (ErrorPosition::Full, "spreading is not allowed here".into()) - } - _ => unreachable!(), - }, - ); + marker.filter_children(p, |x| match x.kind() { + NodeKind::Named => Err(( + ErrorPosition::Full, + "expected expression, found named pair".into(), + )), + NodeKind::Spread => { + Err((ErrorPosition::Full, "spreading is not allowed here".into())) + } + _ => Ok(()), + }); marker.end(p, NodeKind::Array); Ok(()) @@ -503,24 +474,17 @@ fn array(p: &mut Parser, marker: &Marker) -> ParseResult { /// Convert a collection into a dictionary, producing errors for anything other /// than named pairs. fn dict(p: &mut Parser, marker: &Marker) -> ParseResult { - marker.filter_children( - p, - |x| { - x.kind() == &NodeKind::Named - || x.kind().is_paren() - || x.kind() == &NodeKind::Comma - || x.kind() == &NodeKind::Colon - }, - |kind| match kind { - NodeKind::Spread => { - (ErrorPosition::Full, "spreading is not allowed here".into()) - } - _ => ( - ErrorPosition::Full, - "expected named pair, found expression".into(), - ), - }, - ); + marker.filter_children(p, |x| match x.kind() { + NodeKind::Named | NodeKind::Comma | NodeKind::Colon => Ok(()), + NodeKind::Spread => { + Err((ErrorPosition::Full, "spreading is not allowed here".into())) + } + _ if x.kind().is_paren() => Ok(()), + _ => Err(( + ErrorPosition::Full, + "expected named pair, found expression".into(), + )), + }); marker.end(p, NodeKind::Dict); Ok(()) @@ -529,96 +493,90 @@ fn dict(p: &mut Parser, marker: &Marker) -> ParseResult { /// Convert a collection into a list of parameters, producing errors for /// anything other than identifiers, spread operations and named pairs. fn params(p: &mut Parser, marker: &Marker, allow_parens: bool) { - marker.filter_children( - p, - |x| match x.kind() { - NodeKind::Named | NodeKind::Comma | NodeKind::Ident(_) => true, - NodeKind::Spread => matches!( - x.children().last().map(|x| x.kind()), - Some(&NodeKind::Ident(_)) - ), - _ => false, - } - || (allow_parens && x.kind().is_paren()), - |_| (ErrorPosition::Full, "expected identifier".into()), - ); + marker.filter_children(p, |x| match x.kind() { + NodeKind::Named | NodeKind::Comma | NodeKind::Ident(_) => Ok(()), + NodeKind::Spread + if matches!( + x.children().last().map(|x| x.kind()), + Some(&NodeKind::Ident(_)) + ) => + { + Ok(()) + } + _ if allow_parens && x.kind().is_paren() => Ok(()), + _ => Err((ErrorPosition::Full, "expected identifier".into())), + }); } // Parse a template block: `[...]`. -fn template(p: &mut Parser) -> ParseResult { - p.start(); - p.start_group(Group::Bracket, TokenMode::Markup); - markup(p); - p.end_group(); - p.end(NodeKind::Template); - Ok(()) +fn template(p: &mut Parser) { + p.perform(NodeKind::Template, |p| { + p.start_group(Group::Bracket, TokenMode::Markup); + markup(p); + p.end_group(); + }); } /// Parse a code block: `{...}`. -fn block(p: &mut Parser) -> ParseResult { - p.start(); - p.start_group(Group::Brace, TokenMode::Code); - while !p.eof() { - p.start_group(Group::Stmt, TokenMode::Code); - if expr(p).is_ok() { - if !p.eof() { +fn block(p: &mut Parser) { + p.perform(NodeKind::Block, |p| { + p.start_group(Group::Brace, TokenMode::Code); + while !p.eof() { + p.start_group(Group::Stmt, TokenMode::Code); + if expr(p).is_ok() && !p.eof() { p.expected_at("semicolon or line break"); } + p.end_group(); + + // Forcefully skip over newlines since the group's contents can't. + p.eat_while(|t| matches!(t, NodeKind::Space(_))); } p.end_group(); - - // Forcefully skip over newlines since the group's contents can't. - p.eat_while(|t| matches!(t, NodeKind::Space(_))); - } - p.end_group(); - p.end(NodeKind::Block); - Ok(()) + }); } /// Parse a function call. fn call(p: &mut Parser, callee: &Marker) -> ParseResult { - let res = match p.peek_direct() { - Some(NodeKind::LeftParen) | Some(NodeKind::LeftBracket) => args(p, true), + callee.perform(p, NodeKind::Call, |p| match p.peek_direct() { + Some(NodeKind::LeftParen) | Some(NodeKind::LeftBracket) => { + args(p, true); + Ok(()) + } _ => { p.expected_at("argument list"); Err(()) } - }; - - callee.end(p, NodeKind::Call); - res + }) } /// Parse the arguments to a function call. -fn args(p: &mut Parser, allow_template: bool) -> ParseResult { - p.start(); - if !allow_template || p.peek_direct() == Some(&NodeKind::LeftParen) { - p.start_group(Group::Paren, TokenMode::Code); - collection(p); - p.end_group(); - } +fn args(p: &mut Parser, allow_template: bool) { + p.perform(NodeKind::CallArgs, |p| { + if !allow_template || p.peek_direct() == Some(&NodeKind::LeftParen) { + p.start_group(Group::Paren, TokenMode::Code); + collection(p); + p.end_group(); + } - while allow_template && p.peek_direct() == Some(&NodeKind::LeftBracket) { - template(p); - } - - p.end(NodeKind::CallArgs); - Ok(()) + while allow_template && p.peek_direct() == Some(&NodeKind::LeftBracket) { + template(p); + } + }) } /// Parse a with expression. fn with_expr(p: &mut Parser, marker: &Marker) -> ParseResult { - p.eat_assert(&NodeKind::With); + marker.perform(p, NodeKind::WithExpr, |p| { + p.eat_assert(&NodeKind::With); - let res = if p.peek() == Some(&NodeKind::LeftParen) { - args(p, false) - } else { - p.expected("argument list"); - Err(()) - }; - - marker.end(p, NodeKind::WithExpr); - res + if p.peek() == Some(&NodeKind::LeftParen) { + args(p, false); + Ok(()) + } else { + p.expected("argument list"); + Err(()) + } + }) } /// Parse a let expression. @@ -630,17 +588,17 @@ fn let_expr(p: &mut Parser) -> ParseResult { ident(p)?; if p.peek() == Some(&NodeKind::With) { - with_expr(p, &marker); + with_expr(p, &marker)?; } else { // If a parenthesis follows, this is a function definition. let has_params = if p.peek_direct() == Some(&NodeKind::LeftParen) { - p.start(); - p.start_group(Group::Paren, TokenMode::Code); - let marker = p.marker(); - collection(p); - params(p, &marker, true); - p.end_group(); - p.end(NodeKind::ClosureParams); + p.perform(NodeKind::ClosureParams, |p| { + p.start_group(Group::Paren, TokenMode::Code); + let marker = p.marker(); + collection(p); + params(p, &marker, true); + p.end_group(); + }); true } else { false @@ -699,13 +657,10 @@ fn for_expr(p: &mut Parser) -> ParseResult { p.eat_assert(&NodeKind::For); for_pattern(p)?; - if p.eat_expect(&NodeKind::In) { - expr(p)?; - body(p)?; - Ok(()) - } else { - Err(()) - } + p.eat_expect(&NodeKind::In)?; + expr(p)?; + body(p)?; + Ok(()) }) } @@ -723,44 +678,42 @@ fn for_pattern(p: &mut Parser) -> ParseResult { /// Parse an import expression. fn import_expr(p: &mut Parser) -> ParseResult { - p.start(); - p.eat_assert(&NodeKind::Import); + p.perform(NodeKind::ImportExpr, |p| { + p.eat_assert(&NodeKind::Import); - if !p.eat_if(&NodeKind::Star) { - // This is the list of identifiers scenario. - p.start(); - p.start_group(Group::Imports, TokenMode::Code); - let marker = p.marker(); - let items = collection(p).1; - if items == 0 { - p.expected_at("import items"); + if !p.eat_if(&NodeKind::Star) { + // This is the list of identifiers scenario. + p.perform(NodeKind::ImportItems, |p| { + p.start_group(Group::Imports, TokenMode::Code); + let marker = p.marker(); + let items = collection(p).1; + if items == 0 { + p.expected_at("import items"); + } + p.end_group(); + + marker.filter_children(p, |n| match n.kind() { + NodeKind::Ident(_) | NodeKind::Comma => Ok(()), + _ => Err((ErrorPosition::Full, "expected identifier".into())), + }); + }); + }; + + if p.eat_expect(&NodeKind::From).is_ok() { + expr(p)?; } - p.end_group(); - marker.filter_children( - p, - |n| matches!(n.kind(), NodeKind::Ident(_) | NodeKind::Comma), - |_| (ErrorPosition::Full, "expected identifier".into()), - ); - p.end(NodeKind::ImportItems); - }; - - if p.eat_expect(&NodeKind::From) { - expr(p); - } - - p.end(NodeKind::ImportExpr); - Ok(()) + Ok(()) + }) } /// Parse an include expression. fn include_expr(p: &mut Parser) -> ParseResult { - p.start(); - p.eat_assert(&NodeKind::Include); - - expr(p); - p.end(NodeKind::IncludeExpr); - Ok(()) + p.perform(NodeKind::IncludeExpr, |p| { + p.eat_assert(&NodeKind::Include); + expr(p)?; + Ok(()) + }) } /// Parse an identifier. @@ -784,7 +737,9 @@ fn body(p: &mut Parser) -> ParseResult { Some(NodeKind::LeftBrace) => block(p), _ => { p.expected_at("body"); - Err(()) + return Err(()); } } + + Ok(()) } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index bc028876c..3813ee840 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -62,28 +62,24 @@ pub struct Marker(usize); impl Marker { /// Wraps all children in front of the marker. pub fn end(&self, p: &mut Parser, kind: NodeKind) { - if p.children.len() != self.0 { - let stop_nl = p.stop_at_newline(); - let end = (self.0 .. p.children.len()) - .rev() - .find(|&i| !Parser::skip_type_ext(p.children[i].kind(), stop_nl)) - .unwrap_or(self.0) - + 1; + let stop_nl = p.stop_at_newline(); + let end = (self.0 .. p.children.len()) + .rev() + .find(|&i| !Parser::skip_type_ext(p.children[i].kind(), stop_nl)) + .unwrap_or(self.0) + + 1; - let children: Vec<_> = p.children.drain(self.0 .. end).collect(); - let len = children.iter().map(Green::len).sum(); - p.children - .insert(self.0, GreenNode::with_children(kind, len, children).into()); - } + let children: Vec<_> = p.children.drain(self.0 .. end).collect(); + p.children + .insert(self.0, GreenNode::with_children(kind, children).into()); } /// Wrap all children that do not fulfill the predicate in error nodes. - pub fn filter_children(&self, p: &mut Parser, f: F, error: G) + pub fn filter_children(&self, p: &mut Parser, f: F) where - F: Fn(&Green) -> bool, - G: Fn(&NodeKind) -> (ErrorPosition, EcoString), + F: Fn(&Green) -> Result<(), (ErrorPosition, EcoString)>, { - p.filter_children(self, f, error) + p.filter_children(self, f) } /// Insert an error message that `what` was expected at the marker position. @@ -97,6 +93,20 @@ impl Marker { .into(), ); } + + /// Return a reference to the child after the marker. + pub fn child_at<'a>(&self, p: &'a Parser) -> Option<&'a Green> { + p.children.get(self.0) + } + + pub fn perform(&self, p: &mut Parser, kind: NodeKind, f: F) -> T + where + F: FnOnce(&mut Parser) -> T, + { + let success = f(p); + self.end(p, kind); + success + } } impl<'s> Parser<'s> { @@ -121,58 +131,31 @@ impl<'s> Parser<'s> { /// /// Each start call has to be matched with a call to `end`, /// `end_with_custom_children`, `lift`, `abort`, or `end_or_abort`. - pub fn start(&mut self) { + fn start(&mut self) { self.stack.push(std::mem::take(&mut self.children)); } /// Filter the last children using the given predicate. - fn filter_children(&mut self, count: &Marker, f: F, error: G) + fn filter_children(&mut self, count: &Marker, f: F) where - F: Fn(&Green) -> bool, - G: Fn(&NodeKind) -> (ErrorPosition, EcoString), + F: Fn(&Green) -> Result<(), (ErrorPosition, EcoString)>, { for child in &mut self.children[count.0 ..] { if !((self.tokens.mode() != TokenMode::Code || Self::skip_type_ext(child.kind(), false)) - || child.kind().is_error() - || f(&child)) + || child.kind().is_error()) { - let (pos, msg) = error(child.kind()); - let inner = std::mem::take(child); - *child = - GreenNode::with_child(NodeKind::Error(pos, msg), inner.len(), inner) - .into(); + if let Err((pos, msg)) = f(child) { + let inner = std::mem::take(child); + *child = + GreenNode::with_child(NodeKind::Error(pos, msg), inner).into(); + } } } } - /// Return the a child from the current stack frame specified by its - /// non-trivia index from the back. - pub fn child(&self, child: usize) -> Option<&Green> { - self.node_index_from_back(child).map(|i| &self.children[i]) - } - - /// Map a non-trivia index from the back of the current stack frame to a - /// normal index. - fn node_index_from_back(&self, child: usize) -> Option { - let len = self.children.len(); - let code = self.tokens.mode() == TokenMode::Code; - let mut seen = 0; - for x in (0 .. len).rev() { - if self.skip_type(self.children[x].kind()) && code { - continue; - } - if seen == child { - return Some(x); - } - seen += 1; - } - - None - } - /// End the current node as a node of given `kind`. - pub fn end(&mut self, kind: NodeKind) { + fn end(&mut self, kind: NodeKind) { let outer = self.stack.pop().unwrap(); let mut children = std::mem::replace(&mut self.children, outer); @@ -191,15 +174,13 @@ impl<'s> Parser<'s> { remains.reverse(); } - let len = children.iter().map(|c| c.len()).sum(); - self.children - .push(GreenNode::with_children(kind, len, children).into()); + self.children.push(GreenNode::with_children(kind, children).into()); self.children.extend(remains); } - pub fn perform(&mut self, kind: NodeKind, f: F) -> ParseResult + pub fn perform(&mut self, kind: NodeKind, f: F) -> T where - F: FnOnce(&mut Self) -> ParseResult, + F: FnOnce(&mut Self) -> T, { self.start(); let success = f(self); @@ -267,12 +248,12 @@ impl<'s> Parser<'s> { /// Consume the next token if it is the given one and produce an error if /// not. - pub fn eat_expect(&mut self, t: &NodeKind) -> bool { + pub fn eat_expect(&mut self, t: &NodeKind) -> ParseResult { let eaten = self.eat_if(t); if !eaten { self.expected_at(t.as_str()); } - eaten + if eaten { Ok(()) } else { Err(()) } } /// Consume the next token, debug-asserting that it is one of the given ones. @@ -368,10 +349,9 @@ impl<'s> Parser<'s> { /// End the parsing of a group. /// /// This panics if no group was started. - pub fn end_group(&mut self) -> ParseResult { + pub fn end_group(&mut self) { let prev_mode = self.tokens.mode(); let group = self.groups.pop().expect("no started group"); - let mut success = true; self.tokens.set_mode(group.prev_mode); self.repeek(); @@ -392,7 +372,6 @@ impl<'s> Parser<'s> { rescan = false; } else if required { self.push_error(format!("expected {}", end)); - success = false; } } @@ -415,8 +394,6 @@ impl<'s> Parser<'s> { self.next = self.tokens.next(); self.repeek(); } - - if success { Ok(()) } else { Err(()) } } /// Add an error that `what` was expected at the given span. @@ -436,12 +413,13 @@ impl<'s> Parser<'s> { pub fn expected(&mut self, what: &str) { match self.peek().cloned() { Some(found) => { - self.start(); - self.eat(); - self.end(NodeKind::Error( - ErrorPosition::Full, - format!("expected {}, found {}", what, found).into(), - )); + self.perform( + NodeKind::Error( + ErrorPosition::Full, + format!("expected {}, found {}", what, found).into(), + ), + Self::eat, + ); } None => self.expected_at(what), } @@ -451,12 +429,13 @@ impl<'s> Parser<'s> { pub fn unexpected(&mut self) { match self.peek().cloned() { Some(found) => { - self.start(); - self.eat(); - self.end(NodeKind::Error( - ErrorPosition::Full, - format!("unexpected {}", found).into(), - )); + self.perform( + NodeKind::Error( + ErrorPosition::Full, + format!("unexpected {}", found).into(), + ), + Self::eat, + ); } None => self.push_error("unexpected end of file"), } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index b6f64c677..1198d6b1e 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -156,7 +156,11 @@ impl HeadingNode { /// The section depth (numer of equals signs). pub fn level(&self) -> u8 { - self.0.children().filter(|n| n.kind() == &NodeKind::Eq).count() as u8 + self.0 + .children() + .filter(|n| n.kind() == &NodeKind::Eq) + .count() + .min(u8::MAX.into()) as u8 } } diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index db3b0c9ab..363cbe6e9 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -98,15 +98,20 @@ pub struct GreenNode { impl GreenNode { /// Creates a new node with the given kind and children. - pub fn with_children(kind: NodeKind, len: usize, children: Vec) -> Self { - let mut data = GreenData::new(kind, len); - data.erroneous |= children.iter().any(|c| c.erroneous()); + pub fn with_children(kind: NodeKind, children: Vec) -> Self { + let mut data = GreenData::new(kind, 0); + let len = children + .iter() + .inspect(|c| data.erroneous |= c.erroneous()) + .map(Green::len) + .sum(); + data.len = len; Self { data, children } } /// Creates a new node with the given kind and a single child. - pub fn with_child(kind: NodeKind, len: usize, child: impl Into) -> Self { - Self::with_children(kind, len, vec![child.into()]) + pub fn with_child(kind: NodeKind, child: impl Into) -> Self { + Self::with_children(kind, vec![child.into()]) } /// The node's children. diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png index ca52644bb0790b91907d0b0ebb266fce1d67675b..c33da4206beb2755941d314833f67db84dcbe501 100644 GIT binary patch literal 6406 zcmai(2QZvn+sE%^aqGK5^s=lLqW9=^1&JpTiC&lJo#<^5eXSm%ghwJu{s3nloi@c=(r*wVp!AM&o(@Iv zg!H;GTQt`=6fZBwxPDEgDw)1WYRV z&mp3hSK;DYody6Ky>J>}*Ad#1M0MB#s)h$Zamz}N7HxFyLy%v_;i!Y^m52}wj2%-u zqtNvfh*~tnIhf`2J@7`GnIefdgroMBHhJGNs&{+vJ$Ug+NSO%O3h2uLuDDpyK5zO` za_{+G_#opq}VR4S$EIM>N{4 z>v?jNW^u21W9a~nSY4wuz?P4yS5KwagAZjz|E!JrcWL@sYAIR)d+V>jL|e%G=K!mq zB(GqGoIm&2X`))!nyU!kggK#W2OQ=C@ zd68pWPg9M%MPt1BDEE>eM{DcRXx!olis#!Rx$Csi$Uoj6DHL7h06<)Ac%g0N=b_ou z9j7($AgONrxT6wQowg(XR$2FU9{;!{seVeef!ucu*&X?VZTTc)k{`r1 z?ChZZYell3*j62;3hol|vQDla5k=q~+uuOb9M*0*ei$ z-drXv7K3r%BdoN5-2pG1UxA1q!5$o|ZNKK?hCCrB6gfAJ-WJ8M0pZVSPt{;2!h1}C z(Z&Xj6A3|cX^5`-%!HiWev)@~chadN;Tk_xSjGJ)KgyNX`r|ymesm?)gq~jp zpw&cxmHbD++JNF+<3_i4jFPtoUaH;swI7l*mI(E@#?cut_)~hIgf*R_EcX< z-DU@ur$)gMP4Ct3wSMJm%^AoME4|UcWg=etN3{FK!S{9yEVhs|{NZMB3GYqB_zzE5 zSaVL+R3_Y#O2&vp!8D4G!q%4F(Ss~Y9#kM^97QEhI=xQ^!@zh@tNQGIZ@Pw0+UqB>$6er;#A1 zx2e>9zVWcslS?tx5FTniqw6)ryAWW=(09mD5Wo3SO*!mWS$)T~p(icZ6w2sH*|uoy zy#Bu*KOUn#?Qx}cUlzKX-hWG^ZnZYcDvMq5RZ*%sUgJ_ALVW+BM9^dY)q#wNN2rMa zW(^)iUe@R}=1?u$A?L?mOL!@6`#_n*!PjLQ4qzKg#CaABI za$s<+JQS}ZX~fUw#FF2Yj(SGKXu?PsFN+7p!}PvLx_=yczLch7O;t*10v##*Yww35 zFmCQyHiK%S6t|82Ko>sTrBD49-|Jn*;^Y!)^dl z9X`;4gfF`pOAi$)LZ1^U@&PK~#u?XzYDhPxN(}zAbNr*dB4bBZlI|QT6c-uw(Qg_Q zBLhtM7iiJ6CAjde3|fHWB`F|t@Upw`&Nl^3Ql9+o+XBzK6Eg-`eIM?!f8LR3XbB3~ zj#vXnspZ8?D8F?+fi>#t$z6&mmuGc`{H7+Gs#YZe#0#+ zR|r>B>~o84GV`>Cl+v5by{kX#`@}Wkz4_mxMmyrl=zcFSx@o-)%PJ~2 z+S+FsaG|OjfQ-o)d5VCyXo!~pp+Tu^x2It$Feh!Q>n>)Ofi!$9d;E0AlqYScWh5Uj z6OSdgJ@fUI&LlDG=Co^C^DL`?mH@3IO9aa4;SQe6ReZH~93F3aot=VVN6RC{1rt^8 zv#BoZ=aBLuSs&2ma@EFnUo#54s;YN{h;82~j>zJ)c&YryXL4#l+xyn0}M z@+#4k#rH-|>FiIXuc8^VU(FtQNxyQknp+8HT2Pwe7+G65R$n#DiGSp^pZHF(^|#sD z)BLxTa`Kb_GCNs~D=@l7w7hX#)pZ7C?lz$sxgG@HzxCgo#)S#7V*m*%qvt;#XwGT< z?KD~>X};#$STijMd<+e{gUR#OuSpYH6Gt49;t+J&*m8`R73z6V4wW_OK)|B> zZ+h5?550#bWyx=`w4nkBnHHvlE_!&XNvU@%Cv?ep#VxTaoVv58Jf8lU=1}d0+vv|P zio>q?rY}%sRP#4)-+?B2_73Uar|x>Vr2l*~RkVN`!E?ItZafI?Nm`j>7?mXID7008 zzWBB`9&HH`U6)G9)wx}@{)j_e?u}LQ0na@|Ra^fA(ujQR!K2T4Pqu3MTRmwJfuLv6 zvO&}vN_z33OI~F*o_afl@$^`tDZJ}>^O($-pt@3KVKU6P3S#np$X?)PC*#JY&f+M0 zfk`_ideR6=4f_SjqY?Dxiz7VZcekrYtc=FLW~PJ?fu+`ac*CLi#rbIbd|hV6H9J$>-FrL%4}C7 z=4vv$?_3%_We;EEK2d$;=S!RVEC1a7|K8hO=5Q3(W;W9p=U22D zHaU5Et=LLEZ)LlNQ&!hUIIR%%04o{S7q7%bHE~fuMxmWm+Kq zqX6c-5D5%2;6zitG{|9c5{c`;ZN3q=^%-;wNz%f*?+$C)He$4&6UK;RNc#Wvgu5Ob$4WwGg+wrzT67jA&WZc@OK4@ohw>l5WN zJ$^#n$!*Lvb}noC-5K7m#r0C_`jMcTD+4sT0vj%&)-T0f(AEMfbN_5Uw_#2g;l*C= z`zT?K6XjN^^G=1ae;OZ*9_Q)KUa7ESgyPRTW`0~uk-hL>%gpke+_ZHevGa9%niHjh zuRf0kQ5Y8SB@J}k4T3+~t6V#qV*Yc{FW(Jn?+oz8vO|?fd0h0HY1B8QSeF#?w;Cyb z)n-I^fP}6irON$J0m75=xU)~TX_8cHS6*{2JfqTQ?>ksOH`xx#2BmKlEm@vC+xK~F zQyurq%I-vj4|BMF-mLJ0x$>WmoJloun31OwQZddkqf7NEBNx zMp3@!9GE|7(TU67nuGFUQ<`;f{<)ir)$nge99wlq2b-eAhN{fHsc!+vdylgB*jB4T zZ;t0!wIllmHWNnH$VVmomug{yo* zbA=XXZ6R=HyvVxcE@!{!fefy_$rG7Nl*LCE&RQ5%t7z8T4}RS3>IK&fG1_<24?Vsg zlIrO}Y@qNU8KBx^D>dU49doPT^f=AfGtYN&OI}mH&w$GPOpbi2gfzfV5M|F)+MK4G zE>ivTCE7hB)-gK-CsvsY;{2y|LN?$e(osu=j9dF{sBf2|_Y(FND;PrXT|F&W>pG#? zX9vd-A+*n`)|wmcB`lQ&-M+kfHnFa^ix1T~ocoUsdZ7UPU({e; zf6{ip(d-{jrV)&tMq@HFXV;xxJTRVZIcP)F69Ohlgt#hiA*ygb5T6!K%12_G^9hTm?C%_A?;*bB*U~i6h1CEM93XGlb>67O>StR{81G&L=z!9J^GaTM24=daiX5{ne;xXVhLN-WZ%<1++ zYLyp~rk`1oEi{LlbvJEWLEqH}kN|Zj@s9L7F1`Cv1qcmjDG9dTp#akDMXF3z%C9(R zaV41+Mn1^m+1m%lFoJdD4X120?a5KwJk|I67hONGh{EvM0ma3BiI%*Wq+3}t{>+^i z)1?PR2PyLmlXpCGL8P-;k}ngDS}cs* znG)#=s1Ba=kegsmX!9@FDyAGgp4*ZmtO~9_oet|T+Fsa&;tOIKq0Lt!r0WLKN4c+_ zKN|k2md?Cm0a{%7Kl^r@0~)z0c0M(uY&2%lA?H6byKn#tJFC_($(`d=DXTyKPI#8i z``#-tg;$xmGs1Bu8*#Wk@Wn0}+zbCxy`{dN=6w@=GK0+K{8S>~YigP_L!9igff>Z;a!U-TCypgl>-RA=+;UNOa-ew9Jt#82mS^WKZ>gYw;|Ay6Iw>S?nLl=U4x^)bK6D6;d7~kK5-fl1pSZ;+E?}jqM-n9x2 zl&m*btu1z0dRkTf`N@-zVmiCwhS4E^d&=D9>tUt`e-k(Vw6dj=E;W1cEDk7Xx4xF4 zYcB%I)0XWq;Fc3AJ9s&AYB|jqs~D!$`^z(>3H*56+(!c^bRth5v&~bn*D8au5YzSn z%7vWU6+D*0n$#CX5@O(`WN?Ij)Omgn7rUbasf^25<`_M z6G`5j7KX^Ap6mMomA=WgXrhteHFq&ebyUy%&0h1O7@kiIdpllqCTelG4+rqYQuY_H zPd*jeR>0>C+O9_Pj7xm{r&k5 zU0`t4=&8!9o$3Z={maggt~oMoNB_($|1Qw&0+3J&>RUGtw_WZ!t)1ABr4!vpVw`Do z4%t8o9k4@jy}{^Eh-5YJn|bDTc`50Mf6&bGQ-=}IPZ+YYRtm1d7P7+*hT)b&veN}U zm+k>c4F~K+(cYX>IX#F-27|gF3{1c!R|_6rvt!#A!WpZ7Dnk;|t zm8FT}o`5cPr!wr}p)f}E4Z|~nKnSf`>^y%=fsnv_}~s%^EWBz?1mm z0)^!bW!Ax=ndYNt;epF418h0vlWqYI!A4T;cy0>?DSG-)$1k(+ezuubZCh#sX1xUI zuh&S3BEVBC-g{GbHENJV_X7P_2QNV+5pAoj5ykINF~_l=rr!P`9rp8c#xD#Ap0zf< zy`jEM(Q34XUd#c@4MsP2a=1`<&4~<^(Kdv)oHVguDdc#%0x{4zgtVeXrqaJ+(@L`l z*1pwo^FK3*e*+bVoKX1-`MF9#w}?bfO`$M08erWyZF(RnkBX?M1dV0WynXXD&dR9mq&wn-v)!4&9Ek?j3u)hYs|=XsGkHalxbB3wt6Z8awx&5oX+7o`V*3tOQ+e!I_7A=!T%E6RpkruaJ^A zcI&`t(X}|6X%~jQG#)js0m? zxM~tiYZ|vMV8-fz!IWp%r?~8&aei0*uN**`S>xi$;a`S;fIoqp&}7a$F|jQB`+`GX M=Z1Ebrftms0N=@Nk^lez literal 6611 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 diff --git a/tests/typ/markup/heading.typ b/tests/typ/markup/heading.typ index 4647e7a6e..cb0226173 100644 --- a/tests/typ/markup/heading.typ +++ b/tests/typ/markup/heading.typ @@ -8,8 +8,8 @@ === Level 2 ====== Level 6 -// Too many hashtags. -======= Level 7 +// At some point, it should stop shrinking. +=========== Level 11 --- // Heading vs. no heading.