symbols_test.mx raw

   1  package iskra
   2  
   3  import (
   4  	"fmt"
   5  	"testing"
   6  )
   7  
   8  func TestExtractSymbols(t *testing.T) {
   9  	dump := "FuncDecl RuneBytes\n  Params\n    r rune\n  Results\n    []byte\n  Block\n    Assign rs := [UTFMax]\n    Return [rs,EncodeRune,r]\n"
  10  
  11  	st := ExtractSymbols(dump)
  12  	if st == nil {
  13  		t.Fatal("nil symbol table")
  14  	}
  15  
  16  	if st.Counts["RuneBytes"] != 1 {
  17  		t.Errorf("RuneBytes count: got %d, want 1", st.Counts["RuneBytes"])
  18  	}
  19  	if st.Counts["UTFMax"] != 1 {
  20  		t.Errorf("UTFMax count: got %d, want 1", st.Counts["UTFMax"])
  21  	}
  22  	if st.Counts["EncodeRune"] != 1 {
  23  		t.Errorf("EncodeRune count: got %d, want 1", st.Counts["EncodeRune"])
  24  	}
  25  	if st.Counts["rs"] != 2 {
  26  		t.Errorf("rs count: got %d, want 2 (decl+ref)", st.Counts["rs"])
  27  	}
  28  	if st.Counts["r"] != 2 {
  29  		t.Errorf("r count: got %d, want 2 (param+ref)", st.Counts["r"])
  30  	}
  31  }
  32  
  33  func TestCompareSymbols(t *testing.T) {
  34  	dumpA := "FuncDecl Foo\n  Block\n    Assign x := [Bar]\n    Return [x]\n"
  35  	dumpB := "FuncDecl Foo\n  Block\n    Assign x := [Bar]\n    Return [x]\n"
  36  
  37  	stA := ExtractSymbols(dumpA)
  38  	stB := ExtractSymbols(dumpB)
  39  	m := CompareSymbols(stA, stB)
  40  
  41  	if !m.IsEquivalent() {
  42  		t.Errorf("identical dumps should be equivalent, onlyA=%v onlyB=%v", m.OnlyInA, m.OnlyInB)
  43  	}
  44  	if m.Score() != 100 {
  45  		t.Errorf("score: got %d, want 100", m.Score())
  46  	}
  47  
  48  	dumpC := "FuncDecl Foo\n  Block\n    Assign y := [Baz]\n    Return [y]\n"
  49  	stC := ExtractSymbols(dumpC)
  50  	m2 := CompareSymbols(stA, stC)
  51  	if m2.IsEquivalent() {
  52  		t.Error("different dumps should not be equivalent")
  53  	}
  54  }
  55  
  56  func TestIsomorphicIdentical(t *testing.T) {
  57  	// Same structure, same names → isomorphic.
  58  	dump := "FuncDecl Foo\n  Params\n    x int\n  Block\n    Assign y := [x]\n    Return [y]\n"
  59  
  60  	st := ExtractSymbols(dump)
  61  	bg := ExtractBindings(st)
  62  	sig := bg.Signature()
  63  
  64  	if !sig.IsIsomorphic(sig) {
  65  		t.Error("a signature should be isomorphic to itself")
  66  	}
  67  }
  68  
  69  func TestIsomorphicRenamed(t *testing.T) {
  70  	// Same structure, different names → should be isomorphic.
  71  	dumpA := "FuncDecl Foo\n  Params\n    x int\n  Block\n    Assign y := [x]\n    Return [y]\n"
  72  	dumpB := "FuncDecl Bar\n  Params\n    a int\n  Block\n    Assign b := [a]\n    Return [b]\n"
  73  
  74  	stA := ExtractSymbols(dumpA)
  75  	stB := ExtractSymbols(dumpB)
  76  	bgA := ExtractBindings(stA)
  77  	bgB := ExtractBindings(stB)
  78  	sigA := bgA.Signature()
  79  	sigB := bgB.Signature()
  80  
  81  	if !sigA.IsIsomorphic(sigB) {
  82  		t.Error("renamed variables should still be isomorphic")
  83  		t.Logf("sigA locals: %v externs: %v", sigA.Locals, sigA.Externs)
  84  		t.Logf("sigB locals: %v externs: %v", sigB.Locals, sigB.Externs)
  85  	}
  86  	if GeometricDistance(sigA, sigB) != 0 {
  87  		t.Errorf("distance should be 0 for isomorphic sigs, got %d", GeometricDistance(sigA, sigB))
  88  	}
  89  }
  90  
  91  func TestNonIsomorphicExtraBinding(t *testing.T) {
  92  	// Different structure: extra variable.
  93  	dumpA := "FuncDecl F\n  Params\n    x int\n  Block\n    Return [x]\n"
  94  	dumpB := "FuncDecl G\n  Params\n    x int\n  Block\n    Assign y := [x]\n    Return [y]\n"
  95  
  96  	stA := ExtractSymbols(dumpA)
  97  	stB := ExtractSymbols(dumpB)
  98  	sigA := ExtractBindings(stA).Signature()
  99  	sigB := ExtractBindings(stB).Signature()
 100  
 101  	if sigA.IsIsomorphic(sigB) {
 102  		t.Error("different binding counts should not be isomorphic")
 103  	}
 104  	dist := GeometricDistance(sigA, sigB)
 105  	if dist == 0 {
 106  		t.Error("distance should be > 0 for non-isomorphic sigs")
 107  	}
 108  }
 109  
 110  func TestIsomorphicNestedScope(t *testing.T) {
 111  	// Same nesting structure, different names.
 112  	dumpA := "FuncDecl F\n  Block\n    Assign x := [ExtA]\n    If [x]\n      Block\n        Assign y := [x]\n        Return [y]\n"
 113  	dumpB := "FuncDecl G\n  Block\n    Assign a := [ExtB]\n    If [a]\n      Block\n        Assign b := [a]\n        Return [b]\n"
 114  
 115  	stA := ExtractSymbols(dumpA)
 116  	stB := ExtractSymbols(dumpB)
 117  	sigA := ExtractBindings(stA).Signature()
 118  	sigB := ExtractBindings(stB).Signature()
 119  
 120  	if !sigA.IsIsomorphic(sigB) {
 121  		t.Error("same nesting with renamed vars should be isomorphic")
 122  		t.Logf("sigA locals: %v externs: %v", sigA.Locals, sigA.Externs)
 123  		t.Logf("sigB locals: %v externs: %v", sigB.Locals, sigB.Externs)
 124  	}
 125  }
 126  
 127  func TestIsomorphicDifferentDepth(t *testing.T) {
 128  	// Same names, but variable declared at different depth → not isomorphic.
 129  	dumpA := "FuncDecl F\n  Block\n    Assign x :=\n    Return [x]\n"
 130  	dumpB := "FuncDecl F\n  Block\n    If\n      Block\n        Assign x :=\n        Return [x]\n"
 131  
 132  	stA := ExtractSymbols(dumpA)
 133  	stB := ExtractSymbols(dumpB)
 134  	sigA := ExtractBindings(stA).Signature()
 135  	sigB := ExtractBindings(stB).Signature()
 136  
 137  	if sigA.IsIsomorphic(sigB) {
 138  		t.Error("different declaration depths should not be isomorphic")
 139  	}
 140  }
 141  
 142  func TestExternalRefsGeometry(t *testing.T) {
 143  	// External refs (package-level constants) should be tracked
 144  	// by count and depth, not by name.
 145  	dumpA := "FuncDecl F\n  Block\n    Return [RuneError,RuneError]\n"
 146  	dumpB := "FuncDecl G\n  Block\n    Return [MaxRune,MaxRune]\n"
 147  
 148  	stA := ExtractSymbols(dumpA)
 149  	stB := ExtractSymbols(dumpB)
 150  	sigA := ExtractBindings(stA).Signature()
 151  	sigB := ExtractBindings(stB).Signature()
 152  
 153  	if !sigA.IsIsomorphic(sigB) {
 154  		t.Error("same external ref pattern should be isomorphic regardless of name")
 155  	}
 156  }
 157  
 158  func TestRealDecodeRuneGeometry(t *testing.T) {
 159  	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"
 160  
 161  	st := ExtractSymbols(dump)
 162  	bg := ExtractBindings(st)
 163  	sig := bg.Signature()
 164  
 165  	// Sanity: should have local bindings for DecodeRune, p, r, size,
 166  	// n, p0, x, mask, sz, accept, b1, b2, b3.
 167  	if len(bg.Bindings) < 10 {
 168  		t.Errorf("expected at least 10 local bindings, got %d", len(bg.Bindings))
 169  	}
 170  
 171  	// Should have external refs for RuneError, first, acceptRanges,
 172  	// as, lo, hi, mask2, mask3, mask4, maskx, locb, hicb.
 173  	if len(bg.Externals) < 5 {
 174  		t.Errorf("expected at least 5 external refs, got %d", len(bg.Externals))
 175  	}
 176  
 177  	// Self-isomorphism.
 178  	if !sig.IsIsomorphic(sig) {
 179  		t.Error("DecodeRune should be isomorphic to itself")
 180  	}
 181  
 182  	fmt.Printf("DecodeRune: %d locals, %d externals\n", len(bg.Bindings), len(bg.Externals))
 183  	for i, b := range bg.Bindings {
 184  		fmt.Printf("  local[%d]: decl@%d refs@%v\n", i, b.DeclDepth, b.RefDepths)
 185  	}
 186  	for i, e := range bg.Externals {
 187  		fmt.Printf("  extern[%d]: count=%d depths=%v\n", i, e.RefCount, e.Depths)
 188  	}
 189  }
 190