Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b8bd4df
Re-emit branch_point captures on fail-retry
stefanobaghino Apr 20, 2026
1ee0e3c
Decrement Haskell syntest baseline after capture re-emit fix
stefanobaghino Apr 20, 2026
b8e4936
Fix MSRV rustfmt and docs broken intra-doc links
stefanobaghino Apr 20, 2026
c183165
Restore cleared scopes before Pop on pop: N + set: X
stefanobaghino Apr 20, 2026
d1200b3
Clip captures: N span to rule's match bounds
stefanobaghino Apr 21, 2026
cd23cc9
Suppress cur meta_scope on pop: N + embed: match text
stefanobaghino Apr 21, 2026
e9c022a
Drain orphan scope atoms on outer escape fire
stefanobaghino Apr 22, 2026
b185a2c
Order apply_prototype external prototype before target main
stefanobaghino Apr 22, 2026
69cd4b3
Cover apply_prototype external-prototype precedence
stefanobaghino Apr 23, 2026
347ea76
Drop HAML-Rails from syntest baselines
stefanobaghino Apr 23, 2026
17e46c4
Wrap past-EOL syntest assertions to next-line scopes
stefanobaghino Apr 23, 2026
b1b5438
Dedup flushed_ops across multi-fail parse_line calls
stefanobaghino Apr 23, 2026
78da6c4
Anchor replay-born branches to their replay line
stefanobaghino Apr 23, 2026
715a68e
Inherit outer prefix in replay-born branch_points
stefanobaghino Apr 25, 2026
7706736
Refresh post-replay shadow and stack_before snapshots
stefanobaghino Apr 25, 2026
d31dbe3
Subtract bp.pop_count in branch_point retain predicate
stefanobaghino Apr 26, 2026
d4a7e05
Route handle_fail cross-line re-emit through push_meta_ops
stefanobaghino Apr 26, 2026
82703dc
Save flushed_ops before cross-line replay loop and prefer inner corre…
stefanobaghino Apr 26, 2026
f3e497a
Fall through to parent on branch_point exhaustion
stefanobaghino Apr 27, 2026
ed949e4
Restore cleared scopes before deeper pops in pop:N
stefanobaghino Apr 27, 2026
0a2139a
Skip inner replay correction when nested deeper than outer BP
stefanobaghino Apr 27, 2026
d36dc01
Treat pop:N + push:X as Set with pop_count=N
stefanobaghino Apr 27, 2026
b31b727
Skip cur mcs Pop in Set when wrapper has embed_scope_replaces
stefanobaghino Apr 27, 2026
1a83739
Un-ignore embed wrapper-mcs test; CI checks out the submodule
stefanobaghino Jul 11, 2026
02210d7
Un-ignore the 630-era submodule tests too; CI has the submodule
stefanobaghino Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/default_newlines.packdump
Binary file not shown.
Binary file modified assets/default_nonewlines.packdump
Binary file not shown.
355 changes: 281 additions & 74 deletions examples/syntest.rs

Large diffs are not rendered by default.

