summaryrefslogtreecommitdiff
path: root/src/syntax
diff options
context:
space:
mode:
Diffstat (limited to 'src/syntax')
-rw-r--r--src/syntax/expr.rs223
-rw-r--r--src/syntax/mod.rs30
-rw-r--r--src/syntax/node.rs100
3 files changed, 351 insertions, 2 deletions
diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs
index cf6611f9..4916f34f 100644
--- a/src/syntax/expr.rs
+++ b/src/syntax/expr.rs
@@ -21,6 +21,35 @@ pub enum Expr {
Content(ExprContent),
}
+impl Pretty for Expr {
+ fn pretty(&self, p: &mut Printer) {
+ match self {
+ Self::Lit(lit) => lit.pretty(p),
+ Self::Call(call) => call.pretty(p),
+ Self::Unary(unary) => unary.pretty(p),
+ Self::Binary(binary) => binary.pretty(p),
+ Self::Array(array) => array.pretty(p),
+ Self::Dict(dict) => dict.pretty(p),
+ Self::Content(content) => pretty_content_expr(content, p),
+ }
+ }
+}
+
+/// Pretty print content in an expression context.
+pub fn pretty_content_expr(tree: &Tree, p: &mut Printer) {
+ if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = tree.as_slice() {
+ // Remove unncessary braces from content expression containing just a
+ // single function call.
+ //
+ // Example: Transforms "{(call: {[f]})}" => "{(call: [f])}"
+ pretty_bracket_call(call, p, false);
+ } else {
+ p.push_str("{");
+ tree.pretty(p);
+ p.push_str("}");
+ }
+}
+
/// An invocation of a function: `[foo ...]`, `foo(...)`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprCall {
@@ -30,12 +59,68 @@ pub struct ExprCall {
pub args: Spanned<ExprArgs>,
}
+impl Pretty for ExprCall {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(&self.name.v);
+ p.push_str("(");
+ self.args.v.pretty(p);
+ p.push_str(")");
+ }
+}
+
+/// Pretty print a bracketed function call, with body or chaining when possible.
+pub fn pretty_bracket_call(call: &ExprCall, p: &mut Printer, chained: bool) {
+ if chained {
+ p.push_str(" | ");
+ } else {
+ p.push_str("[");
+ }
+
+ // Function name.
+ p.push_str(&call.name.v);
+
+ // Find out whether this can be written as body or chain.
+ //
+ // Example: Transforms "[v {Hi}]" => "[v][Hi]".
+ if let [head @ .., Argument::Pos(Spanned { v: Expr::Content(content), .. })] =
+ call.args.v.as_slice()
+ {
+ // Previous arguments.
+ if !head.is_empty() {
+ p.push_str(" ");
+ p.join(head, ", ", |item, p| item.pretty(p));
+ }
+
+ // Find out whether this can written as a chain.
+ //
+ // Example: Transforms "[v][[f]]" => "[v | f]".
+ if let [Spanned { v: Node::Expr(Expr::Call(call)), .. }] = content.as_slice() {
+ return pretty_bracket_call(call, p, true);
+ } else {
+ p.push_str("][");
+ content.pretty(p);
+ }
+ } else if !call.args.v.is_empty() {
+ p.push_str(" ");
+ call.args.v.pretty(p);
+ }
+
+ // Either end of header or end of body.
+ p.push_str("]");
+}
+
/// The arguments to a function: `12, draw: false`.
///
/// In case of a bracketed invocation with a body, the body is _not_
/// included in the span for the sake of clearer error messages.
pub type ExprArgs = Vec<Argument>;
+impl Pretty for Vec<Argument> {
+ fn pretty(&self, p: &mut Printer) {
+ p.join(self, ", ", |item, p| item.pretty(p));
+ }
+}
+
/// An argument to a function call: `12` or `draw: false`.
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
@@ -45,6 +130,15 @@ pub enum Argument {
Named(Named),
}
+impl Pretty for Argument {
+ fn pretty(&self, p: &mut Printer) {
+ match self {
+ Self::Pos(expr) => expr.v.pretty(p),
+ Self::Named(named) => named.pretty(p),
+ }
+ }
+}
+
/// A pair of a name and an expression: `pattern: dashed`.
#[derive(Debug, Clone, PartialEq)]
pub struct Named {
@@ -54,6 +148,14 @@ pub struct Named {
pub expr: Spanned<Expr>,
}
+impl Pretty for Named {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(&self.name.v);
+ p.push_str(": ");
+ self.expr.v.pretty(p);
+ }
+}
+
/// A unary operation: `-x`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprUnary {
@@ -63,6 +165,13 @@ pub struct ExprUnary {
pub expr: Box<Spanned<Expr>>,
}
+impl Pretty for ExprUnary {
+ fn pretty(&self, p: &mut Printer) {
+ self.op.v.pretty(p);
+ self.expr.v.pretty(p);
+ }
+}
+
/// A unary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum UnOp {
@@ -70,6 +179,14 @@ pub enum UnOp {
Neg,
}
+impl Pretty for UnOp {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(match self {
+ Self::Neg => "-",
+ });
+ }
+}
+
/// A binary operation: `a + b`, `a / b`.
#[derive(Debug, Clone, PartialEq)]
pub struct ExprBinary {
@@ -81,6 +198,16 @@ pub struct ExprBinary {
pub rhs: Box<Spanned<Expr>>,
}
+impl Pretty for ExprBinary {
+ fn pretty(&self, p: &mut Printer) {
+ self.lhs.v.pretty(p);
+ p.push_str(" ");
+ self.op.v.pretty(p);
+ p.push_str(" ");
+ self.rhs.v.pretty(p);
+ }
+}
+
/// A binary operator.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BinOp {
@@ -94,12 +221,46 @@ pub enum BinOp {
Div,
}
+impl Pretty for BinOp {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str(match self {
+ Self::Add => "+",
+ Self::Sub => "-",
+ Self::Mul => "*",
+ Self::Div => "/",
+ });
+ }
+}
+
/// An array expression: `(1, "hi", 12cm)`.
pub type ExprArray = SpanVec<Expr>;
+impl Pretty for ExprArray {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str("(");
+ p.join(self, ", ", |item, p| item.v.pretty(p));
+ if self.len() == 1 {
+ p.push_str(",");
+ }
+ p.push_str(")");
+ }
+}
+
/// A dictionary expression: `(color: #f79143, pattern: dashed)`.
pub type ExprDict = Vec<Named>;
+impl Pretty for ExprDict {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str("(");
+ if self.is_empty() {
+ p.push_str(":");
+ } else {
+ p.join(self, ", ", |named, p| named.pretty(p));
+ }
+ p.push_str(")");
+ }
+}
+
/// A content expression: `{*Hello* there!}`.
pub type ExprContent = Tree;
@@ -128,3 +289,65 @@ pub enum Lit {
/// A string literal: `"hello!"`.
Str(String),
}
+
+impl Pretty for Lit {
+ fn pretty(&self, p: &mut Printer) {
+ match self {
+ Self::Ident(v) => p.push_str(&v),
+ Self::None => p.push_str("none"),
+ Self::Bool(v) => write!(p, "{}", v).unwrap(),
+ Self::Int(v) => write!(p, "{}", v).unwrap(),
+ Self::Float(v) => write!(p, "{}", v).unwrap(),
+ Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(),
+ Self::Percent(v) => write!(p, "{}%", v).unwrap(),
+ Self::Color(v) => write!(p, "{}", v).unwrap(),
+ Self::Str(s) => write!(p, "{:?}", &s).unwrap(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::tests::test_pretty;
+
+ #[test]
+ fn test_pretty_print_chaining() {
+ // All equivalent.
+ test_pretty("[v [f]]", "[v | f]");
+ test_pretty("[v {[f]}]", "[v | f]");
+ test_pretty("[v][[f]]", "[v | f]");
+ test_pretty("[v | f]", "[v | f]");
+ }
+
+ #[test]
+ fn test_pretty_print_expressions() {
+ // Unary and binary operations.
+ test_pretty("{1 +}", "{1}");
+ test_pretty("{1 + func(-2)}", "{1 + func(-2)}");
+
+ // Array.
+ test_pretty("(-5,)", "(-5,)");
+ test_pretty("(1, 2, 3)", "(1, 2, 3)");
+
+ // Dictionary.
+ test_pretty("{(:)}", "{(:)}");
+ test_pretty("{(percent: 5%)}", "{(percent: 5%)}");
+
+ // Content expression without unncessary braces.
+ test_pretty("[v [f], 1]", "[v [f], 1]");
+ test_pretty("(func: {[f]})", "(func: [f])");
+ }
+
+ #[test]
+ fn test_pretty_print_literals() {
+ test_pretty("{none}", "{none}");
+ test_pretty("{true}", "{true}");
+ test_pretty("{25}", "{25}");
+ test_pretty("{2.50}", "{2.5}");
+ test_pretty("{1e2}", "{100}");
+ test_pretty("{12pt}", "{12pt}");
+ test_pretty("{50%}", "{50%}");
+ test_pretty("{#fff}", "{#ffffff}");
+ test_pretty(r#"{"hi\n"}"#, r#"{"hi\n"}"#);
+ }
+}
diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs
index 9c78fbc3..22f51d82 100644
--- a/src/syntax/mod.rs
+++ b/src/syntax/mod.rs
@@ -12,5 +12,33 @@ pub use node::*;
pub use span::*;
pub use token::*;
-/// A collection of nodes which form a tree together with their children.
+use crate::pretty::{Pretty, Printer};
+
+/// The abstract syntax tree.
pub type Tree = SpanVec<Node>;
+
+impl Pretty for Tree {
+ fn pretty(&self, p: &mut Printer) {
+ for node in self {
+ node.v.pretty(p);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::parse::parse;
+ use crate::pretty::pretty;
+
+ #[track_caller]
+ pub fn test_pretty(src: &str, exp: &str) {
+ let tree = parse(src).output;
+ let found = pretty(&tree);
+ if exp != found {
+ println!("tree: {:#?}", tree);
+ println!("expected: {}", exp);
+ println!("found: {}", found);
+ panic!("test failed");
+ }
+ }
+}
diff --git a/src/syntax/node.rs b/src/syntax/node.rs
index d64d4fed..91fa72d7 100644
--- a/src/syntax/node.rs
+++ b/src/syntax/node.rs
@@ -27,15 +27,62 @@ pub enum Node {
Expr(Expr),
}
+impl Pretty for Node {
+ fn pretty(&self, p: &mut Printer) {
+ match self {
+ Self::Text(text) => p.push_str(&text),
+ Self::Space => p.push_str(" "),
+ Self::Linebreak => p.push_str(r"\"),
+ Self::Parbreak => p.push_str("\n\n"),
+ Self::Strong => p.push_str("*"),
+ Self::Emph => p.push_str("_"),
+ Self::Heading(heading) => heading.pretty(p),
+ Self::Raw(raw) => raw.pretty(p),
+ Self::Expr(expr) => pretty_expr_node(expr, p),
+ }
+ }
+}
+
+/// Pretty print an expression in a node context.
+pub fn pretty_expr_node(expr: &Expr, p: &mut Printer) {
+ match expr {
+ // Prefer bracket calls over expression blocks with just a single paren
+ // call.
+ //
+ // Example: Transforms "{v()}" => "[v]".
+ Expr::Call(call) => pretty_bracket_call(call, p, false),
+
+ // Remove unncessary nesting of content and expression blocks.
+ //
+ // Example: Transforms "{{Hi}}" => "Hi".
+ Expr::Content(content) => content.pretty(p),
+
+ _ => {
+ p.push_str("{");
+ expr.pretty(p);
+ p.push_str("}");
+ }
+ }
+}
+
/// A section heading: `# Introduction`.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHeading {
- /// The section depth (numer of hashtags minus 1).
+ /// The section depth (numer of hashtags minus 1, capped at 5).
pub level: Spanned<u8>,
/// The contents of the heading.
pub contents: Tree,
}
+impl Pretty for NodeHeading {
+ fn pretty(&self, p: &mut Printer) {
+ for _ in 0 ..= self.level.v {
+ p.push_str("#");
+ }
+ self.contents.pretty(p);
+ }
+}
+
/// A raw block with optional syntax highlighting: `` `raw` ``.
///
/// Raw blocks start with an arbitrary number of backticks and end with the same
@@ -114,3 +161,54 @@ pub struct NodeRaw {
/// are inline-level when they contain no newlines.
pub inline: bool,
}
+
+impl Pretty for NodeRaw {
+ fn pretty(&self, p: &mut Printer) {
+ p.push_str("`");
+ if let Some(lang) = &self.lang {
+ p.push_str(&lang);
+ p.push_str(" ");
+ }
+ // TODO: Technically, we should handle backticks in the lines
+ // by wrapping with more backticks and possibly adding space
+ // before the first or after the last line.
+ p.join(&self.lines, "\n", |line, p| p.push_str(line));
+ p.push_str("`");
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::tests::test_pretty;
+
+ #[test]
+ fn test_pretty_print_removes_nesting() {
+ // Even levels of nesting do not matter.
+ test_pretty("{{Hi}}", "Hi");
+ test_pretty("{{{{Hi}}}}", "Hi");
+ }
+
+ #[test]
+ fn test_pretty_print_prefers_bracket_calls() {
+ // All reduces to a simple bracket call.
+ test_pretty("{v()}", "[v]");
+ test_pretty("[v]", "[v]");
+ test_pretty("{[v]}", "[v]");
+ test_pretty("{{[v]}}", "[v]");
+ }
+
+ #[test]
+ fn test_pretty_print_nodes() {
+ // Basic text and markup.
+ test_pretty(r"*Hi_\", r"*Hi_\");
+
+ // Whitespace.
+ test_pretty(" ", " ");
+ test_pretty("\n\n\n", "\n\n");
+
+ // Heading and raw.
+ test_pretty("# Ok", "# Ok");
+ test_pretty("``\none\ntwo\n``", "`one\ntwo`");
+ test_pretty("`lang one\ntwo`", "`lang one\ntwo`");
+ }
+}