[WIP] Label and reference syntax
This commit is contained in:
parent
0cb876ebf9
commit
0d12f2ab23
@ -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(),
|
||||
})
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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::*;
|
||||
|
28
src/library/structure/reference.rs
Normal file
28
src/library/structure/reference.rs
Normal file
@ -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)))
|
||||
}
|
||||
}
|
@ -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),
|
||||
],
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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),
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@ -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"
|
||||
|
||||
|
@ -3,10 +3,10 @@
|
||||
---
|
||||
// Escapable symbols.
|
||||
\\ \/ \[ \] \{ \} \# \* \_ \
|
||||
\= \~ \` \$ \" \'
|
||||
\= \~ \` \$ \" \' \< \> \@
|
||||
|
||||
// No need to escape.
|
||||
( ) ; < >
|
||||
( ) ;
|
||||
|
||||
// Unescapable.
|
||||
\a \: \; \( \)
|
||||
|
@ -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.
|
||||
|
@ -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" }]
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user