-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add [GreenTreeBuilder::revert
] to support backtracking parsers
#67
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use super::*; | ||
use cstree::interning::Resolver; | ||
|
||
type GreenNodeBuilder<'cache, 'interner> = cstree::build::GreenNodeBuilder<'cache, 'interner, SyntaxKind>; | ||
|
||
fn with_builder(f: impl FnOnce(&mut GreenNodeBuilder)) -> (SyntaxNode, impl Resolver) { | ||
let mut builder = GreenNodeBuilder::new(); | ||
f(&mut builder); | ||
let (node, cache) = builder.finish(); | ||
(SyntaxNode::new_root(node), cache.unwrap().into_interner().unwrap()) | ||
} | ||
|
||
#[test] | ||
#[should_panic = "`left == right` failed"] | ||
fn comparison_works() { | ||
let (first, res1) = with_builder(|_| {}); | ||
let (second, res2) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
builder.token(SyntaxKind(1), "hi"); | ||
builder.finish_node(); | ||
}); | ||
assert_tree_eq((&first, &res1), (&second, &res2)); | ||
} | ||
|
||
#[test] | ||
fn simple() { | ||
let (first, res1) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
builder.finish_node(); | ||
}); | ||
let (second, res2) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
|
||
// Add a token, then remove it. | ||
let initial = builder.checkpoint(); | ||
builder.token(SyntaxKind(1), "hi"); | ||
builder.revert(initial); | ||
|
||
builder.finish_node(); | ||
}); | ||
assert_tree_eq((&first, &res1), (&second, &res2)); | ||
} | ||
|
||
#[test] | ||
fn nested() { | ||
let (first, res1) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
builder.finish_node(); | ||
}); | ||
|
||
let (second, res2) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
// Add two tokens, then remove both. | ||
let initial = builder.checkpoint(); | ||
builder.token(SyntaxKind(1), "hi"); | ||
builder.token(SyntaxKind(2), "hello"); | ||
builder.revert(initial); | ||
|
||
builder.finish_node(); | ||
}); | ||
|
||
let (third, res3) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
builder.finish_node(); | ||
Comment on lines
+63
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, I assume this was intended to contain the actual nested case? Since the first tree is the control and the second only has one checkpoint. I added some code in the other branch, but you might want to double-check if that does what you wanted to test here given that from the name it looks like this should test the case of creating multiple checkpoints back to back. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fascinating, apparently i forgot an entire test here lol. good catch ty, i meant to write this probably builder.start_node(SyntaxKind(0));
// Add two tokens, then remove both.
let initial = builder.checkpoint();
builder.token(SyntaxKind(1), "hi");
builder.start_node(SyntaxKind(3));
builder.token(SyntaxKind(2), "hello");
builder.finish_node();
builder.revert(initial);
builder.finish_node(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, so not nesting checkpoints but nesting nodes? Then this would have caught the checkpoint not working across node boundaries, but I think testing using two checkpoints (in the correct order, not like the |
||
}); | ||
|
||
assert_tree_eq((&first, &res1), (&second, &res2)); | ||
assert_tree_eq((&first, &res1), (&third, &res3)); | ||
} | ||
|
||
#[test] | ||
#[should_panic = "checkpoint in the future"] | ||
fn misuse() { | ||
with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
|
||
// Add two tokens, but remove them in the wrong order. | ||
let initial = builder.checkpoint(); | ||
builder.token(SyntaxKind(1), "hi"); | ||
let new = builder.checkpoint(); | ||
builder.token(SyntaxKind(2), "hello"); | ||
builder.revert(initial); | ||
builder.revert(new); | ||
|
||
builder.finish_node(); | ||
}); | ||
} | ||
|
||
#[test] | ||
fn misuse2() { | ||
let (first, res1) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
builder.token(SyntaxKind(3), "no"); | ||
builder.finish_node(); | ||
}); | ||
|
||
let (second, res2) = with_builder(|builder| { | ||
builder.start_node(SyntaxKind(0)); | ||
|
||
// Add two tokens, revert to the initial state, add three tokens, and try to revert to an earlier checkpoint. | ||
let initial = builder.checkpoint(); | ||
builder.token(SyntaxKind(1), "hi"); | ||
let new = builder.checkpoint(); | ||
builder.token(SyntaxKind(2), "hello"); | ||
builder.revert(initial); | ||
|
||
// This is wrong, but there's not a whole lot the library can do about it. | ||
builder.token(SyntaxKind(3), "no"); | ||
builder.token(SyntaxKind(4), "bad"); | ||
builder.token(SyntaxKind(4), "wrong"); | ||
builder.revert(new); | ||
|
||
builder.finish_node(); | ||
}); | ||
|
||
assert_tree_eq((&first, &res1), (&second, &res2)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't quite get what you mean to say here about "pairing checkpoint/start_node_at". What exactly is this about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
say you have the following call pattern:
one might expect, in an ideal world, that this is a noop. but in fact it leaves you with an unpaired
start_node()
that has no tokens.i don't remember why i mentioned
start_node_at
specifically, i can remove that line.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, yeah I get that originally this would only revert the token (I've changed in on the other branch, because the parent-tracking version does now indeed delete the node, which I think is better / more correct and should be covered by the
unfinished_node
test). I just don't understand what should be "paired" here, perhaps this read like "you should only follow-up a call tocheckpoint
with a call tostart_node_at
if you want to use it, withoutstart_node
in between"? Either way I think we don't need it anymore if the new behaviour is to delete both node and token :)