Basic enums
This commit is contained in:
parent
3330767c20
commit
4dbd9285c9
@ -57,10 +57,11 @@ impl ExecWithMap for syntax::Node {
|
||||
Self::Parbreak(_) => ctx.parbreak(),
|
||||
Self::Strong(_) => ctx.state.font_mut().strong ^= true,
|
||||
Self::Emph(_) => ctx.state.font_mut().emph ^= true,
|
||||
Self::Raw(raw) => raw.exec(ctx),
|
||||
Self::Heading(heading) => heading.exec_with_map(ctx, map),
|
||||
Self::List(list) => list.exec_with_map(ctx, map),
|
||||
Self::Expr(expr) => map[&(expr as *const _)].exec(ctx),
|
||||
Self::Raw(n) => n.exec(ctx),
|
||||
Self::Heading(n) => n.exec_with_map(ctx, map),
|
||||
Self::List(n) => n.exec_with_map(ctx, map),
|
||||
Self::Enum(n) => n.exec_with_map(ctx, map),
|
||||
Self::Expr(n) => map[&(n as *const _)].exec(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -98,33 +99,43 @@ impl ExecWithMap for syntax::HeadingNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecWithMap for syntax::ListNode {
|
||||
impl ExecWithMap for syntax::ListItem {
|
||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
||||
ctx.parbreak();
|
||||
|
||||
let bullet = ctx.exec_stack(|ctx| ctx.push_text("•"));
|
||||
let body = ctx.exec_tree_stack(&self.body, map);
|
||||
|
||||
let stack = StackNode {
|
||||
dirs: Gen::new(Dir::TTB, ctx.state.lang.dir),
|
||||
aspect: None,
|
||||
children: vec![
|
||||
StackChild::Any(bullet.into(), Gen::default()),
|
||||
StackChild::Spacing(ctx.state.font.size / 2.0),
|
||||
StackChild::Any(body.into(), Gen::default()),
|
||||
],
|
||||
};
|
||||
|
||||
ctx.push(FixedNode {
|
||||
width: None,
|
||||
height: None,
|
||||
child: stack.into(),
|
||||
});
|
||||
|
||||
ctx.parbreak();
|
||||
exec_item(ctx, "•".to_string(), &self.body, map);
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecWithMap for syntax::EnumItem {
|
||||
fn exec_with_map(&self, ctx: &mut ExecContext, map: &ExprMap) {
|
||||
let label = self.number.unwrap_or(1).to_string() + ".";
|
||||
exec_item(ctx, label, &self.body, map);
|
||||
}
|
||||
}
|
||||
|
||||
fn exec_item(ctx: &mut ExecContext, label: String, body: &syntax::Tree, map: &ExprMap) {
|
||||
ctx.parbreak();
|
||||
|
||||
let label = ctx.exec_stack(|ctx| ctx.push_text(label));
|
||||
let body = ctx.exec_tree_stack(body, map);
|
||||
let stack = StackNode {
|
||||
dirs: Gen::new(Dir::TTB, ctx.state.lang.dir),
|
||||
aspect: None,
|
||||
children: vec![
|
||||
StackChild::Any(label.into(), Gen::default()),
|
||||
StackChild::Spacing(ctx.state.font.size / 2.0),
|
||||
StackChild::Any(body.into(), Gen::default()),
|
||||
],
|
||||
};
|
||||
|
||||
ctx.push(FixedNode {
|
||||
width: None,
|
||||
height: None,
|
||||
child: stack.into(),
|
||||
});
|
||||
|
||||
ctx.parbreak();
|
||||
}
|
||||
|
||||
impl Exec for Value {
|
||||
fn exec(&self, ctx: &mut ExecContext) {
|
||||
match self {
|
||||
|
@ -25,25 +25,33 @@ pub fn parse(src: &str) -> Pass<Tree> {
|
||||
|
||||
/// Parse a syntax tree.
|
||||
fn tree(p: &mut Parser) -> Tree {
|
||||
tree_while(p, |_| true)
|
||||
tree_while(p, true, |_| true)
|
||||
}
|
||||
|
||||
/// Parse a syntax tree that stays right of the column at the start of the next
|
||||
/// non-whitespace token.
|
||||
fn tree_indented(p: &mut Parser) -> Tree {
|
||||
p.skip_white();
|
||||
p.eat_while(|t| match t {
|
||||
Token::Space(n) => n == 0,
|
||||
Token::LineComment(_) | Token::BlockComment(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let column = p.column(p.next_start());
|
||||
tree_while(p, |p| match p.peek() {
|
||||
tree_while(p, false, |p| match p.peek() {
|
||||
Some(Token::Space(n)) if n >= 1 => p.column(p.next_end()) >= column,
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a syntax tree.
|
||||
fn tree_while(p: &mut Parser, mut f: impl FnMut(&mut Parser) -> bool) -> Tree {
|
||||
// We keep track of whether we are at the start of a block or paragraph
|
||||
// to know whether things like headings are allowed.
|
||||
let mut at_start = true;
|
||||
fn tree_while(
|
||||
p: &mut Parser,
|
||||
mut at_start: bool,
|
||||
mut f: impl FnMut(&mut Parser) -> bool,
|
||||
) -> Tree {
|
||||
// We use `at_start` to keep track of whether we are at the start of a line
|
||||
// or template to know whether things like headings are allowed.
|
||||
let mut tree = vec![];
|
||||
while !p.eof() && f(p) {
|
||||
if let Some(node) = node(p, &mut at_start) {
|
||||
@ -85,19 +93,13 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
Token::Star => Node::Strong(span),
|
||||
Token::Underscore => Node::Emph(span),
|
||||
Token::Raw(t) => raw(p, t),
|
||||
Token::Hashtag => {
|
||||
if *at_start {
|
||||
return Some(heading(p));
|
||||
} else {
|
||||
Node::Text(p.peek_src().into())
|
||||
}
|
||||
}
|
||||
Token::Hyph => {
|
||||
if *at_start {
|
||||
return Some(list(p));
|
||||
} else {
|
||||
Node::Text(p.peek_src().into())
|
||||
}
|
||||
Token::Hashtag if *at_start => return Some(heading(p)),
|
||||
Token::Hyph if *at_start => return Some(list_item(p)),
|
||||
Token::Numbering(number) if *at_start => return Some(enum_item(p, number)),
|
||||
|
||||
// Line-based markup that is not currently at the start of the line.
|
||||
Token::Hashtag | Token::Hyph | Token::Numbering(_) => {
|
||||
Node::Text(p.peek_src().into())
|
||||
}
|
||||
|
||||
// Hashtag + keyword / identifier.
|
||||
@ -118,19 +120,12 @@ fn node(p: &mut Parser, at_start: &mut bool) -> Option<Node> {
|
||||
}
|
||||
p.end_group();
|
||||
|
||||
// Uneat spaces we might have eaten eagerly.
|
||||
return expr.map(Node::Expr);
|
||||
}
|
||||
|
||||
// Block.
|
||||
Token::LeftBrace => {
|
||||
return Some(Node::Expr(block(p, false)));
|
||||
}
|
||||
|
||||
// Template.
|
||||
Token::LeftBracket => {
|
||||
return Some(Node::Expr(template(p)));
|
||||
}
|
||||
// Block and template.
|
||||
Token::LeftBrace => return Some(Node::Expr(block(p, false))),
|
||||
Token::LeftBracket => return Some(Node::Expr(template(p))),
|
||||
|
||||
// Comments.
|
||||
Token::LineComment(_) | Token::BlockComment(_) => {
|
||||
@ -202,11 +197,19 @@ fn heading(p: &mut Parser) -> Node {
|
||||
}
|
||||
|
||||
/// Parse a single list item.
|
||||
fn list(p: &mut Parser) -> Node {
|
||||
fn list_item(p: &mut Parser) -> Node {
|
||||
let start = p.next_start();
|
||||
p.assert(Token::Hyph);
|
||||
let body = tree_indented(p);
|
||||
Node::List(ListNode { span: p.span(start), body })
|
||||
Node::List(ListItem { span: p.span(start), body })
|
||||
}
|
||||
|
||||
/// Parse a single enum item.
|
||||
fn enum_item(p: &mut Parser, number: Option<usize>) -> Node {
|
||||
let start = p.next_start();
|
||||
p.assert(Token::Numbering(number));
|
||||
let body = tree_indented(p);
|
||||
Node::Enum(EnumItem { span: p.span(start), number, body })
|
||||
}
|
||||
|
||||
/// Parse an expression.
|
||||
@ -500,7 +503,9 @@ fn block(p: &mut Parser, scoping: bool) -> Expr {
|
||||
}
|
||||
}
|
||||
p.end_group();
|
||||
p.skip_white();
|
||||
|
||||
// Forcefully skip over newlines since the group's contents can't.
|
||||
p.eat_while(|t| matches!(t, Token::Space(_)));
|
||||
}
|
||||
let span = p.end_group();
|
||||
Expr::Block(BlockExpr { span, exprs, scoping })
|
||||
|
@ -242,6 +242,16 @@ impl<'s> Parser<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume tokens while the condition is true.
|
||||
pub fn eat_while<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(Token<'s>) -> bool,
|
||||
{
|
||||
while self.peek().map_or(false, |t| f(t)) {
|
||||
self.eat();
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the next token if the closure maps it a to `Some`-variant.
|
||||
pub fn eat_map<T, F>(&mut self, f: F) -> Option<T>
|
||||
where
|
||||
@ -278,18 +288,6 @@ impl<'s> Parser<'s> {
|
||||
debug_assert_eq!(next, Some(t));
|
||||
}
|
||||
|
||||
/// Skip whitespace and comment tokens.
|
||||
pub fn skip_white(&mut self) {
|
||||
while matches!(
|
||||
self.peek(),
|
||||
Some(Token::Space(_)) |
|
||||
Some(Token::LineComment(_)) |
|
||||
Some(Token::BlockComment(_))
|
||||
) {
|
||||
self.eat();
|
||||
}
|
||||
}
|
||||
|
||||
/// The index at which the last token ended.
|
||||
///
|
||||
/// Refers to the end of the last _non-whitespace_ token in code mode.
|
||||
|
@ -102,6 +102,7 @@ impl<'s> Tokens<'s> {
|
||||
'`' => self.raw(),
|
||||
'$' => self.math(),
|
||||
'-' => self.hyph(start),
|
||||
c if c == '.' || c.is_ascii_digit() => self.numbering(start, c),
|
||||
|
||||
// Plain text.
|
||||
_ => self.text(start),
|
||||
@ -185,11 +186,11 @@ impl<'s> Tokens<'s> {
|
||||
// Whitespace.
|
||||
c if c.is_whitespace() => true,
|
||||
// Comments.
|
||||
'/' if self.s.check(|c| c == '/' || c == '*') => true,
|
||||
'/' => true,
|
||||
// Parentheses.
|
||||
'[' | ']' | '{' | '}' => true,
|
||||
// Markup.
|
||||
'#' | '~' | '*' | '_' | '-' | '`' | '$' => true,
|
||||
'#' | '~' | '*' | '_' | '`' | '$' | '-' => true,
|
||||
// Escaping.
|
||||
'\\' => true,
|
||||
// Just text.
|
||||
@ -274,6 +275,25 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
fn numbering(&mut self, start: usize, c: char) -> Token<'s> {
|
||||
let number = if c != '.' {
|
||||
self.s.eat_while(|c| c.is_ascii_digit());
|
||||
let read = self.s.eaten_from(start);
|
||||
if !self.s.eat_if('.') {
|
||||
return Token::Text(read);
|
||||
}
|
||||
read.parse().ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.s.check(|c| !c.is_whitespace()) {
|
||||
return Token::Text(self.s.eaten_from(start));
|
||||
}
|
||||
|
||||
Token::Numbering(number)
|
||||
}
|
||||
|
||||
fn raw(&mut self) -> Token<'s> {
|
||||
let mut backticks = 1;
|
||||
while self.s.eat_if('`') {
|
||||
@ -357,12 +377,12 @@ impl<'s> Tokens<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
fn number(&mut self, start: usize, first: char) -> Token<'s> {
|
||||
fn number(&mut self, start: usize, c: char) -> Token<'s> {
|
||||
// Read the first part (integer or fractional depending on `first`).
|
||||
self.s.eat_while(|c| c.is_ascii_digit());
|
||||
|
||||
// Read the fractional part if not already done and present.
|
||||
if first != '.' && self.s.eat_if('.') {
|
||||
// Read the fractional part if not already done.
|
||||
if c != '.' && self.s.eat_if('.') {
|
||||
self.s.eat_while(|c| c.is_ascii_digit());
|
||||
}
|
||||
|
||||
@ -654,7 +674,7 @@ mod tests {
|
||||
|
||||
// Test code symbols in text.
|
||||
t!(Markup[" /"]: "a():\"b" => Text("a():\"b"));
|
||||
t!(Markup[" /"]: ";:,|/+" => Text(";:,|/+"));
|
||||
t!(Markup[" /"]: ";:,|/+" => Text(";:,|"), Text("/+"));
|
||||
t!(Markup[" /"]: "#-a" => Text("#"), Text("-"), Text("a"));
|
||||
t!(Markup[" "]: "#123" => Text("#"), Text("123"));
|
||||
|
||||
@ -707,10 +727,14 @@ mod tests {
|
||||
t!(Markup: "_" => Underscore);
|
||||
t!(Markup[""]: "###" => Hashtag, Hashtag, Hashtag);
|
||||
t!(Markup["a1/"]: "# " => Hashtag, Space(0));
|
||||
t!(Markup["a1/"]: "- " => Hyph, Space(0));
|
||||
t!(Markup: "~" => Tilde);
|
||||
t!(Markup[" "]: r"\" => Backslash);
|
||||
t!(Markup["a "]: r"a--" => Text("a"), HyphHyph);
|
||||
t!(Markup["a1/"]: "- " => Hyph, Space(0));
|
||||
t!(Markup[" "]: "." => Numbering(None));
|
||||
t!(Markup[" "]: "1." => Numbering(Some(1)));
|
||||
t!(Markup[" "]: "1.a" => Text("1."), Text("a"));
|
||||
t!(Markup[" /"]: "a1." => Text("a1."));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -97,13 +97,14 @@ impl Pretty for Node {
|
||||
Self::Strong(_) => p.push('*'),
|
||||
Self::Emph(_) => p.push('_'),
|
||||
Self::Raw(raw) => raw.pretty(p),
|
||||
Self::Heading(heading) => heading.pretty(p),
|
||||
Self::List(list) => list.pretty(p),
|
||||
Self::Expr(expr) => {
|
||||
if expr.has_short_form() {
|
||||
Self::Heading(n) => n.pretty(p),
|
||||
Self::List(n) => n.pretty(p),
|
||||
Self::Enum(n) => n.pretty(p),
|
||||
Self::Expr(n) => {
|
||||
if n.has_short_form() {
|
||||
p.push('#');
|
||||
}
|
||||
expr.pretty(p);
|
||||
n.pretty(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,13 +176,23 @@ impl Pretty for HeadingNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for ListNode {
|
||||
impl Pretty for ListItem {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
p.push_str("- ");
|
||||
self.body.pretty(p);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for EnumItem {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
if let Some(number) = self.number {
|
||||
write!(p, "{}", number).unwrap();
|
||||
}
|
||||
p.push_str(". ");
|
||||
self.body.pretty(p);
|
||||
}
|
||||
}
|
||||
|
||||
impl Pretty for Expr {
|
||||
fn pretty(&self, p: &mut Printer) {
|
||||
match self {
|
||||
|
@ -21,8 +21,10 @@ pub enum Node {
|
||||
Raw(RawNode),
|
||||
/// A section heading: `= Introduction`.
|
||||
Heading(HeadingNode),
|
||||
/// A single list item: `- ...`.
|
||||
List(ListNode),
|
||||
/// An item in an unordered list: `- ...`.
|
||||
List(ListItem),
|
||||
/// An item in an enumeration (ordered list): `1. ...`.
|
||||
Enum(EnumItem),
|
||||
/// An expression.
|
||||
Expr(Expr),
|
||||
}
|
||||
@ -115,11 +117,22 @@ pub struct HeadingNode {
|
||||
pub body: Rc<Tree>,
|
||||
}
|
||||
|
||||
/// A single list item: `- ...`.
|
||||
/// An item in an unordered list: `- ...`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ListNode {
|
||||
pub struct ListItem {
|
||||
/// The source code location.
|
||||
pub span: Span,
|
||||
/// The contents of the list item.
|
||||
pub body: Tree,
|
||||
}
|
||||
|
||||
/// An item in an enumeration (ordered list): `1. ...`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EnumItem {
|
||||
/// The source code location.
|
||||
pub span: Span,
|
||||
/// The number, if any.
|
||||
pub number: Option<usize>,
|
||||
/// The contents of the list item.
|
||||
pub body: Tree,
|
||||
}
|
||||
|
@ -118,6 +118,10 @@ pub enum Token<'s> {
|
||||
/// One or two dollar signs followed by inner contents, terminated with the
|
||||
/// same number of dollar signs.
|
||||
Math(MathToken<'s>),
|
||||
/// A numbering: `23.`.
|
||||
///
|
||||
/// Can also exist without the number: `.`.
|
||||
Numbering(Option<usize>),
|
||||
/// An identifier: `center`.
|
||||
Ident(&'s str),
|
||||
/// A boolean: `true`, `false`.
|
||||
@ -256,6 +260,7 @@ impl<'s> Token<'s> {
|
||||
Self::UnicodeEscape(_) => "unicode escape sequence",
|
||||
Self::Raw(_) => "raw block",
|
||||
Self::Math(_) => "math formula",
|
||||
Self::Numbering(_) => "numbering",
|
||||
Self::Ident(_) => "identifier",
|
||||
Self::Bool(_) => "boolean",
|
||||
Self::Int(_) => "integer",
|
||||
|
@ -59,6 +59,7 @@ visit! {
|
||||
Node::Raw(_) => {}
|
||||
Node::Heading(n) => v.visit_heading(n),
|
||||
Node::List(n) => v.visit_list(n),
|
||||
Node::Enum(n) => v.visit_enum(n),
|
||||
Node::Expr(n) => v.visit_expr(n),
|
||||
}
|
||||
}
|
||||
@ -67,7 +68,11 @@ visit! {
|
||||
v.visit_tree(&node.body);
|
||||
}
|
||||
|
||||
fn visit_list(v, node: &ListNode) {
|
||||
fn visit_list(v, node: &ListItem) {
|
||||
v.visit_tree(&node.body);
|
||||
}
|
||||
|
||||
fn visit_enum(v, node: &EnumItem) {
|
||||
v.visit_tree(&node.body);
|
||||
}
|
||||
|
||||
|
BIN
tests/ref/markup/enums.png
Normal file
BIN
tests/ref/markup/enums.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
11
tests/typ/markup/enums.typ
Normal file
11
tests/typ/markup/enums.typ
Normal file
@ -0,0 +1,11 @@
|
||||
// Test enums.
|
||||
|
||||
---
|
||||
1. Embrace
|
||||
2. Extend
|
||||
3. Extinguish
|
||||
|
||||
---
|
||||
1. First.
|
||||
2. Second.
|
||||
1. Back to first.
|
Loading…
x
Reference in New Issue
Block a user