diff options
Diffstat (limited to 'src/parse/mod.rs')
| -rw-r--r-- | src/parse/mod.rs | 371 |
1 files changed, 188 insertions, 183 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 2f34357c..395090af 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -46,55 +46,55 @@ fn tree(p: &mut Parser) -> SynTree { /// Parse a syntax node. fn node(p: &mut Parser, at_start: bool) -> Option<Spanned<SynNode>> { - let token = p.eat()?; - let span = token.span; - Some(match token.v { + let start = p.pos(); + let node = match p.eat()? { // Spaces. Token::Space(newlines) => { if newlines < 2 { - SynNode::Space.span_with(span) + SynNode::Space } else { - SynNode::Parbreak.span_with(span) + SynNode::Parbreak } } - Token::Text(text) => SynNode::Text(text.into()).span_with(span), + Token::Text(text) => SynNode::Text(text.into()), // Comments. Token::LineComment(_) | Token::BlockComment(_) => return None, // Markup. - Token::Star => SynNode::ToggleBolder.span_with(span), - Token::Underscore => SynNode::ToggleItalic.span_with(span), - Token::Backslash => SynNode::Linebreak.span_with(span), + Token::Star => SynNode::ToggleBolder, + Token::Underscore => SynNode::ToggleItalic, + Token::Backslash => SynNode::Linebreak, Token::Hashtag => { if at_start { - heading(p, span.start).map(SynNode::Heading) + SynNode::Heading(heading(p, start)) } else { - SynNode::Text(p.get(span).into()).span_with(span) + SynNode::Text(p.eaten_from(start).into()) } } - Token::Raw(token) => raw(p, token, span).map(SynNode::Raw), - Token::UnicodeEscape(token) => unicode_escape(p, token, span).map(SynNode::Text), + Token::Raw(token) => SynNode::Raw(raw(p, token)), + Token::UnicodeEscape(token) => SynNode::Text(unicode_escape(p, token, start)), // Functions. Token::LeftBracket => { - p.jump(span.start); - bracket_call(p).map(Expr::Call).map(SynNode::Expr) + p.jump(start); + SynNode::Expr(Expr::Call(bracket_call(p))) } // Bad tokens. - _ => { - p.diag_unexpected(token); + token => { + p.diag_unexpected(token.span_with(start .. p.pos())); return None; } - }) + }; + Some(node.span_with(start .. p.pos())) } /// Parse a heading. -fn heading(p: &mut Parser, start: Pos) -> Spanned<NodeHeading> { +fn heading(p: &mut Parser, start: Pos) -> NodeHeading { // Parse the section depth. let count = p.eat_while(|c| c == Token::Hashtag); - let span = (start, p.pos()); + let span = Span::new(start, p.pos()); let level = (count.min(5) as u8).span_with(span); if count > 5 { p.diag(warning!(span, "section depth larger than 6 has no effect")); @@ -109,26 +109,23 @@ fn heading(p: &mut Parser, start: Pos) -> Spanned<NodeHeading> { } } - NodeHeading { level, contents }.span_with((start, p.pos())) + NodeHeading { level, contents } } /// Parse a raw block. -fn raw(p: &mut Parser, token: TokenRaw, span: Span) -> Spanned<NodeRaw> { +fn raw(p: &mut Parser, token: TokenRaw) -> NodeRaw { let raw = resolve::resolve_raw(token.text, token.backticks); if !token.terminated { - p.diag(error!(span.end, "expected backtick(s)")); + p.diag(error!(p.pos(), "expected backtick(s)")); } - raw.span_with(span) + raw } /// Parse a unicode escape sequence. -fn unicode_escape( - p: &mut Parser, - token: TokenUnicodeEscape, - span: Span, -) -> Spanned<String> { +fn unicode_escape(p: &mut Parser, token: TokenUnicodeEscape, start: Pos) -> String { + let span = Span::new(start, p.pos()); let text = if let Some(c) = resolve::resolve_hex(token.sequence) { c.to_string() } else { @@ -142,29 +139,28 @@ fn unicode_escape( p.diag(error!(span.end, "expected closing brace")); } - text.span_with(span) + text } /// Parse a bracketed function call. -fn bracket_call(p: &mut Parser) -> Spanned<ExprCall> { - let before_bracket = p.pos(); +fn bracket_call(p: &mut Parser) -> ExprCall { p.start_group(Group::Bracket); p.push_mode(TokenMode::Header); // One header is guaranteed, but there may be more (through chaining). let mut outer = vec![]; - let mut inner = bracket_subheader(p); + let mut inner = p.span(|p| bracket_subheader(p)); - while p.eat_if(Token::Chain).is_some() { + while p.eat_if(Token::Chain) { outer.push(inner); - inner = bracket_subheader(p); + inner = p.span(|p| bracket_subheader(p)); } p.pop_mode(); p.end_group(); if p.peek() == Some(Token::LeftBracket) { - let expr = bracket_body(p).map(Lit::Content).map(Expr::Lit); + let expr = p.span(|p| Expr::Lit(Lit::Content(bracket_body(p)))); inner.span.expand(expr.span); inner.v.args.0.push(LitDictEntry { key: None, expr }); } @@ -177,26 +173,26 @@ fn bracket_call(p: &mut Parser) -> Spanned<ExprCall> { inner = top; } - inner.v.span_with((before_bracket, p.pos())) + inner.v } /// Parse one subheader of a bracketed function call. -fn bracket_subheader(p: &mut Parser) -> Spanned<ExprCall> { +fn bracket_subheader(p: &mut Parser) -> ExprCall { p.start_group(Group::Subheader); - let before_name = p.pos(); + let start = p.pos(); p.skip_white(); - let name = ident(p).unwrap_or_else(|| { + let name = p.span(|p| ident(p)).transpose().unwrap_or_else(|| { if p.eof() { - p.diag_expected_at("function name", before_name); + p.diag_expected_at("function name", start); } else { p.diag_expected("function name"); } - Ident(String::new()).span_with(before_name) + Ident(String::new()).span_with(start) }); p.skip_white(); - let args = if p.eat_if(Token::Colon).is_some() { + let args = if p.eat_if(Token::Colon) { dict_contents(p).0 } else { // Ignore the rest if there's no colon. @@ -207,16 +203,110 @@ fn bracket_subheader(p: &mut Parser) -> Spanned<ExprCall> { LitDict::new() }; - ExprCall { name, args }.span_with(p.end_group()) + p.end_group(); + ExprCall { name, args } } /// Parse the body of a bracketed function call. -fn bracket_body(p: &mut Parser) -> Spanned<SynTree> { +fn bracket_body(p: &mut Parser) -> SynTree { p.start_group(Group::Bracket); p.push_mode(TokenMode::Body); let tree = tree(p); p.pop_mode(); - tree.span_with(p.end_group()) + p.end_group(); + tree +} + +/// Parse a parenthesized function call. +fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> ExprCall { + p.start_group(Group::Paren); + let args = dict_contents(p).0; + p.end_group(); + ExprCall { name, args } +} + +/// Parse the contents of a dictionary. +fn dict_contents(p: &mut Parser) -> (LitDict, bool) { + let mut dict = LitDict::new(); + let mut comma_and_keyless = true; + + loop { + p.skip_white(); + if p.eof() { + break; + } + + let entry = if let Some(entry) = dict_entry(p) { + entry + } else { + p.diag_expected("value"); + continue; + }; + + if let Some(key) = &entry.key { + comma_and_keyless = false; + p.deco(Decoration::DictKey.span_with(key.span)); + } + + let behind = entry.expr.span.end; + dict.0.push(entry); + + p.skip_white(); + if p.eof() { + break; + } + + if !p.eat_if(Token::Comma) { + p.diag_expected_at("comma", behind); + } + + comma_and_keyless = false; + } + + let coercable = comma_and_keyless && !dict.0.is_empty(); + (dict, coercable) +} + +/// Parse a single entry in a dictionary. +fn dict_entry(p: &mut Parser) -> Option<LitDictEntry> { + if let Some(ident) = p.span(|p| ident(p)).transpose() { + p.skip_white(); + match p.peek() { + // Key-value pair. + Some(Token::Equals) => { + p.eat_assert(Token::Equals); + p.skip_white(); + if let Some(expr) = expr(p) { + Some(LitDictEntry { + key: Some(ident.map(|id| DictKey::Str(id.0))), + expr, + }) + } else { + None + } + } + + // Function call. + Some(Token::LeftParen) => Some(LitDictEntry { + key: None, + expr: { + let start = ident.span.start; + let call = paren_call(p, ident); + Expr::Call(call).span_with(start .. p.pos()) + }, + }), + + // Just an identifier. + _ => Some(LitDictEntry { + key: None, + expr: ident.map(|id| Expr::Lit(Lit::Ident(id))), + }), + } + } else if let Some(expr) = expr(p) { + Some(LitDictEntry { key: None, expr }) + } else { + None + } } /// Parse an expression: `term (+ term)*`. @@ -248,7 +338,7 @@ fn binops( loop { p.skip_white(); - if let Some(op) = p.eat_map(op) { + if let Some(op) = p.span(|p| p.eat_map(op)).transpose() { p.skip_white(); if let Some(rhs) = operand(p) { @@ -275,87 +365,91 @@ fn binops( /// Parse a factor of the form `-?value`. fn factor(p: &mut Parser) -> Option<Spanned<Expr>> { - if let Some(op) = p.eat_map(|token| match token { + let op = |token| match token { Token::Hyphen => Some(UnOp::Neg), _ => None, - }) { - p.skip_white(); - if let Some(expr) = factor(p) { - let span = op.span.join(expr.span); - let expr = Expr::Unary(ExprUnary { op, expr: expr.map(Box::new) }); - Some(expr.span_with(span)) + }; + + p.span(|p| { + if let Some(op) = p.span(|p| p.eat_map(op)).transpose() { + p.skip_white(); + if let Some(expr) = factor(p) { + Some(Expr::Unary(ExprUnary { op, expr: expr.map(Box::new) })) + } else { + p.diag(error!(op.span, "missing factor")); + None + } } else { - p.diag(error!(op.span, "missing factor")); - None + value(p) } - } else { - value(p) - } + }) + .transpose() } /// Parse a value. -fn value(p: &mut Parser) -> Option<Spanned<Expr>> { - let Spanned { v: token, span } = p.eat()?; - Some(match token { +fn value(p: &mut Parser) -> Option<Expr> { + let start = p.pos(); + Some(match p.eat()? { // Bracketed function call. Token::LeftBracket => { - p.jump(span.start); - let call = bracket_call(p); - let span = call.span; - let node = call.map(Expr::Call).map(SynNode::Expr); - Expr::Lit(Lit::Content(vec![node])).span_with(span) + p.jump(start); + let node = p.span(|p| SynNode::Expr(Expr::Call(bracket_call(p)))); + Expr::Lit(Lit::Content(vec![node])) } // Content expression. Token::LeftBrace => { - p.jump(span.start); - content(p).map(Lit::Content).map(Expr::Lit) + p.jump(start); + Expr::Lit(Lit::Content(content(p))) } // Dictionary or just a parenthesized expression. Token::LeftParen => { - p.jump(span.start); + p.jump(start); parenthesized(p) } // Function or just ident. Token::Ident(id) => { - let ident = Ident(id.into()).span_with(span); + let ident = Ident(id.into()); + let after = p.pos(); p.skip_white(); if p.peek() == Some(Token::LeftParen) { - paren_call(p, ident).map(Expr::Call) + let name = ident.span_with(start .. after); + Expr::Call(paren_call(p, name)) } else { - ident.map(Lit::Ident).map(Expr::Lit) + Expr::Lit(Lit::Ident(ident)) } } // Atomic values. - Token::Bool(b) => Expr::Lit(Lit::Bool(b)).span_with(span), - Token::Number(f) => Expr::Lit(Lit::Float(f)).span_with(span), - Token::Length(l) => Expr::Lit(Lit::Length(l)).span_with(span), - Token::Hex(hex) => color(p, hex, span).map(Lit::Color).map(Expr::Lit), - Token::Str(token) => string(p, token, span).map(Lit::Str).map(Expr::Lit), + Token::Bool(b) => Expr::Lit(Lit::Bool(b)), + Token::Number(f) => Expr::Lit(Lit::Float(f)), + Token::Length(l) => Expr::Lit(Lit::Length(l)), + Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))), + Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))), // No value. _ => { - p.jump(span.start); + p.jump(start); return None; } }) } // Parse a content expression: `{...}`. -fn content(p: &mut Parser) -> Spanned<SynTree> { +fn content(p: &mut Parser) -> SynTree { p.start_group(Group::Brace); p.push_mode(TokenMode::Body); let tree = tree(p); p.pop_mode(); - tree.span_with(p.end_group()) + p.end_group(); + tree } /// Parse a parenthesized expression: `(a + b)`, `(1, key="value"). -fn parenthesized(p: &mut Parser) -> Spanned<Expr> { +fn parenthesized(p: &mut Parser) -> Expr { p.start_group(Group::Paren); let (dict, coercable) = dict_contents(p); let expr = if coercable { @@ -363,99 +457,12 @@ fn parenthesized(p: &mut Parser) -> Spanned<Expr> { } else { Expr::Lit(Lit::Dict(dict)) }; - expr.span_with(p.end_group()) -} - -/// Parse a parenthesized function call. -fn paren_call(p: &mut Parser, name: Spanned<Ident>) -> Spanned<ExprCall> { - p.start_group(Group::Paren); - let args = dict_contents(p).0; - let span = name.span.join(p.end_group()); - ExprCall { name, args }.span_with(span) -} - -/// Parse the contents of a dictionary. -fn dict_contents(p: &mut Parser) -> (LitDict, bool) { - let mut dict = LitDict::new(); - let mut comma_and_keyless = true; - - loop { - p.skip_white(); - if p.eof() { - break; - } - - let entry = if let Some(entry) = dict_entry(p) { - entry - } else { - p.diag_expected("value"); - continue; - }; - - if let Some(key) = &entry.key { - comma_and_keyless = false; - p.deco(Decoration::DictKey.span_with(key.span)); - } - - let behind = entry.expr.span.end; - dict.0.push(entry); - - p.skip_white(); - if p.eof() { - break; - } - - if p.eat_if(Token::Comma).is_none() { - p.diag_expected_at("comma", behind); - } - - comma_and_keyless = false; - } - - let coercable = comma_and_keyless && !dict.0.is_empty(); - (dict, coercable) -} - -/// Parse a single entry in a dictionary. -fn dict_entry(p: &mut Parser) -> Option<LitDictEntry> { - if let Some(ident) = ident(p) { - p.skip_white(); - match p.peek() { - // Key-value pair. - Some(Token::Equals) => { - p.eat_assert(Token::Equals); - p.skip_white(); - if let Some(expr) = expr(p) { - Some(LitDictEntry { - key: Some(ident.map(|id| DictKey::Str(id.0))), - expr, - }) - } else { - None - } - } - - // Function call. - Some(Token::LeftParen) => Some(LitDictEntry { - key: None, - expr: paren_call(p, ident).map(Expr::Call), - }), - - // Just an identifier. - _ => Some(LitDictEntry { - key: None, - expr: ident.map(|id| Expr::Lit(Lit::Ident(id))), - }), - } - } else if let Some(expr) = expr(p) { - Some(LitDictEntry { key: None, expr }) - } else { - None - } + p.end_group(); + expr } /// Parse an identifier. -fn ident(p: &mut Parser) -> Option<Spanned<Ident>> { +fn ident(p: &mut Parser) -> Option<Ident> { p.eat_map(|token| match token { Token::Ident(id) => Some(Ident(id.into())), _ => None, @@ -463,23 +470,21 @@ fn ident(p: &mut Parser) -> Option<Spanned<Ident>> { } /// Parse a color. -fn color(p: &mut Parser, hex: &str, span: Span) -> Spanned<RgbaColor> { - RgbaColor::from_str(hex) - .unwrap_or_else(|_| { - // Heal color by assuming black. - p.diag(error!(span, "invalid color")); - RgbaColor::new_healed(0, 0, 0, 255) - }) - .span_with(span) +fn color(p: &mut Parser, hex: &str, start: Pos) -> RgbaColor { + RgbaColor::from_str(hex).unwrap_or_else(|_| { + // Heal color by assuming black. + p.diag(error!(start .. p.pos(), "invalid color")); + RgbaColor::new_healed(0, 0, 0, 255) + }) } /// Parse a string. -fn string(p: &mut Parser, token: TokenStr, span: Span) -> Spanned<String> { +fn string(p: &mut Parser, token: TokenStr) -> String { if !token.terminated { - p.diag_expected_at("quote", span.end); + p.diag_expected_at("quote", p.pos()); } - resolve::resolve_string(token.string).span_with(span) + resolve::resolve_string(token.string) } #[cfg(test)] |
