From c2e458a133772a94009733040b39d58e781af977 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 5 Dec 2022 12:25:37 +0100 Subject: [PATCH] Symbol notation --- Cargo.lock | 6 +++++ library/Cargo.toml | 1 + library/src/lib.rs | 2 ++ library/src/text/mod.rs | 2 ++ library/src/text/symbol.rs | 35 ++++++++++++++++++++++++++++ src/model/eval.rs | 9 +++++++ src/model/library.rs | 2 ++ src/syntax/ast.rs | 19 +++++++++++++++ src/syntax/highlight.rs | 4 ++++ src/syntax/kind.rs | 5 ++++ src/syntax/parsing.rs | 1 + src/syntax/tokens.rs | 21 ++++++++++++++++- tests/ref/text/symbol.png | Bin 0 -> 14112 bytes tests/typ/text/symbol.typ | 27 +++++++++++++++++++++ tools/support/typst.tmLanguage.json | 4 ++++ 15 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 library/src/text/symbol.rs create mode 100644 tests/ref/text/symbol.png create mode 100644 tests/typ/text/symbol.typ diff --git a/Cargo.lock b/Cargo.lock index 222e1c98..fe522d84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,6 +1009,11 @@ dependencies = [ "siphasher", ] +[[package]] +name = "symmie" +version = "0.1.0" +source = "git+https://github.com/typst/symmie#8504bf7ec0d8996d160832c2724ba024ab6e988a" + [[package]] name = "syn" version = "1.0.102" @@ -1175,6 +1180,7 @@ dependencies = [ "roxmltree", "rustybuzz", "serde_json", + "symmie", "syntect", "ttf-parser 0.17.1", "typed-arena", diff --git a/library/Cargo.toml b/library/Cargo.toml index 92bf84a2..2410cb0c 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -21,6 +21,7 @@ rex = { git = "https://github.com/laurmaedje/ReX" } roxmltree = "0.14" rustybuzz = "0.5" serde_json = "1" +symmie = { git = "https://github.com/typst/symmie" } syntect = { version = "5", default-features = false, features = ["default-syntaxes", "regex-fancy"] } ttf-parser = "0.17" typed-arena = "2" diff --git a/library/src/lib.rs b/library/src/lib.rs index 3543a672..d549c1cd 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -34,6 +34,7 @@ fn scope() -> Scope { // Text. std.def_node::("text"); std.def_node::("linebreak"); + std.def_node::("symbol"); std.def_node::("smartquote"); std.def_node::("strong"); std.def_node::("emph"); @@ -173,6 +174,7 @@ fn items() -> LangItems { text: |text| text::TextNode(text).pack(), text_id: NodeId::of::(), text_str: |content| Some(&content.to::()?.0), + symbol: |notation| text::SymbolNode(notation).pack(), smart_quote: |double| text::SmartQuoteNode { double }.pack(), parbreak: || layout::ParbreakNode.pack(), strong: |body| text::StrongNode(body).pack(), diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 47aaba36..5466637e 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -6,6 +6,7 @@ mod quotes; mod raw; mod shaping; mod shift; +mod symbol; pub use self::deco::*; pub use self::misc::*; @@ -13,6 +14,7 @@ pub use self::quotes::*; pub use self::raw::*; pub use self::shaping::*; pub use self::shift::*; +pub use self::symbol::*; use std::borrow::Cow; diff --git a/library/src/text/symbol.rs b/library/src/text/symbol.rs new file mode 100644 index 00000000..cc12afb9 --- /dev/null +++ b/library/src/text/symbol.rs @@ -0,0 +1,35 @@ +use crate::prelude::*; +use crate::text::TextNode; + +/// A symbol identified by symmie notation. +#[derive(Debug, Hash)] +pub struct SymbolNode(pub EcoString); + +#[node(Show)] +impl SymbolNode { + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("notation")?).pack()) + } + + fn field(&self, name: &str) -> Option { + match name { + "notation" => Some(Value::Str(self.0.clone().into())), + _ => None, + } + } +} + +impl Show for SymbolNode { + fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult { + match symmie::get(&self.0) { + Some(c) => Ok(TextNode::packed(c)), + None => { + if let Some(span) = this.span() { + bail!(span, "unknown symbol"); + } + + Ok(Content::empty()) + } + } + } +} diff --git a/src/model/eval.rs b/src/model/eval.rs index 43cdbbcc..1d942dd0 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -265,6 +265,7 @@ impl Eval for ast::MarkupNode { Self::Text(v) => v.eval(vm)?, Self::Escape(v) => (vm.items.text)(v.get().into()), Self::Shorthand(v) => v.eval(vm)?, + Self::Symbol(v) => v.eval(vm)?, Self::SmartQuote(v) => v.eval(vm)?, Self::Strong(v) => v.eval(vm)?, Self::Emph(v) => v.eval(vm)?, @@ -306,6 +307,14 @@ impl Eval for ast::Shorthand { } } +impl Eval for ast::Symbol { + type Output = Content; + + fn eval(&self, vm: &mut Vm) -> SourceResult { + Ok((vm.items.symbol)(self.get().clone())) + } +} + impl Eval for ast::SmartQuote { type Output = Content; diff --git a/src/model/library.rs b/src/model/library.rs index c890fef1..63bd5839 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -41,6 +41,8 @@ pub struct LangItems { pub text_id: NodeId, /// Get the string if this is a text node. pub text_str: fn(&Content) -> Option<&str>, + /// Symbol notation: `:arrow:l:`. + pub symbol: fn(notation: EcoString) -> Content, /// A smart quote: `'` or `"`. pub smart_quote: fn(double: bool) -> Content, /// A paragraph break. diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 3c60acbb..c44fa2a0 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -85,6 +85,8 @@ pub enum MarkupNode { /// A shorthand for a unicode codepoint. For example, `~` for non-breaking /// space or `-?` for a soft hyphen. Shorthand(Shorthand), + /// Symbol notation: `:arrow:l:`. + Symbol(Symbol), /// A smart quote: `'` or `"`. SmartQuote(SmartQuote), /// Strong content: `*Strong*`. @@ -119,6 +121,7 @@ impl AstNode for MarkupNode { SyntaxKind::Text(_) => node.cast().map(Self::Text), SyntaxKind::Escape(_) => node.cast().map(Self::Escape), SyntaxKind::Shorthand(_) => node.cast().map(Self::Shorthand), + SyntaxKind::Symbol(_) => node.cast().map(Self::Symbol), SyntaxKind::SmartQuote { .. } => node.cast().map(Self::SmartQuote), SyntaxKind::Strong => node.cast().map(Self::Strong), SyntaxKind::Emph => node.cast().map(Self::Emph), @@ -141,6 +144,7 @@ impl AstNode for MarkupNode { Self::Text(v) => v.as_untyped(), Self::Escape(v) => v.as_untyped(), Self::Shorthand(v) => v.as_untyped(), + Self::Symbol(v) => v.as_untyped(), Self::SmartQuote(v) => v.as_untyped(), Self::Strong(v) => v.as_untyped(), Self::Emph(v) => v.as_untyped(), @@ -223,6 +227,21 @@ impl Shorthand { } } +node! { + /// Symbol notation: `:arrow:l:`. + Symbol +} + +impl Symbol { + /// Get the symbol's notation. + pub fn get(&self) -> &EcoString { + match self.0.kind() { + SyntaxKind::Symbol(v) => v, + _ => panic!("symbol is of wrong kind"), + } + } +} + node! { /// A smart quote: `'` or `"`. SmartQuote diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 526d1302..d4da7b3e 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -139,6 +139,8 @@ pub enum Category { Escape, /// An easily typable shortcut to a unicode codepoint. Shorthand, + /// Symbol notation. + Symbol, /// A smart quote. SmartQuote, /// Strong markup. @@ -285,6 +287,7 @@ impl Category { SyntaxKind::Linebreak => Some(Category::Escape), SyntaxKind::Escape(_) => Some(Category::Escape), SyntaxKind::Shorthand(_) => Some(Category::Shorthand), + SyntaxKind::Symbol(_) => Some(Category::Symbol), SyntaxKind::SmartQuote { .. } => Some(Category::SmartQuote), SyntaxKind::Strong => Some(Category::Strong), SyntaxKind::Emph => Some(Category::Emph), @@ -369,6 +372,7 @@ impl Category { Self::Punctuation => "punctuation.typst", Self::Escape => "constant.character.escape.typst", Self::Shorthand => "constant.character.shorthand.typst", + Self::Symbol => "constant.symbol.typst", Self::SmartQuote => "constant.character.quote.typst", Self::Strong => "markup.bold.typst", Self::Emph => "markup.italic.typst", diff --git a/src/syntax/kind.rs b/src/syntax/kind.rs index a7425d70..a4eb317b 100644 --- a/src/syntax/kind.rs +++ b/src/syntax/kind.rs @@ -144,6 +144,9 @@ pub enum SyntaxKind { /// A shorthand for a unicode codepoint. For example, `~` for non-breaking /// space or `-?` for a soft hyphen. Shorthand(char), + /// Symbol notation: `:arrow:l:`. The string only contains the inner part + /// without leading and trailing dot. + Symbol(EcoString), /// A smart quote: `'` or `"`. SmartQuote { double: bool }, /// Strong content: `*Strong*`. @@ -389,6 +392,7 @@ impl SyntaxKind { Self::Linebreak => "linebreak", Self::Escape(_) => "escape sequence", Self::Shorthand(_) => "shorthand", + Self::Symbol(_) => "symbol notation", Self::Strong => "strong content", Self::Emph => "emphasized content", Self::Raw(_) => "raw block", @@ -507,6 +511,7 @@ impl Hash for SyntaxKind { Self::Linebreak => {} Self::Escape(c) => c.hash(state), Self::Shorthand(c) => c.hash(state), + Self::Symbol(s) => s.hash(state), Self::SmartQuote { double } => double.hash(state), Self::Strong => {} Self::Emph => {} diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 1678eb01..59e066a6 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -230,6 +230,7 @@ fn markup_node(p: &mut Parser, at_start: &mut bool) { | SyntaxKind::SmartQuote { .. } | SyntaxKind::Escape(_) | SyntaxKind::Shorthand(_) + | SyntaxKind::Symbol(_) | SyntaxKind::Link(_) | SyntaxKind::Raw(_) | SyntaxKind::Ref(_) => p.eat(), diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index e7015bb2..130ad668 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -203,6 +203,7 @@ impl<'s> Tokens<'s> { '#' => self.hash(start), '.' if self.s.eat_if("..") => SyntaxKind::Shorthand('\u{2026}'), '-' => self.hyph(), + ':' => self.colon(), 'h' if self.s.eat_if("ttp://") || self.s.eat_if("ttps://") => { self.link(start) } @@ -224,7 +225,6 @@ impl<'s> Tokens<'s> { '=' => SyntaxKind::Eq, '+' => SyntaxKind::Plus, '/' => SyntaxKind::Slash, - ':' => SyntaxKind::Colon, // Plain text. _ => self.text(start), @@ -328,6 +328,25 @@ impl<'s> Tokens<'s> { } } + fn colon(&mut self) -> SyntaxKind { + let start = self.s.cursor(); + let mut end = start; + while !self.s.eat_while(char::is_ascii_alphanumeric).is_empty() && self.s.at(':') + { + end = self.s.cursor(); + self.s.eat(); + } + + self.s.jump(end); + + if start < end { + self.s.expect(':'); + SyntaxKind::Symbol(self.s.get(start..end).into()) + } else { + SyntaxKind::Colon + } + } + fn link(&mut self, start: usize) -> SyntaxKind { #[rustfmt::skip] self.s.eat_while(|c: char| matches!(c, diff --git a/tests/ref/text/symbol.png b/tests/ref/text/symbol.png new file mode 100644 index 0000000000000000000000000000000000000000..4806683b81e22556417ac11505c52532d4a89526 GIT binary patch literal 14112 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 literal 0 HcmV?d00001 diff --git a/tests/typ/text/symbol.typ b/tests/typ/text/symbol.typ new file mode 100644 index 00000000..4dcef58b --- /dev/null +++ b/tests/typ/text/symbol.typ @@ -0,0 +1,27 @@ +// Test symbol notation. + +--- +:face: +:face:unknown: +:woman:old: +:turtle: + +#set text("NewComputerModernMath") +:arrow: +:arrow:l: +:arrow:r:squiggly: +#symbol(("arrow", "tr", "hook").join(":")) + +--- +Just a: colon. \ +Still :not a symbol. \ +Also not:a symbol \ +:arrow:r:this and this:arrow:l: \ + +--- +#show symbol.where(notation: "my:custom"): "MY" +This is :my:custom: notation. + +--- +// Error: 1-14 unknown symbol +:nonexisting: diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index f8d66234..c849382e 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -72,6 +72,10 @@ "name": "punctuation.definition.ellipsis.typst", "match": "\\.\\.\\." }, + { + "name": "constant.symbol.typst", + "match": ":([a-zA-Z0-9]+:)+" + }, { "name": "markup.bold.typst", "begin": "(^\\*|\\*$|((?<=\\W|_)\\*)|(\\*(?=\\W|_)))",