2,031 changes: 1,891 additions & 140 deletions src/parsing/parser.rs

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions src/parsing/syntax_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,17 +274,20 @@ impl<'a> Iterator for MatchIter<'a> {
_ => return self.next(),
};
let ctx_ptr = self.syntax_set.get_context(context_id).unwrap();
// Also include the external syntax's prototype if the context allows it
// Push target first, then external prototype on top so
// its patterns are iterated first: `MatchIter::next`
// reads the stack top, and ST's `apply_prototype` runs
// the prototype ahead of the included context — mirror
// of `ParseState::find_best_match`'s prototype chaining.
self.ctx_stack.push(ctx_ptr);
self.index_stack.push(0);
if ctx_ptr.meta_include_prototype.unwrap_or(true) {
if let Some(ref proto_id) = ctx_ptr.prototype {
let proto_ctx = self.syntax_set.get_context(proto_id).unwrap();
// Push prototype first (it will be iterated first)
self.ctx_stack.push(proto_ctx);
self.index_stack.push(0);
}
}
self.ctx_stack.push(ctx_ptr);
self.index_stack.push(0);
}
}
} else {
Expand Down
155 changes: 153 additions & 2 deletions src/parsing/syntax_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2576,8 +2576,159 @@ mod tests {
builder.add(syntax_using_proto);
let ss = builder.build();

// Just verify it builds without errors and the syntax exists
assert!(ss.find_syntax_by_name("UsingProto").is_some());
let syntax = ss.find_syntax_by_name("UsingProto").unwrap();
let mut parse_state = ParseState::new(syntax);
let ops = parse_state.parse_line("#", &ss).expect("#[cfg(test)]").ops;
// The external `prototype`'s `#` rule must be reachable from the
// including context via `apply_prototype: true`.
let expected = (0, ScopeStackOp::Push(Scope::new("comment.proto").unwrap()));
assert_ops_contain(&ops, &expected);
}

#[test]
fn apply_prototype_prototype_wins_tie_over_target_main() {
// Both the target's `main` and its `prototype` match `|` at the same
// position. ST's `apply_prototype` semantics put the prototype ahead
// of the target, so the prototype's scope wins the tie.
let target = SyntaxDefinition::load_from_str(
r#"
name: Target
scope: source.target
file_extensions: [t]
contexts:
prototype:
- match: '\|'
scope: proto.pipe
main:
- match: '\|'
scope: target.bitor
"#,
true,
None,
)
.unwrap();

let outer = SyntaxDefinition::load_from_str(
r#"
name: Outer
scope: source.outer
file_extensions: [o]
contexts:
main:
- include: scope:source.target
apply_prototype: true
"#,
true,
None,
)
.unwrap();

let mut builder = SyntaxSetBuilder::new();
builder.add(target);
builder.add(outer);
let ss = builder.build();

let syntax = ss.find_syntax_by_name("Outer").unwrap();
let mut parse_state = ParseState::new(syntax);
let ops = parse_state.parse_line("|", &ss).expect("#[cfg(test)]").ops;
assert_ops_contain(
&ops,
&(0, ScopeStackOp::Push(Scope::new("proto.pipe").unwrap())),
);
assert!(
!ops.iter()
.any(|(_, op)| matches!(op, ScopeStackOp::Push(s) if s == &Scope::new("target.bitor").unwrap())),
"target main's `|` rule must not pre-empt the external prototype's `|` rule: {:?}",
ops,
);
}

#[test]
fn apply_prototype_respects_meta_include_prototype_false() {
// When the include target opts out of prototype inclusion, the
// external prototype must NOT be injected even with
// `apply_prototype: true` on the include.
let target = SyntaxDefinition::load_from_str(
r#"
name: Target
scope: source.target
file_extensions: [t]
contexts:
prototype:
- match: '\|'
scope: proto.pipe
main:
- meta_include_prototype: false
- match: '\|'
scope: target.bitor
"#,
true,
None,
)
.unwrap();

let outer = SyntaxDefinition::load_from_str(
r#"
name: Outer
scope: source.outer
file_extensions: [o]
contexts:
main:
- include: scope:source.target
apply_prototype: true
"#,
true,
None,
)
.unwrap();

let mut builder = SyntaxSetBuilder::new();
builder.add(target);
builder.add(outer);
let ss = builder.build();

let syntax = ss.find_syntax_by_name("Outer").unwrap();
let mut parse_state = ParseState::new(syntax);
let ops = parse_state.parse_line("|", &ss).expect("#[cfg(test)]").ops;
assert_ops_contain(
&ops,
&(0, ScopeStackOp::Push(Scope::new("target.bitor").unwrap())),
);
assert!(
!ops.iter()
.any(|(_, op)| matches!(op, ScopeStackOp::Push(s) if s == &Scope::new("proto.pipe").unwrap())),
"prototype must stay out when target's `meta_include_prototype: false`: {:?}",
ops,
);
}

#[test]
fn haml_pipe_continuation_wins_over_ruby_bitor() {
// Real-package regression guard for the fix: inside HAML attribute
// braces, a trailing `|` must scope as HAML's pipe-continuation
// (injected via Ruby-for-HAML's prototype under `apply_prototype`),
// not as Ruby's bitwise-or operator.
let ss = &*testdata::PACKAGES_SYN_SET;
let syntax = ss.find_syntax_by_name("HAML").unwrap();
let mut parse_state = ParseState::new(syntax);
let ops = parse_state
.parse_line("%p{:a => 1, |", ss)
.expect("#[cfg(test)]")
.ops;
let pipe_cont = Scope::new("punctuation.separator.continuation.haml").unwrap();
let ruby_bitor = Scope::new("keyword.operator.bitwise.ruby").unwrap();
assert!(
ops.iter()
.any(|(_, op)| matches!(op, ScopeStackOp::Push(s) if s == &pipe_cont)),
"expected punctuation.separator.continuation.haml push: {:?}",
ops,
);
assert!(
!ops.iter()
.any(|(_, op)| matches!(op, ScopeStackOp::Push(s) if s == &ruby_bitor)),
"Ruby bitwise-or must not win over HAML pipe-continuation: {:?}",
ops,
);
}

// =====================================================
Expand Down
Loading