Skip to content

Commit

Permalink
Implement Display for Expr and OptimizedExpr (#896)
Browse files Browse the repository at this point in the history
* Merge needed codes from #895

Not related changes included in previous pull request #895.

* Add several tests about display

To cover more paths in Seq and Choice. Thanks for @tomtau to point it out in #896.
  • Loading branch information
TheVeryDarkness committed Jul 20, 2023
1 parent ac0aed3 commit 02cffe6
Show file tree
Hide file tree
Showing 2 changed files with 732 additions and 13 deletions.
328 changes: 327 additions & 1 deletion meta/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,79 @@ impl Expr {
}
}

impl core::fmt::Display for Expr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Expr::Str(s) => write!(f, "{:?}", s),
Expr::Insens(s) => write!(f, "^{:?}", s),
Expr::Range(start, end) => {
let start = start.chars().next().expect("Empty range start.");
let end = end.chars().next().expect("Empty range end.");
write!(f, "({:?}..{:?})", start, end)
}
Expr::Ident(id) => write!(f, "{}", id),
Expr::PeekSlice(start, end) => match end {
Some(end) => write!(f, "PEEK[{}..{}]", start, end),
None => write!(f, "PEEK[{}..]", start),
},
Expr::PosPred(expr) => write!(f, "&{}", expr.as_ref()),
Expr::NegPred(expr) => write!(f, "!{}", expr.as_ref()),
Expr::Seq(lhs, rhs) => {
let mut nodes = Vec::new();
nodes.push(lhs);
let mut current = rhs;
while let Expr::Seq(lhs, rhs) = current.as_ref() {
nodes.push(lhs);
current = rhs;
}
nodes.push(current);
let sequence = nodes
.iter()
.map(|node| format!("{}", node))
.collect::<Vec<_>>()
.join(" ~ ");
write!(f, "({})", sequence)
}
Expr::Choice(lhs, rhs) => {
let mut nodes = Vec::new();
nodes.push(lhs);
let mut current = rhs;
while let Expr::Choice(lhs, rhs) = current.as_ref() {
nodes.push(lhs);
current = rhs;
}
nodes.push(current);
let sequence = nodes
.iter()
.map(|node| format!("{}", node))
.collect::<Vec<_>>()
.join(" | ");
write!(f, "({})", sequence)
}
Expr::Opt(expr) => write!(f, "{}?", expr),
Expr::Rep(expr) => write!(f, "{}*", expr),
Expr::RepOnce(expr) => write!(f, "{}+", expr),
Expr::RepExact(expr, n) => write!(f, "{}{{{}}}", expr, n),
Expr::RepMin(expr, min) => write!(f, "{}{{{},}}", expr, min),
Expr::RepMax(expr, max) => write!(f, "{}{{,{}}}", expr, max),
Expr::RepMinMax(expr, min, max) => write!(f, "{}{{{}, {}}}", expr, min, max),
Expr::Skip(strings) => {
let strings = strings
.iter()
.map(|s| format!("{:?}", s))
.collect::<Vec<_>>()
.join(" | ");
write!(f, "(!({}) ~ ANY)*", strings)
}
Expr::Push(expr) => write!(f, "PUSH({})", expr),
#[cfg(feature = "grammar-extras")]
Expr::NodeTag(expr, tag) => {
write!(f, "(#{} = {})", tag, expr)
}
}
}
}

/// The top down iterator for an expression.
pub struct ExprTopDownIterator {
current: Option<Expr>,
Expand Down Expand Up @@ -362,7 +435,260 @@ mod tests {
expr.clone()
.map_bottom_up(|expr| expr)
.map_top_down(|expr| expr),
expr
expr,
);
}

