diff --git a/cgo_harness/issue454_variant_cguard_fivesize_test.go b/cgo_harness/issue454_variant_cguard_fivesize_test.go new file mode 100644 index 000000000..2e9a0c246 --- /dev/null +++ b/cgo_harness/issue454_variant_cguard_fivesize_test.go @@ -0,0 +1,60 @@ +//go:build cgo && treesitter_c_parity + +package cgoharness + +import ( + "bytes" + "strconv" + "testing" + + "github.com/odvcencio/gotreesitter/internal/benchfixtures" + sitter "github.com/tree-sitter/go-tree-sitter" +) + +func TestIssue454VariantCGuardFiveSizes(t *testing.T) { + sizes := []int{1024, 4096, 16384, 65536, benchfixtures.Issue454CFixtureBytes} + for _, size := range sizes { + size := size + t.Run(sizeLabel(size), func(t *testing.T) { + source := append([]byte(nil), benchfixtures.Issue454CSource()[:size]...) + site := bytes.Index(source, []byte("x0")) + if site < 0 { + t.Fatal("C edit marker is absent") + } + edited := append(append([]byte(nil), source[:site]...), source[site+1:]...) + + goTree, goLang, err := parseWithGo(parityCase{name: "c"}, edited, nil) + if err != nil { + t.Fatalf("parse edited C witness with Go: %v", err) + } + t.Cleanup(goTree.Release) + + cLang, err := ParityCLanguage("c") + if err != nil { + t.Fatalf("load locked C grammar: %v", err) + } + cParser := sitter.NewParser() + t.Cleanup(cParser.Close) + if err := cParser.SetLanguage(cLang); err != nil { + t.Fatalf("set locked C grammar: %v", err) + } + cTree := cParser.Parse(edited, nil) + if cTree == nil || cTree.RootNode() == nil { + t.Fatal("locked C parser returned no tree") + } + t.Cleanup(cTree.Close) + + if diff := FirstDivergenceDumpV1(goTree.RootNode(), goLang, cTree.RootNode()); diff != nil { + t.Fatalf("size=%d locked-C parity diverged: %+v", size, *diff) + } + t.Logf("size=%d locked-C parity passed", size) + }) + } +} + +func sizeLabel(size int) string { + if size == benchfixtures.Issue454CFixtureBytes { + return "137KiB" + } + return strconv.Itoa(size) + "B" +} diff --git a/parser_recover_c.go b/parser_recover_c.go index a5079c823..7039c349c 100644 --- a/parser_recover_c.go +++ b/parser_recover_c.go @@ -4074,6 +4074,21 @@ func (p *Parser) cAppendVisibleSplice(dst []*Node, n *Node) []*Node { return dst } +func (p *Parser) cAppendVisibleSpliceWithFields(scratch *reduceBuildScratch, n *Node) { + if p == nil || scratch == nil || n == nil { + return + } + if n.symbol == errorSymbol || n.isMissing() || p.cSymbolVisible(n.symbol) { + scratch.appendNode(n) + return + } + if hiddenTreeHasFieldIDs(n) { + appendFlattenedHiddenChildrenWithFieldScratch(scratch, n, p.language.SymbolMetadata, nil) + return + } + appendFlattenedHiddenChildrenToScratch(scratch, n, p.language.SymbolMetadata, nil) +} + func (p *Parser) cAppendVisibleSpliceUntil(dst []*Node, n *Node, limit int) ([]*Node, bool) { if n == nil { return dst, true @@ -4162,7 +4177,8 @@ func (p *Parser) cRecoverToState(v *glrStack, depth int, goal StateID, arena *no // open ERROR node's children) and splice invisible nodes the way the // engine's reduce does. The raw popped extent pins the ERROR span (C // error regions cover invisible subtrees too). - children := make([]*Node, 0, len(wrapped)+2) + splice := reduceBuildScratch{} + splice.nodes = make([]*Node, 0, len(wrapped)+2) openErr := (*cRecoverState)(nil) if v.cRec != nil { openErr = v.cRec @@ -4175,14 +4191,17 @@ func (p *Parser) cRecoverToState(v *glrStack, depth int, goal StateID, arena *no rawLast = n if openErr != nil && n == openErr.openErr { // Open-region children were visible-spliced at absorb time. - children = append(children, n.children...) + for _, child := range n.children { + splice.appendNode(child) + } continue } // C parity: popped closed subtrees (ERROR carriers included) keep // their identity inside the new ERROR; only invisible subtrees // flatten. - children = p.cAppendVisibleSplice(children, n) + p.cAppendVisibleSpliceWithFields(&splice, n) } + children, fieldIDs, fieldSources := materializeReduceChildrenFromScratch(&splice, arena) fork := v.cloneWithScratch(gssScratch) fork.cRec = nil @@ -4215,6 +4234,7 @@ func (p *Parser) cRecoverToState(v *glrStack, depth int, goal StateID, arena *no if rawFirst != nil { errNode := p.newRecoveryParentNodeInArena(arena, errorSymbol, true, children, 0) + errNode.setFieldMetadata(fieldIDs, fieldSources) cSetNodeSpan(errNode, rawFirst.startByte, rawLast.endByte, rawFirst.startPoint, rawLast.endPoint) errNode.setHasError(true) errNode.setExtra(true) @@ -4277,7 +4297,9 @@ func (p *Parser) cAbsorbTokenIntoError(v *glrStack, tok Token, nodeCount *int, a if leafVisible { leaf = newLeafNodeInArena(arena, tok.Symbol, tok.Symbol == errorSymbol || p.isNamedSymbol(tok.Symbol), tok.StartByte, tok.EndByte, tok.StartPoint, tok.EndPoint) - leaf.setHasError(true) + if p.language == nil || p.language.Name != "c" { + leaf.setHasError(true) + } // C: if the token shifts as extra in state 1, mark it extra so it is // not counted in error cost calculations. if idx := p.lookupActionIndex(1, tok.Symbol); idx != 0 && int(idx) < len(p.language.ParseActions) {