package iskra import ( "fmt" "testing" ) func TestExtractSymbols(t *testing.T) { dump := "FuncDecl RuneBytes\n Params\n r rune\n Results\n []byte\n Block\n Assign rs := [UTFMax]\n Return [rs,EncodeRune,r]\n" st := ExtractSymbols(dump) if st == nil { t.Fatal("nil symbol table") } if st.Counts["RuneBytes"] != 1 { t.Errorf("RuneBytes count: got %d, want 1", st.Counts["RuneBytes"]) } if st.Counts["UTFMax"] != 1 { t.Errorf("UTFMax count: got %d, want 1", st.Counts["UTFMax"]) } if st.Counts["EncodeRune"] != 1 { t.Errorf("EncodeRune count: got %d, want 1", st.Counts["EncodeRune"]) } if st.Counts["rs"] != 2 { t.Errorf("rs count: got %d, want 2 (decl+ref)", st.Counts["rs"]) } if st.Counts["r"] != 2 { t.Errorf("r count: got %d, want 2 (param+ref)", st.Counts["r"]) } } func TestCompareSymbols(t *testing.T) { dumpA := "FuncDecl Foo\n Block\n Assign x := [Bar]\n Return [x]\n" dumpB := "FuncDecl Foo\n Block\n Assign x := [Bar]\n Return [x]\n" stA := ExtractSymbols(dumpA) stB := ExtractSymbols(dumpB) m := CompareSymbols(stA, stB) if !m.IsEquivalent() { t.Errorf("identical dumps should be equivalent, onlyA=%v onlyB=%v", m.OnlyInA, m.OnlyInB) } if m.Score() != 100 { t.Errorf("score: got %d, want 100", m.Score()) } dumpC := "FuncDecl Foo\n Block\n Assign y := [Baz]\n Return [y]\n" stC := ExtractSymbols(dumpC) m2 := CompareSymbols(stA, stC) if m2.IsEquivalent() { t.Error("different dumps should not be equivalent") } } func TestIsomorphicIdentical(t *testing.T) { // Same structure, same names → isomorphic. dump := "FuncDecl Foo\n Params\n x int\n Block\n Assign y := [x]\n Return [y]\n" st := ExtractSymbols(dump) bg := ExtractBindings(st) sig := bg.Signature() if !sig.IsIsomorphic(sig) { t.Error("a signature should be isomorphic to itself") } } func TestIsomorphicRenamed(t *testing.T) { // Same structure, different names → should be isomorphic. dumpA := "FuncDecl Foo\n Params\n x int\n Block\n Assign y := [x]\n Return [y]\n" dumpB := "FuncDecl Bar\n Params\n a int\n Block\n Assign b := [a]\n Return [b]\n" stA := ExtractSymbols(dumpA) stB := ExtractSymbols(dumpB) bgA := ExtractBindings(stA) bgB := ExtractBindings(stB) sigA := bgA.Signature() sigB := bgB.Signature() if !sigA.IsIsomorphic(sigB) { t.Error("renamed variables should still be isomorphic") t.Logf("sigA locals: %v externs: %v", sigA.Locals, sigA.Externs) t.Logf("sigB locals: %v externs: %v", sigB.Locals, sigB.Externs) } if GeometricDistance(sigA, sigB) != 0 { t.Errorf("distance should be 0 for isomorphic sigs, got %d", GeometricDistance(sigA, sigB)) } } func TestNonIsomorphicExtraBinding(t *testing.T) { // Different structure: extra variable. dumpA := "FuncDecl F\n Params\n x int\n Block\n Return [x]\n" dumpB := "FuncDecl G\n Params\n x int\n Block\n Assign y := [x]\n Return [y]\n" stA := ExtractSymbols(dumpA) stB := ExtractSymbols(dumpB) sigA := ExtractBindings(stA).Signature() sigB := ExtractBindings(stB).Signature() if sigA.IsIsomorphic(sigB) { t.Error("different binding counts should not be isomorphic") } dist := GeometricDistance(sigA, sigB) if dist == 0 { t.Error("distance should be > 0 for non-isomorphic sigs") } } func TestIsomorphicNestedScope(t *testing.T) { // Same nesting structure, different names. dumpA := "FuncDecl F\n Block\n Assign x := [ExtA]\n If [x]\n Block\n Assign y := [x]\n Return [y]\n" dumpB := "FuncDecl G\n Block\n Assign a := [ExtB]\n If [a]\n Block\n Assign b := [a]\n Return [b]\n" stA := ExtractSymbols(dumpA) stB := ExtractSymbols(dumpB) sigA := ExtractBindings(stA).Signature() sigB := ExtractBindings(stB).Signature() if !sigA.IsIsomorphic(sigB) { t.Error("same nesting with renamed vars should be isomorphic") t.Logf("sigA locals: %v externs: %v", sigA.Locals, sigA.Externs) t.Logf("sigB locals: %v externs: %v", sigB.Locals, sigB.Externs) } } func TestIsomorphicDifferentDepth(t *testing.T) { // Same names, but variable declared at different depth → not isomorphic. dumpA := "FuncDecl F\n Block\n Assign x :=\n Return [x]\n" dumpB := "FuncDecl F\n Block\n If\n Block\n Assign x :=\n Return [x]\n" stA := ExtractSymbols(dumpA) stB := ExtractSymbols(dumpB) sigA := ExtractBindings(stA).Signature() sigB := ExtractBindings(stB).Signature() if sigA.IsIsomorphic(sigB) { t.Error("different declaration depths should not be isomorphic") } } func TestExternalRefsGeometry(t *testing.T) { // External refs (package-level constants) should be tracked // by count and depth, not by name. dumpA := "FuncDecl F\n Block\n Return [RuneError,RuneError]\n" dumpB := "FuncDecl G\n Block\n Return [MaxRune,MaxRune]\n" stA := ExtractSymbols(dumpA) stB := ExtractSymbols(dumpB) sigA := ExtractBindings(stA).Signature() sigB := ExtractBindings(stB).Signature() if !sigA.IsIsomorphic(sigB) { t.Error("same external ref pattern should be isomorphic regardless of name") } } func TestRealDecodeRuneGeometry(t *testing.T) { dump := "FuncDecl DecodeRune\n Params\n p []byte\n Results\n r rune\n size int\n Block\n Assign n := [p]\n If [n]\n Block\n Return [RuneError]\n Assign p0 := [p]\n Assign x := [first,p0]\n If [x,as]\n Block\n Assign mask := [x]\n Return [p,mask,RuneError]\n Assign sz := [x]\n Assign accept := [acceptRanges,x]\n If [n,sz]\n Block\n Return [RuneError]\n Assign b1 := [p]\n If [b1,accept,lo,hi]\n Block\n Return [RuneError]\n If [sz]\n Block\n Return [p0,mask2,b1,maskx]\n Assign b2 := [p]\n If [b2,locb,hicb]\n Block\n Return [RuneError]\n If [sz]\n Block\n Return [p0,mask3,b1,maskx,b2]\n Assign b3 := [p]\n If [b3,locb,hicb]\n Block\n Return [RuneError]\n Return [p0,mask4,b1,maskx,b2,b3]\n" st := ExtractSymbols(dump) bg := ExtractBindings(st) sig := bg.Signature() // Sanity: should have local bindings for DecodeRune, p, r, size, // n, p0, x, mask, sz, accept, b1, b2, b3. if len(bg.Bindings) < 10 { t.Errorf("expected at least 10 local bindings, got %d", len(bg.Bindings)) } // Should have external refs for RuneError, first, acceptRanges, // as, lo, hi, mask2, mask3, mask4, maskx, locb, hicb. if len(bg.Externals) < 5 { t.Errorf("expected at least 5 external refs, got %d", len(bg.Externals)) } // Self-isomorphism. if !sig.IsIsomorphic(sig) { t.Error("DecodeRune should be isomorphic to itself") } fmt.Printf("DecodeRune: %d locals, %d externals\n", len(bg.Bindings), len(bg.Externals)) for i, b := range bg.Bindings { fmt.Printf(" local[%d]: decl@%d refs@%v\n", i, b.DeclDepth, b.RefDepths) } for i, e := range bg.Externals { fmt.Printf(" extern[%d]: count=%d depths=%v\n", i, e.RefCount, e.Depths) } }