feat(parse): add an option to limit tree depth #535

Merged
ada4a merged 1 commit from ada4a/mergiraf:parse-max-depth into main 2025-07-26 14:57:04 +02:00

View file

@ -33,6 +33,9 @@ enum Command {
Parse {
/// Path to the file to parse. Its type will be guessed from its extension.
path: PathBuf,
/// Limit the depth of the tree
#[arg(short, long)]
max_depth: Option<usize>,
},
/// Compare two files, returning exit code 0 if their trees are isomorphic, and 1 otherwise
Compare {
@ -75,7 +78,7 @@ fn real_main(args: &CliArgs) -> Result<i32, String> {
};
let exit_code = match &args.command {
Command::Parse { path } => {
Command::Parse { path, max_depth } => {
let lang_profile = lang_profile(path)?;
let contents = contents(path)?;
@ -83,7 +86,7 @@ fn real_main(args: &CliArgs) -> Result<i32, String> {
let tree = AstNode::parse(&contents, lang_profile, &arena, &ref_arena)
.map_err(|err| format!("File has parse errors: {err}"))?;
print!("{}", tree.ascii_tree());
print!("{}", tree.ascii_tree(*max_depth));
0
}
Command::Compare {

View file

@ -822,17 +822,30 @@ impl<'a> AstNode<'a> {
}
}
/// Represents the node and its sub-structure in ASCII art
pub fn ascii_tree(&'a self) -> String {
self.internal_ascii_tree(&Color::DarkGray.prefix().to_string(), true, None)
/// Represents the node and its sub-structure in ASCII art, optionally printing only the nodes
/// up to a given depth
pub fn ascii_tree(&'a self, max_depth: Option<usize>) -> String {
self.internal_ascii_tree(
0,
max_depth,
&Color::DarkGray.prefix().to_string(),
true,
None,
)
}
fn internal_ascii_tree(
&'a self,
depth: usize,
max_depth: Option<usize>,
prefix: &str,
last_child: bool,
parent: Option<&CommutativeParent>,
) -> String {
if max_depth == Some(depth) {
return String::new();
}
let num_children = self.children.len();
let next_parent = self.commutative_parent_definition();
@ -883,7 +896,13 @@ impl<'a> AstNode<'a> {
.filter(|(_, child)| child.grammar_name != "@virtual_line@")
.map(|(index, child)| {
let new_prefix = format!("{prefix}{} ", if last_child { " " } else { "" });
child.internal_ascii_tree(&new_prefix, index == num_children - 1, next_parent)
child.internal_ascii_tree(
depth + 1,
max_depth,
&new_prefix,
index == num_children - 1,
next_parent,
)
}),
)
.collect()
@ -1513,8 +1532,6 @@ mod tests {
let ctx = ctx();
let tree = ctx.parse("a.json", "{\"foo\": 3, \"bar\": 4}");
let ascii_tree = tree.ascii_tree();
let expected = "\
\u{1b}[90m\u{1b}[0mdocument
\u{1b}[90m \u{1b}[0mobject\u{1b}[95m Commutative\u{1b}[0m
@ -1537,7 +1554,34 @@ mod tests {
\u{1b}[90m \u{1b}[0m\u{1b}[31m}\u{1b}[0m
";
assert_eq!(ascii_tree, expected);
assert_eq!(tree.ascii_tree(None), expected);
assert_eq!(tree.ascii_tree(Some(5)), expected);
let expected = "\
\u{1b}[90m\u{1b}[0mdocument
\u{1b}[90m \u{1b}[0mobject\u{1b}[95m Commutative\u{1b}[0m
\u{1b}[90m \u{1b}[0m\u{1b}[31m{\u{1b}[0m
\u{1b}[90m \u{1b}[0mpair \u{1b}[96mSignature [[\"foo\"]]\u{1b}[0m
\u{1b}[90m key: \u{1b}[0mstring
\u{1b}[90m \u{1b}[0m\u{1b}[31m:\u{1b}[0m
\u{1b}[90m value: \u{1b}[0mnumber \u{1b}[31m3\u{1b}[0m
\u{1b}[90m \u{1b}[0m\u{1b}[31m,\u{1b}[0m
\u{1b}[90m \u{1b}[0mpair \u{1b}[96mSignature [[\"bar\"]]\u{1b}[0m
\u{1b}[90m key: \u{1b}[0mstring
\u{1b}[90m \u{1b}[0m\u{1b}[31m:\u{1b}[0m
\u{1b}[90m value: \u{1b}[0mnumber \u{1b}[31m4\u{1b}[0m
\u{1b}[90m \u{1b}[0m\u{1b}[31m}\u{1b}[0m
";
assert_eq!(tree.ascii_tree(Some(4)), expected);
let expected = "\
\u{1b}[90m\u{1b}[0mdocument
";
assert_eq!(tree.ascii_tree(Some(1)), expected);
assert_eq!(tree.ascii_tree(Some(0)), "");
}
#[test]