diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-08-30 15:00:18 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-09-07 11:07:17 +0200 |
| commit | 0d12f2ab23177642eef2e6bb9c583cdd0c743b33 (patch) | |
| tree | 03a88594081dcf360d0d880167feb1debca970e6 | |
| parent | 0cb876ebf9138c1ee3b3c87165952a73569ffb28 (diff) | |
[WIP] Label and reference syntax
| -rw-r--r-- | src/eval/mod.rs | 2 | ||||
| -rw-r--r-- | src/library/mod.rs | 1 | ||||
| -rw-r--r-- | src/library/structure/mod.rs | 2 | ||||
| -rw-r--r-- | src/library/structure/reference.rs | 28 | ||||
| -rw-r--r-- | src/library/text/raw.rs | 5 | ||||
| -rw-r--r-- | src/parse/mod.rs | 30 | ||||
| -rw-r--r-- | src/parse/tokens.rs | 31 | ||||
| -rw-r--r-- | src/syntax/ast.rs | 6 | ||||
| -rw-r--r-- | src/syntax/highlight.rs | 34 | ||||
| -rw-r--r-- | src/syntax/mod.rs | 8 | ||||
| -rw-r--r-- | tests/ref/text/escape.png | bin | 13729 -> 14079 bytes | |||
| -rw-r--r-- | tests/ref/text/raw.png | bin | 18090 -> 18137 bytes | |||
| -rw-r--r-- | tests/typ/code/import.typ | 8 | ||||
| -rw-r--r-- | tests/typ/text/escape.typ | 4 | ||||
| -rw-r--r-- | tests/typ/text/raw.typ | 10 | ||||
| -rw-r--r-- | tools/support/typst.tmLanguage.json | 2 |
16 files changed, 129 insertions, 42 deletions
diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 94d9ef40..7f182f48 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -203,6 +203,8 @@ impl Eval for MarkupNode { Self::Heading(heading) => heading.eval(vm)?, Self::List(list) => list.eval(vm)?, Self::Enum(enum_) => enum_.eval(vm)?, + Self::Label(_) => Content::Empty, + Self::Ref(label) => Content::show(library::structure::RefNode(label.clone())), Self::Expr(expr) => expr.eval(vm)?.display(), }) } diff --git a/src/library/mod.rs b/src/library/mod.rs index bd34590a..c1a645fb 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -37,6 +37,7 @@ pub fn new() -> Scope { std.def_fn("smallcaps", text::smallcaps); // Structure. + std.def_node::<structure::RefNode>("ref"); std.def_node::<structure::HeadingNode>("heading"); std.def_node::<structure::ListNode>("list"); std.def_node::<structure::EnumNode>("enum"); diff --git a/src/library/structure/mod.rs b/src/library/structure/mod.rs index a597211e..088d1e6c 100644 --- a/src/library/structure/mod.rs +++ b/src/library/structure/mod.rs @@ -3,9 +3,11 @@ mod doc; mod heading; mod list; +mod reference; mod table; pub use doc::*; pub use heading::*; pub use list::*; +pub use reference::*; pub use table::*; diff --git a/src/library/structure/reference.rs b/src/library/structure/reference.rs new file mode 100644 index 00000000..0eeb4bf5 --- /dev/null +++ b/src/library/structure/reference.rs @@ -0,0 +1,28 @@ +use crate::library::prelude::*; + +/// A reference to a label. +#[derive(Debug, Hash)] +pub struct RefNode(pub EcoString); + +#[node(showable)] +impl RefNode { + fn construct(_: &mut Machine, args: &mut Args) -> TypResult<Content> { + Ok(Content::show(Self(args.expect("label")?))) + } +} + +impl Show for RefNode { + fn unguard(&self, _: Selector) -> ShowNode { + Self(self.0.clone()).pack() + } + + fn encode(&self, _: StyleChain) -> Dict { + dict! { + "label" => Value::Str(self.0.clone().into()), + } + } + + fn realize(&self, _: &mut Context, _: StyleChain) -> TypResult<Content> { + Ok(Content::Text(format_eco!("@{}", self.0))) + } +} diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index e64636f8..a09d791d 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -169,6 +169,9 @@ pub static THEME: Lazy<Theme> = Lazy::new(|| Theme { item("markup.raw", Some("#818181"), None), item("markup.list", Some("#8b41b1"), None), item("comment", Some("#8a8a8a"), None), + item("punctuation.shortcut", Some("#1d6c76"), None), + item("constant.character.escape", Some("#1d6c76"), None), + item("entity.name.label, markup.other.reference", Some("#1d6c76"), None), item("keyword, constant.language, variable.language", Some("#d73a49"), None), item("storage.type, storage.modifier", Some("#d73a49"), None), item("entity.other", Some("#8b41b1"), None), @@ -177,8 +180,6 @@ pub static THEME: Lazy<Theme> = Lazy::new(|| Theme { item("meta.annotation", Some("#301414"), None), item("constant", Some("#b60157"), None), item("string", Some("#298e0d"), None), - item("punctuation.shortcut", Some("#1d6c76"), None), - item("constant.character.escape", Some("#1d6c76"), None), item("invalid", Some("#ff0000"), None), ], }); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index a997421e..0737d4ca 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -175,18 +175,24 @@ fn markup_indented(p: &mut Parser, min_indent: usize) { _ => false, }); + let marker = p.marker(); let mut at_start = false; - p.perform(NodeKind::Markup { min_indent }, |p| { - while !p.eof() { - if let Some(NodeKind::Space { newlines: (1 ..) }) = p.peek() { - if p.column(p.current_end()) < min_indent { - break; - } - } - markup_node(p, &mut at_start); + while !p.eof() { + match p.peek() { + Some(NodeKind::Space { newlines: (1 ..) }) + if p.column(p.current_end()) < min_indent => + { + break; + } + Some(NodeKind::Label(_)) => break, + _ => {} } - }); + + markup_node(p, &mut at_start); + } + + marker.end(p, NodeKind::Markup { min_indent }); } /// Parse a markup node. @@ -212,16 +218,18 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { // Text and markup. NodeKind::Text(_) + | NodeKind::Linebreak { .. } | NodeKind::NonBreakingSpace | NodeKind::Shy | NodeKind::EnDash | NodeKind::EmDash | NodeKind::Ellipsis | NodeKind::Quote { .. } - | NodeKind::Linebreak { .. } + | NodeKind::Escape(_) | NodeKind::Raw(_) | NodeKind::Math(_) - | NodeKind::Escape(_) => { + | NodeKind::Label(_) + | NodeKind::Ref(_) => { p.eat(); } diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index be107f3c..e004dd37 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -148,8 +148,10 @@ impl<'s> Tokens<'s> { '*' if !self.in_word() => NodeKind::Star, '_' if !self.in_word() => NodeKind::Underscore, '`' => self.raw(), - '$' => self.math(), '=' => NodeKind::Eq, + '$' => self.math(), + '<' => self.label(), + '@' => self.reference(), c if c == '.' || c.is_ascii_digit() => self.numbering(start, c), // Plain text. @@ -277,7 +279,9 @@ impl<'s> Tokens<'s> { // Parenthesis and hashtag. '[' | ']' | '{' | '}' | '#' | // Markup. - '~' | '\'' | '"' | '*' | '_' | '`' | '$' | '=' | '-' | '.' => { + '~' | '-' | '.' | ':' | + '\'' | '"' | '*' | '_' | '`' | '$' | '=' | + '<' | '>' | '@' => { self.s.expect(c); NodeKind::Escape(c) } @@ -453,6 +457,29 @@ impl<'s> Tokens<'s> { } } + fn label(&mut self) -> NodeKind { + let label = self.s.eat_while(is_id_continue); + if self.s.eat_if('>') { + if !label.is_empty() { + NodeKind::Label(label.into()) + } else { + NodeKind::Error(SpanPos::Full, "label cannot be empty".into()) + } + } else { + self.terminated = false; + NodeKind::Error(SpanPos::End, "expected closing angle bracket".into()) + } + } + + fn reference(&mut self) -> NodeKind { + let label = self.s.eat_while(is_id_continue); + if !label.is_empty() { + NodeKind::Ref(label.into()) + } else { + NodeKind::Error(SpanPos::Full, "label cannot be empty".into()) + } + } + fn ident(&mut self, start: usize) -> NodeKind { self.s.eat_while(is_id_continue); match self.s.from(start) { diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 67f9e038..10bee4e8 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -84,6 +84,8 @@ impl Markup { NodeKind::Heading => node.cast().map(MarkupNode::Heading), NodeKind::List => node.cast().map(MarkupNode::List), NodeKind::Enum => node.cast().map(MarkupNode::Enum), + NodeKind::Label(v) => Some(MarkupNode::Label(v.clone())), + NodeKind::Ref(v) => Some(MarkupNode::Ref(v.clone())), _ => node.cast().map(MarkupNode::Expr), }) } @@ -116,6 +118,10 @@ pub enum MarkupNode { List(ListNode), /// An item in an enumeration (ordered list): `1. ...`. Enum(EnumNode), + /// A label. + Label(EcoString), + /// A reference. + Ref(EcoString), /// An expression. Expr(Expr), } diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index b52234d4..ff02190a 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -153,6 +153,10 @@ pub enum Category { Punctuation, /// A line or block comment. Comment, + /// An easily typable shortcut to a unicode codepoint. + Shortcut, + /// An escape sequence. + Escape, /// Strong text. Strong, /// Emphasized text. @@ -165,10 +169,10 @@ pub enum Category { Heading, /// A list or enumeration. List, - /// An easily typable shortcut to a unicode codepoint. - Shortcut, - /// An escape sequence. - Escape, + /// A label. + Label, + /// A reference. + Ref, /// A keyword. Keyword, /// An operator symbol. @@ -212,6 +216,13 @@ impl Category { NodeKind::Dot => Some(Category::Punctuation), NodeKind::LineComment => Some(Category::Comment), NodeKind::BlockComment => Some(Category::Comment), + NodeKind::Linebreak { .. } => Some(Category::Shortcut), + NodeKind::NonBreakingSpace => Some(Category::Shortcut), + NodeKind::Shy => Some(Category::Shortcut), + NodeKind::EnDash => Some(Category::Shortcut), + NodeKind::EmDash => Some(Category::Shortcut), + NodeKind::Ellipsis => Some(Category::Shortcut), + NodeKind::Escape(_) => Some(Category::Escape), NodeKind::Strong => Some(Category::Strong), NodeKind::Emph => Some(Category::Emph), NodeKind::Raw(_) => Some(Category::Raw), @@ -222,13 +233,8 @@ impl Category { _ => Some(Category::Operator), }, NodeKind::EnumNumbering(_) => Some(Category::List), - NodeKind::Linebreak { .. } => Some(Category::Shortcut), - NodeKind::NonBreakingSpace => Some(Category::Shortcut), - NodeKind::Shy => Some(Category::Shortcut), - NodeKind::EnDash => Some(Category::Shortcut), - NodeKind::EmDash => Some(Category::Shortcut), - NodeKind::Ellipsis => Some(Category::Shortcut), - NodeKind::Escape(_) => Some(Category::Escape), + NodeKind::Label(_) => Some(Category::Label), + NodeKind::Ref(_) => Some(Category::Ref), NodeKind::Not => Some(Category::Keyword), NodeKind::And => Some(Category::Keyword), NodeKind::Or => Some(Category::Keyword), @@ -344,14 +350,16 @@ impl Category { Self::Bracket => "punctuation.definition.typst", Self::Punctuation => "punctuation.typst", Self::Comment => "comment.typst", + Self::Shortcut => "punctuation.shortcut.typst", + Self::Escape => "constant.character.escape.content.typst", Self::Strong => "markup.bold.typst", Self::Emph => "markup.italic.typst", Self::Raw => "markup.raw.typst", Self::Math => "string.other.math.typst", Self::Heading => "markup.heading.typst", Self::List => "markup.list.typst", - Self::Shortcut => "punctuation.shortcut.typst", - Self::Escape => "constant.character.escape.content.typst", + Self::Label => "entity.name.label.typst", + Self::Ref => "markup.other.reference.typst", Self::Keyword => "keyword.typst", Self::Operator => "keyword.operator.typst", Self::None => "constant.language.none.typst", diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 4bae7a4b..eb070a04 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -723,6 +723,10 @@ pub enum NodeKind { /// /// Can also exist without the number: `.`. EnumNumbering(Option<usize>), + /// A label: `<label>`. + Label(EcoString), + /// A reference: `@label`. + Ref(EcoString), /// An identifier: `center`. Ident(EcoString), /// A boolean: `true`, `false`. @@ -935,6 +939,8 @@ impl NodeKind { Self::Heading => "heading", Self::Enum => "enumeration item", Self::EnumNumbering(_) => "enumeration item numbering", + Self::Label(_) => "label", + Self::Ref(_) => "reference", Self::Ident(_) => "identifier", Self::Bool(_) => "boolean", Self::Int(_) => "integer", @@ -1060,6 +1066,8 @@ impl Hash for NodeKind { Self::Heading => {} Self::Enum => {} Self::EnumNumbering(num) => num.hash(state), + Self::Label(c) => c.hash(state), + Self::Ref(c) => c.hash(state), Self::Ident(v) => v.hash(state), Self::Bool(v) => v.hash(state), Self::Int(v) => v.hash(state), diff --git a/tests/ref/text/escape.png b/tests/ref/text/escape.png Binary files differindex 77cc21f2..5ee9dbf8 100644 --- a/tests/ref/text/escape.png +++ b/tests/ref/text/escape.png diff --git a/tests/ref/text/raw.png b/tests/ref/text/raw.png Binary files differindex 2c101dcb..3a7691fd 100644 --- a/tests/ref/text/raw.png +++ b/tests/ref/text/raw.png diff --git a/tests/typ/code/import.typ b/tests/typ/code/import.typ index aef5e9c4..2c27f135 100644 --- a/tests/typ/code/import.typ +++ b/tests/typ/code/import.typ @@ -30,7 +30,7 @@ // Who needs whitespace anyways? #import*from"target.typ" -// Should output `Hi`. +// Should output `bye`. // Stop at semicolon. #import a, c from "target.typ";bye @@ -93,25 +93,21 @@ This is never reached. // Error: 17-18 expected expression, found comma #import a, b, c,, from "target.typ" -// Should output `"target.typ"`. // Error: 1-6 unexpected keyword `from` #from "target.typ" -// Should output `target`. // Error: 2:2 expected semicolon or line break #import * from "target.typ "target -// Should output `@ 0.2.1`. // Error: 28 expected semicolon or line break -#import * from "target.typ" @ 0.2.1 +#import * from "target.typ" ยง 0.2.1 // A star in the list. // Error: 12-13 expected expression, found star #import a, *, b from "target.typ" // An item after a star. -// Should output `, a from "target.typ"`. // Error: 10 expected keyword `from` #import *, a from "target.typ" diff --git a/tests/typ/text/escape.typ b/tests/typ/text/escape.typ index e03d73e5..ccaf78e9 100644 --- a/tests/typ/text/escape.typ +++ b/tests/typ/text/escape.typ @@ -3,10 +3,10 @@ --- // Escapable symbols. \\ \/ \[ \] \{ \} \# \* \_ \ -\= \~ \` \$ \" \' +\= \~ \` \$ \" \' \< \> \@ // No need to escape. -( ) ; < > +( ) ; // Unescapable. \a \: \; \( \) diff --git a/tests/typ/text/raw.typ b/tests/typ/text/raw.typ index cf497d75..c17c8fec 100644 --- a/tests/typ/text/raw.typ +++ b/tests/typ/text/raw.typ @@ -31,11 +31,11 @@ Text The keyword ```rust let```. // Trimming depends on number backticks. -<``> \ -<` untrimmed `> \ -<``` trimmed` ```> \ -<``` trimmed ```> \ -<``` trimmed```> +(``) \ +(` untrimmed `) \ +(``` trimmed` ```) \ +(``` trimmed ```) \ +(``` trimmed```) \ --- // First line is not dedented and leading space is still possible. diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index 0f8102e6..8e1a66c4 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -81,7 +81,7 @@ "name": "markup.heading.typst", "contentName": "entity.name.section.typst", "begin": "^\\s*=+\\s+", - "end": "\n", + "end": "\n|(?=<)", "beginCaptures": { "0": { "name": "punctuation.definition.heading.typst" } }, "patterns": [{ "include": "#markup" }] }, |
