Basic enums

This commit is contained in:
Laurenz 2021-06-11 14:00:06 +02:00
parent 3330767c20
commit 4dbd9285c9
10 changed files with 173 additions and 90 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,11 @@
// Test enums.
---
1. Embrace
2. Extend
3. Extinguish
---
1. First.
2. Second.
1. Back to first.