mod display {
use super::super::*;

#[test]
fn string() {
assert_eq!(Expr::Str("a".to_owned()).to_string(), r#""a""#);
}

#[test]
fn insens() {
assert_eq!(Expr::Insens("a".to_owned()).to_string(), r#"^"a""#);
}

#[test]
fn range() {
assert_eq!(
Expr::Range("a".to_owned(), "z".to_owned()).to_string(),
r#"('a'..'z')"#,
);
}

#[test]
fn ident() {
assert_eq!(Expr::Ident("a".to_owned()).to_string(), "a");
}

#[test]
fn peek_slice() {
assert_eq!(Expr::PeekSlice(0, None).to_string(), "PEEK[0..]");
assert_eq!(Expr::PeekSlice(0, Some(-1)).to_string(), "PEEK[0..-1]");
}

#[test]
fn pos_pred() {
assert_eq!(
Expr::PosPred(Box::new(Expr::Ident("e".to_owned()))).to_string(),
"&e",
);
}

#[test]
fn neg_pred() {
assert_eq!(
Expr::NegPred(Box::new(Expr::Ident("e".to_owned()))).to_string(),
"!e",
);
}

#[test]
fn seq() {
assert_eq!(
Expr::Seq(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Ident("e2".to_owned())),
)
.to_string(),
"(e1 ~ e2)",
);
assert_eq!(
Expr::Seq(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Seq(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Ident("e3".to_owned())),
)),
)
.to_string(),
"(e1 ~ e2 ~ e3)",
);
assert_eq!(
Expr::Seq(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Seq(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Seq(
Box::new(Expr::Ident("e3".to_owned())),
Box::new(Expr::Ident("e4".to_owned())),
)),
)),
)
.to_string(),
"(e1 ~ e2 ~ e3 ~ e4)",
);
assert_eq!(
Expr::Seq(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Choice(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Seq(
Box::new(Expr::Ident("e3".to_owned())),
Box::new(Expr::Ident("e4".to_owned())),
)),
)),
)
.to_string(),
"(e1 ~ (e2 | (e3 ~ e4)))",
);
assert_eq!(
Expr::Seq(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Seq(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Choice(
Box::new(Expr::Ident("e3".to_owned())),
Box::new(Expr::Ident("e4".to_owned())),
)),
)),
)
.to_string(),
"(e1 ~ e2 ~ (e3 | e4))",
);
}

#[test]
fn choice() {
assert_eq!(
Expr::Choice(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Ident("e2".to_owned())),
)
.to_string(),
"(e1 | e2)",
);
assert_eq!(
Expr::Choice(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Choice(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Ident("e3".to_owned())),
)),
)
.to_string(),
"(e1 | e2 | e3)",
);
assert_eq!(
Expr::Choice(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Choice(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Choice(
Box::new(Expr::Ident("e3".to_owned())),
Box::new(Expr::Ident("e4".to_owned())),
)),
)),
)
.to_string(),
"(e1 | e2 | e3 | e4)",
);
assert_eq!(
Expr::Choice(
Box::new(Expr::Ident("e1".to_owned())),
Box::new(Expr::Seq(
Box::new(Expr::Ident("e2".to_owned())),
Box::new(Expr::Choice(
Box::new(Expr::Ident("e3".to_owned())),
Box::new(Expr::Ident("e4".to_owned())),
)),
)),
)
.to_string(),
"(e1 | (e2 ~ (e3 | e4)))",
);
}

#[test]
fn opt() {
assert_eq!(
Expr::Opt(Box::new(Expr::Ident("e".to_owned()))).to_string(),
"e?",
);
}

#[test]
fn rep() {
assert_eq!(
Expr::Rep(Box::new(Expr::Ident("e".to_owned()))).to_string(),
"e*",
);
}

#[test]
fn rep_once() {
assert_eq!(
Expr::RepOnce(Box::new(Expr::Ident("e".to_owned()))).to_string(),
"e+",
);
}

#[test]
fn rep_exact() {
assert_eq!(
Expr::RepExact(Box::new(Expr::Ident("e".to_owned())), 1).to_string(),
"e{1}",
);
}

#[test]
fn rep_min() {
assert_eq!(
Expr::RepMin(Box::new(Expr::Ident("e".to_owned())), 1).to_string(),
"e{1,}",
);
}

#[test]
fn rep_max() {
assert_eq!(
Expr::RepMax(Box::new(Expr::Ident("e".to_owned())), 1).to_string(),
"e{,1}",
);
}

#[test]
fn rep_min_max() {
assert_eq!(
Expr::RepMinMax(Box::new(Expr::Ident("e".to_owned())), 1, 2).to_string(),
"e{1, 2}",
);
}

#[test]
fn skip() {
assert_eq!(
Expr::Skip(
["a", "bc"]
.into_iter()
.map(|s| s.to_owned())
.collect::<Vec<_>>(),
)
.to_string(),
r#"(!("a" | "bc") ~ ANY)*"#,
);
}

#[test]
fn push() {
assert_eq!(
Expr::Push(Box::new(Expr::Ident("e".to_owned()))).to_string(),
"PUSH(e)",
);
}

#[test]
#[cfg(feature = "grammar-extras")]
fn node_tag() {
assert_eq!(
Expr::NodeTag(Box::new(Expr::Ident("expr".to_owned())), "label".to_owned())
.to_string(),
"(#label = expr)",
);
}
}
}

0 comments on commit 02cffe6

Please sign in to comment.