[WIP] Label and reference syntax

This commit is contained in:
Laurenz 2022-08-30 15:00:18 +02:00
parent 0cb876ebf9
commit 0d12f2ab23
16 changed files with 129 additions and 42 deletions

View File

@ -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(),
})
}

View File

@ -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");

View File

@ -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::*;

View 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)))
}
}

View File

@ -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),
],
});

View File

@ -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();
}

View File

@ -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) {

View File

@ -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),
}

View File

@ -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",

View File

@ -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

View File

@ -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"

View File

@ -3,10 +3,10 @@
---
// Escapable symbols.
\\ \/ \[ \] \{ \} \# \* \_ \
\= \~ \` \$ \" \'
\= \~ \` \$ \" \' \< \> \@
// No need to escape.
( ) ; < >
( ) ;
// Unescapable.
\a \: \; \( \)

View File

@ -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.

View File

@ -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" }]
},