summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/exec/mod.rs63
-rw-r--r--src/parse/mod.rs71
-rw-r--r--src/parse/parser.rs22
-rw-r--r--src/parse/tokens.rs38
-rw-r--r--src/pretty.rs23
-rw-r--r--src/syntax/node.rs21
-rw-r--r--src/syntax/token.rs5
-rw-r--r--src/syntax/visit.rs7
8 files changed, 161 insertions, 89 deletions
diff --git a/src/exec/mod.rs b/src/exec/mod.rs
index 639c1a87..8b088f85 100644
--- a/src/exec/mod.rs
+++ b/src/exec/mod.rs
@@ -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(),
- });
+ exec_item(ctx, "•".to_string(), &self.body, map);
+ }
+}
- ctx.parbreak();
+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 {
diff --git a/src/parse/mod.rs b/src/parse/mod.rs
index 5ab5b2d8..41257668 100644
--- a/src/parse/mod.rs
+++ b/src/parse/mod.rs
@@ -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 })
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 27346587..8ea80d68 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -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.
diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs
index f3ca25d9..a496010e 100644
--- a/src/parse/tokens.rs
+++ b/src/parse/tokens.rs
@@ -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]
diff --git a/src/pretty.rs b/src/pretty.rs
index 1281e27b..e2942f6f 100644
--- a/src/pretty.rs
+++ b/src/pretty.rs
@@ -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 {
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index b4684d0b..a97430b6 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -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,
+}
diff --git a/src/syntax/token.rs b/src/syntax/token.rs
index 2263f806..254a56a2 100644
--- a/src/syntax/token.rs
+++ b/src/syntax/token.rs
@@ -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",
diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs
index 97e8d4ed..a1a848ef 100644
--- a/src/syntax/visit.rs
+++ b/src/syntax/visit.rs
@@ -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);
}