diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-21 17:46:09 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-21 17:50:56 +0100 |
| commit | 5e08028fb36aa766957cba64c5c665edf9b96fb7 (patch) | |
| tree | 912799dad3c1e25b7032f3e3bee009537c6f555b /src/parse | |
| parent | 898728f260923a91444eb23b522d0abf01a4299b (diff) | |
Syntax functions 🚀
This adds overridable functions that markup desugars into. Specifically:
- \ desugars into linebreak
- Two newlines desugar into parbreak
- * desugars into strong
- _ desugars into emph
- = .. desugars into heading
- `..` desugars into raw
Diffstat (limited to 'src/parse')
| -rw-r--r-- | src/parse/mod.rs | 58 | ||||
| -rw-r--r-- | src/parse/resolve.rs | 95 |
2 files changed, 76 insertions, 77 deletions
diff --git a/src/parse/mod.rs b/src/parse/mod.rs index ceb8a206..b4727fe9 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -31,7 +31,7 @@ fn tree(p: &mut Parser) -> Tree { let mut tree = vec![]; while !p.eof() { if let Some(node) = node(p, &mut at_start) { - if !matches!(node, Node::Parbreak | Node::Space) { + if !matches!(node, Node::Parbreak(_) | Node::Space) { at_start = false; } tree.push(node); @@ -43,19 +43,24 @@ fn tree(p: &mut Parser) -> Tree { /// Parse a syntax node. fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { let token = p.peek()?; + let span = p.peek_span(); let node = match token { // Whitespace. Token::Space(newlines) => { *at_start |= newlines > 0; - if newlines < 2 { Node::Space } else { Node::Parbreak } + if newlines < 2 { + Node::Space + } else { + Node::Parbreak(span) + } } // Text. Token::Text(text) => Node::Text(text.into()), // Markup. - Token::Star => Node::Strong, - Token::Underscore => Node::Emph, + Token::Star => Node::Strong(span), + Token::Underscore => Node::Emph(span), Token::Eq => { if *at_start { return Some(heading(p)); @@ -64,7 +69,7 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> { } } Token::Tilde => Node::Text("\u{00A0}".into()), - Token::Backslash => Node::Linebreak, + Token::Backslash => Node::Linebreak(span), Token::Raw(t) => raw(p, t), Token::UnicodeEscape(t) => Node::Text(unicode_escape(p, t)), @@ -120,28 +125,33 @@ fn heading(p: &mut Parser) -> Node { p.assert(Token::Eq); // Count depth. - let mut level: usize = 0; + let mut level: usize = 1; while p.eat_if(Token::Eq) { level += 1; } - if level > 5 { + if level > 6 { p.diag(warning!(start .. p.end(), "should not exceed depth 6")); - level = 5; + level = 6; } // Parse the heading contents. - let mut contents = vec![]; + let mut tree = vec![]; while p.check(|t| !matches!(t, Token::Space(n) if n >= 1)) { - contents.extend(node(p, &mut false)); + tree.extend(node(p, &mut false)); } - Node::Heading(HeadingNode { level, contents }) + Node::Heading(HeadingNode { + span: p.span(start), + level, + contents: Rc::new(tree), + }) } /// Handle a raw block. fn raw(p: &mut Parser, token: RawToken) -> Node { - let raw = resolve::resolve_raw(token.text, token.backticks, p.start()); + let span = p.peek_span(); + let raw = resolve::resolve_raw(span, token.text, token.backticks); if !token.terminated { p.diag(error!(p.peek_span().end, "expected backtick(s)")); } @@ -280,17 +290,18 @@ fn primary(p: &mut Parser, atomic: bool) -> Option<Expr> { /// Parse a literal. fn literal(p: &mut Parser) -> Option<Expr> { - let kind = match p.peek()? { + let span = p.peek_span(); + let expr = match p.peek()? { // Basic values. - Token::None => LitKind::None, - Token::Bool(b) => LitKind::Bool(b), - Token::Int(i) => LitKind::Int(i), - Token::Float(f) => LitKind::Float(f), - Token::Length(val, unit) => LitKind::Length(val, unit), - Token::Angle(val, unit) => LitKind::Angle(val, unit), - Token::Percent(p) => LitKind::Percent(p), - Token::Color(color) => LitKind::Color(color), - Token::Str(token) => LitKind::Str({ + Token::None => Expr::None(span), + Token::Bool(b) => Expr::Bool(span, b), + Token::Int(i) => Expr::Int(span, i), + Token::Float(f) => Expr::Float(span, f), + Token::Length(val, unit) => Expr::Length(span, val, unit), + Token::Angle(val, unit) => Expr::Angle(span, val, unit), + Token::Percent(p) => Expr::Percent(span, p), + Token::Color(color) => Expr::Color(span, color), + Token::Str(token) => Expr::Str(span, { if !token.terminated { p.expected_at("quote", p.peek_span().end); } @@ -298,7 +309,8 @@ fn literal(p: &mut Parser) -> Option<Expr> { }), _ => return None, }; - Some(Expr::Lit(Lit { span: p.eat_span(), kind })) + p.eat(); + Some(expr) } /// Parse something that starts with a parenthesis, which can be either of: diff --git a/src/parse/resolve.rs b/src/parse/resolve.rs index 1f33198a..b3fdef4a 100644 --- a/src/parse/resolve.rs +++ b/src/parse/resolve.rs @@ -1,5 +1,5 @@ use super::{is_newline, Scanner}; -use crate::syntax::{Ident, Pos, RawNode}; +use crate::syntax::{Ident, RawNode, Span}; /// Resolve all escape sequences in a string. pub fn resolve_string(string: &str) -> String { @@ -47,19 +47,17 @@ pub fn resolve_hex(sequence: &str) -> Option<char> { } /// Resolve the language tag and trims the raw text. -pub fn resolve_raw(text: &str, backticks: usize, start: Pos) -> RawNode { +pub fn resolve_raw(span: Span, text: &str, backticks: usize) -> RawNode { if backticks > 1 { let (tag, inner) = split_at_lang_tag(text); - let (lines, had_newline) = trim_and_split_raw(inner); - RawNode { - lang: Ident::new(tag, start .. start + tag.len()), - lines, - block: had_newline, - } + let (text, block) = trim_and_split_raw(inner); + let lang = Ident::new(tag, span.start .. span.start + tag.len()); + RawNode { span, lang, text, block } } else { RawNode { + span, lang: None, - lines: split_lines(text), + text: split_lines(text).join("\n"), block: false, } } @@ -77,7 +75,7 @@ fn split_at_lang_tag(raw: &str) -> (&str, &str) { /// Trim raw text and splits it into lines. /// /// Returns whether at least one newline was contained in `raw`. -fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) { +fn trim_and_split_raw(mut raw: &str) -> (String, bool) { // Trims one space at the start. raw = raw.strip_prefix(' ').unwrap_or(raw); @@ -87,8 +85,8 @@ fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) { } let mut lines = split_lines(raw); - let had_newline = lines.len() > 1; let is_whitespace = |line: &String| line.chars().all(char::is_whitespace); + let had_newline = lines.len() > 1; // Trims a sequence of whitespace followed by a newline at the start. if lines.first().map_or(false, is_whitespace) { @@ -100,7 +98,7 @@ fn trim_and_split_raw(mut raw: &str) -> (Vec<String>, bool) { lines.pop(); } - (lines, had_newline) + (lines.join("\n"), had_newline) } /// Split a string into a vector of lines @@ -171,64 +169,53 @@ mod tests { raw: &str, backticks: usize, lang: Option<&str>, - lines: &[&str], + text: &str, block: bool, ) { - Span::without_cmp(|| assert_eq!(resolve_raw(raw, backticks, Pos(0)), RawNode { - lang: lang.and_then(|id| Ident::new(id, 0)), - lines: lines.iter().map(ToString::to_string).collect(), - block, - })); + Span::without_cmp(|| { + assert_eq!(resolve_raw(Span::ZERO, raw, backticks), RawNode { + span: Span::ZERO, + lang: lang.and_then(|id| Ident::new(id, 0)), + text: text.into(), + block, + }); + }); } // Just one backtick. - test("py", 1, None, &["py"], false); - test("1\n2", 1, None, &["1", "2"], false); - test("1\r\n2", 1, None, &["1", "2"], false); + test("py", 1, None, "py", false); + test("1\n2", 1, None, "1\n2", false); + test("1\r\n2", 1, None, "1\n2", false); // More than one backtick with lang tag. - test("js alert()", 2, Some("js"), &["alert()"], false); - test("py quit(\n\n)", 3, Some("py"), &["quit(", "", ")"], true); - test("♥", 2, None, &[], false); + test("js alert()", 2, Some("js"), "alert()", false); + test("py quit(\n\n)", 3, Some("py"), "quit(\n\n)", true); + test("♥", 2, None, "", false); // Trimming of whitespace (tested more thoroughly in separate test). - test(" a", 2, None, &["a"], false); - test(" a", 2, None, &[" a"], false); - test(" \na", 2, None, &["a"], true); + test(" a", 2, None, "a", false); + test(" a", 2, None, " a", false); + test(" \na", 2, None, "a", true); } #[test] fn test_trim_raw() { #[track_caller] - fn test(text: &str, expected: Vec<&str>) { + fn test(text: &str, expected: &str) { assert_eq!(trim_and_split_raw(text).0, expected); } - test(" hi", vec!["hi"]); - test(" hi", vec![" hi"]); - test("\nhi", vec!["hi"]); - test(" \n hi", vec![" hi"]); - test("hi` ", vec!["hi`"]); - test("hi` ", vec!["hi` "]); - test("hi` ", vec!["hi` "]); - test("hi ", vec!["hi "]); - test("hi ", vec!["hi "]); - test("hi\n", vec!["hi"]); - test("hi \n ", vec!["hi "]); - test(" \n hi \n ", vec![" hi "]); - } - - #[test] - fn test_split_lines() { - #[track_caller] - fn test(text: &str, expected: Vec<&str>) { - assert_eq!(split_lines(text), expected); - } - - test("raw\ntext", vec!["raw", "text"]); - test("a\r\nb", vec!["a", "b"]); - test("a\n\nb", vec!["a", "", "b"]); - test("a\r\x0Bb", vec!["a", "", "b"]); - test("a\r\n\r\nb", vec!["a", "", "b"]); + test(" hi", "hi"); + test(" hi", " hi"); + test("\nhi", "hi"); + test(" \n hi", " hi"); + test("hi` ", "hi`"); + test("hi` ", "hi` "); + test("hi` ", "hi` "); + test("hi ", "hi "); + test("hi ", "hi "); + test("hi\n", "hi"); + test("hi \n ", "hi "); + test(" \n hi \n ", " hi "); } } |
