A Golang runtime and compilation backend for Delta Interaction Nets.

native

+396 -5
+189 -5
pkg/deltanet/deltanet.go
··· 16 16 NodeTypeEraser 17 17 NodeTypeReplicator 18 18 NodeTypeVar // Wire/Interface 19 + NodeTypeData 20 + NodeTypeNative 19 21 ) 20 22 21 23 func (t NodeType) String() string { ··· 28 30 return "Replicator" 29 31 case NodeTypeVar: 30 32 return "Var" 33 + case NodeTypeData: 34 + return "Data" 35 + case NodeTypeNative: 36 + return "Native" 31 37 default: 32 38 return "Unknown" 33 39 } ··· 44 50 SetDead() bool 45 51 IsDead() bool 46 52 Revive() 53 + // For Data nodes 54 + GetValue() interface{} 55 + // For Native nodes 56 + GetName() string 47 57 } 48 58 49 59 // Port represents a connection point on a node. ··· 69 79 dead int32 70 80 } 71 81 72 - func (n *BaseNode) Type() NodeType { return n.typ } 73 - func (n *BaseNode) ID() uint64 { return n.id } 74 - func (n *BaseNode) Ports() []*Port { return n.ports } 75 - func (n *BaseNode) Level() int { return 0 } 76 - func (n *BaseNode) Deltas() []int { return nil } 82 + func (n *BaseNode) Type() NodeType { return n.typ } 83 + func (n *BaseNode) ID() uint64 { return n.id } 84 + func (n *BaseNode) Ports() []*Port { return n.ports } 85 + func (n *BaseNode) Level() int { return 0 } 86 + func (n *BaseNode) Deltas() []int { return nil } 87 + func (n *BaseNode) GetValue() interface{} { return nil } 88 + func (n *BaseNode) GetName() string { return "" } 77 89 78 90 func (n *BaseNode) SetDead() bool { 79 91 return atomic.CompareAndSwapInt32(&n.dead, 0, 1) ··· 97 109 func (n *ReplicatorNode) Level() int { return n.level } 98 110 func (n *ReplicatorNode) Deltas() []int { return n.deltas } 99 111 112 + // DataNode holds opaque data values. 113 + type DataNode struct { 114 + BaseNode 115 + value interface{} 116 + } 117 + 118 + func (n *DataNode) GetValue() interface{} { return n.value } 119 + 120 + // NativeNode represents a registered native function. 121 + type NativeNode struct { 122 + BaseNode 123 + name string 124 + } 125 + 126 + func (n *NativeNode) GetName() string { return n.name } 127 + 128 + // NativeFunc is a pure function that takes data and returns data or error. 129 + type NativeFunc func(interface{}) (interface{}, error) 130 + 100 131 // Network manages the graph of nodes and interactions. 101 132 type Network struct { 102 133 nextID uint64 ··· 122 153 nodes map[uint64]Node 123 154 nodesMu sync.Mutex 124 155 156 + // Native function registry 157 + natives map[string]NativeFunc 158 + nativesMu sync.RWMutex 159 + 125 160 traceBuf []TraceEvent 126 161 traceCap uint64 127 162 traceIdx uint64 ··· 148 183 scheduler: NewScheduler(), 149 184 workers: runtime.NumCPU(), 150 185 nodes: make(map[uint64]Node), 186 + natives: make(map[string]NativeFunc), 151 187 phase: 1, 152 188 } 153 189 return n ··· 277 313 return node 278 314 } 279 315 316 + func (n *Network) NewData(value interface{}) Node { 317 + id := n.nextNodeID() 318 + node := &DataNode{ 319 + BaseNode: BaseNode{ 320 + id: id, 321 + typ: NodeTypeData, 322 + ports: make([]*Port, 1), // 0: Connection 323 + }, 324 + value: value, 325 + } 326 + node.ports[0] = &Port{Node: node, Index: 0} 327 + n.nodesMu.Lock() 328 + if n.nodes == nil { 329 + n.nodes = make(map[uint64]Node) 330 + } 331 + n.nodes[node.id] = node 332 + n.nodesMu.Unlock() 333 + return node 334 + } 335 + 336 + func (n *Network) NewNative(name string) Node { 337 + id := n.nextNodeID() 338 + node := &NativeNode{ 339 + BaseNode: BaseNode{ 340 + id: id, 341 + typ: NodeTypeNative, 342 + ports: make([]*Port, 1), // 0: Connection (for application) 343 + }, 344 + name: name, 345 + } 346 + node.ports[0] = &Port{Node: node, Index: 0} 347 + n.nodesMu.Lock() 348 + if n.nodes == nil { 349 + n.nodes = make(map[uint64]Node) 350 + } 351 + n.nodes[node.id] = node 352 + n.nodesMu.Unlock() 353 + return node 354 + } 355 + 356 + func (n *Network) RegisterNative(name string, fn NativeFunc) { 357 + n.nativesMu.Lock() 358 + defer n.nativesMu.Unlock() 359 + n.natives[name] = fn 360 + } 361 + 362 + func (n *Network) GetNative(name string) (NativeFunc, bool) { 363 + n.nativesMu.RLock() 364 + defer n.nativesMu.RUnlock() 365 + fn, ok := n.natives[name] 366 + return fn, ok 367 + } 368 + 280 369 // Canonicalize prunes all nodes not reachable from the given root (node, port). 281 370 // For every unreachable node, all its connected wires are replaced by erasers. 282 371 func (n *Network) Canonicalize(root Node, rootPort int) { ··· 556 645 n.commuteFanReplicator(b, a, depth) 557 646 } 558 647 } 648 + case (a.Type() == NodeTypeFan && b.Type() == NodeTypeNative) || (a.Type() == NodeTypeNative && b.Type() == NodeTypeFan): 649 + // Fan-Native interaction: Application of native function 650 + rule = RuleFanNative 651 + if a.Type() == NodeTypeFan { 652 + n.applyNative(a, b, depth) 653 + } else { 654 + n.applyNative(b, a, depth) 655 + } 656 + case (a.Type() == NodeTypeFan && b.Type() == NodeTypeData) || (a.Type() == NodeTypeData && b.Type() == NodeTypeFan): 657 + // Fan-Data: should not happen in normal reduction (Data comes after Native) 658 + // But if it does, treat Data as inert (like Var) 659 + rule = RuleUnknown 660 + fmt.Printf("Warning: Fan-Data interaction (Fan %d <-> Data %d)\n", a.ID(), b.ID()) 661 + a.Revive() 662 + b.Revive() 559 663 default: 560 664 fmt.Printf("Unknown interaction: %v <-> %v\n", a.Type(), b.Type()) 561 665 } ··· 838 942 839 943 func (n *Network) createReplicatorCopyWithLevel(original Node, newLevel int) Node { 840 944 return n.NewReplicator(newLevel, original.Deltas()) 945 + } 946 + 947 + // applyNative executes a native function application: Fan-Native interaction 948 + // Fan represents application: Fan.0 = function, Fan.2 = argument, Fan.1 = result 949 + // Native is the function node 950 + func (n *Network) applyNative(fan, native Node, depth uint64) { 951 + // Get the native function 952 + nativeName := native.GetName() 953 + fn, ok := n.GetNative(nativeName) 954 + if !ok { 955 + fmt.Printf("Error: Native function %q not registered\n", nativeName) 956 + // Create error data node 957 + errData := n.NewData(fmt.Errorf("native function %q not found", nativeName)) 958 + // Connect result to error 959 + if fan.Ports()[1].Wire.Load() != nil { 960 + n.splice(errData.Ports()[0], fan.Ports()[1]) 961 + } 962 + n.removeNode(fan) 963 + n.removeNode(native) 964 + return 965 + } 966 + 967 + // Get the argument from Fan.2 968 + argNode, _ := n.GetLink(fan, 2) 969 + 970 + // Check if argument is Data 971 + if argNode == nil { 972 + fmt.Printf("Error: Native %q applied to nil argument\n", nativeName) 973 + errData := n.NewData(fmt.Errorf("nil argument")) 974 + if fan.Ports()[1].Wire.Load() != nil { 975 + n.splice(errData.Ports()[0], fan.Ports()[1]) 976 + } 977 + n.removeNode(fan) 978 + n.removeNode(native) 979 + return 980 + } 981 + 982 + if argNode.Type() == NodeTypeData { 983 + // Execute native function with data 984 + value := argNode.GetValue() 985 + result, err := fn(value) 986 + 987 + var resultNode Node 988 + if err != nil { 989 + // Return error as data 990 + resultNode = n.NewData(err) 991 + } else { 992 + // Check if result is a function (for currying) 993 + if resultFn, isFn := result.(func(interface{}) (interface{}, error)); isFn { 994 + // Result is a partially applied function - create new Native node 995 + // Register it with a unique name 996 + partialName := fmt.Sprintf("%s$partial$%d", nativeName, n.nextNodeID()) 997 + n.RegisterNative(partialName, resultFn) 998 + resultNode = n.NewNative(partialName) 999 + } else { 1000 + // Result is data 1001 + resultNode = n.NewData(result) 1002 + } 1003 + } 1004 + 1005 + // Connect result to Fan.1 1006 + if fan.Ports()[1].Wire.Load() != nil { 1007 + n.splice(resultNode.Ports()[0], fan.Ports()[1]) 1008 + } 1009 + 1010 + // Remove processed nodes 1011 + n.removeNode(fan) 1012 + n.removeNode(native) 1013 + n.removeNode(argNode) 1014 + } else { 1015 + // Argument is not Data yet - the argument needs to reduce first 1016 + // This shouldn't happen in normal execution since we reduce arguments before functions 1017 + // But if it does, we treat this as an error case 1018 + fmt.Printf("Warning: Native %q applied to non-Data argument (type %v)\n", nativeName, argNode.Type()) 1019 + 1020 + // For now, just leave the structure as-is 1021 + // The reduction will continue with other active wires 1022 + fan.Revive() 1023 + native.Revive() 1024 + } 841 1025 } 842 1026 843 1027 func (n *Network) SetPhase(p int) {
+206
pkg/deltanet/native_test.go
··· 1 + package deltanet 2 + 3 + import ( 4 + "fmt" 5 + "testing" 6 + ) 7 + 8 + // TestStringConcat tests concatenating two strings using native functions 9 + func TestStringConcat(t *testing.T) { 10 + net := NewNetwork() 11 + 12 + // Register concat native function 13 + net.RegisterNative("concat", func(a interface{}) (interface{}, error) { 14 + // This returns a function that captures 'a' (first string) 15 + s1, ok := a.(string) 16 + if !ok { 17 + return nil, fmt.Errorf("concat: first arg must be string, got %T", a) 18 + } 19 + 20 + // Return a native function that takes the second argument 21 + return func(b interface{}) (interface{}, error) { 22 + s2, ok := b.(string) 23 + if !ok { 24 + return nil, fmt.Errorf("concat: second arg must be string, got %T", b) 25 + } 26 + return s1 + s2, nil 27 + }, nil 28 + }) 29 + 30 + // Build net structure for: concat "hello" "world" 31 + // This is: (concat "hello") "world" 32 + // Structure: Fan(App) where: 33 + // Fan.0 -> connects to another Fan(App) for (concat "hello") 34 + // Fan.2 -> Data("world") 35 + // Fan.1 -> result 36 + 37 + // Inner application: concat "hello" 38 + innerFan := net.NewFan() 39 + concatNode := net.NewNative("concat") 40 + helloData := net.NewData("hello") 41 + 42 + net.Link(innerFan, 0, concatNode, 0) // Function 43 + net.Link(innerFan, 2, helloData, 0) // Argument 44 + // innerFan.1 is the result of (concat "hello") 45 + 46 + // Outer application: (concat "hello") "world" 47 + outerFan := net.NewFan() 48 + worldData := net.NewData("world") 49 + 50 + net.Link(outerFan, 0, innerFan, 1) // Function (result of inner app) 51 + net.Link(outerFan, 2, worldData, 0) // Argument 52 + // outerFan.1 is the final result 53 + 54 + // Connect result to output var 55 + output := net.NewVar() 56 + net.Link(outerFan, 1, output, 0) 57 + 58 + // Reduce 59 + net.ReduceAll() 60 + 61 + // Check result 62 + resultNode, resultPort := net.GetLink(output, 0) 63 + if resultNode == nil { 64 + t.Fatal("Result node is nil") 65 + } 66 + if resultNode.Type() != NodeTypeData { 67 + t.Errorf("Expected result to be Data node, got %v", resultNode.Type()) 68 + } 69 + 70 + result := resultNode.GetValue() 71 + expected := "helloworld" 72 + if result != expected { 73 + t.Errorf("Expected %q, got %v", expected, result) 74 + } 75 + 76 + t.Logf("Result: %v (port %d)", result, resultPort) 77 + } 78 + 79 + // TestStringLength tests computing the length of a string 80 + func TestStringLength(t *testing.T) { 81 + net := NewNetwork() 82 + 83 + // Register length native function 84 + net.RegisterNative("length", func(v interface{}) (interface{}, error) { 85 + s, ok := v.(string) 86 + if !ok { 87 + return nil, fmt.Errorf("length: arg must be string, got %T", v) 88 + } 89 + return len(s), nil 90 + }) 91 + 92 + // Build net structure for: length "hello" 93 + // Structure: Fan(App) where: 94 + // Fan.0 -> Native("length") 95 + // Fan.2 -> Data("hello") 96 + // Fan.1 -> result 97 + 98 + fan := net.NewFan() 99 + lengthNode := net.NewNative("length") 100 + helloData := net.NewData("hello") 101 + 102 + net.Link(fan, 0, lengthNode, 0) // Function 103 + net.Link(fan, 2, helloData, 0) // Argument 104 + 105 + // Connect result to output var 106 + output := net.NewVar() 107 + net.Link(fan, 1, output, 0) 108 + 109 + // Reduce 110 + net.ReduceAll() 111 + 112 + // Check result 113 + resultNode, resultPort := net.GetLink(output, 0) 114 + if resultNode == nil { 115 + t.Fatal("Result node is nil") 116 + } 117 + if resultNode.Type() != NodeTypeData { 118 + t.Errorf("Expected result to be Data node, got %v", resultNode.Type()) 119 + } 120 + 121 + result := resultNode.GetValue() 122 + expected := 5 123 + if result != expected { 124 + t.Errorf("Expected %d, got %v", expected, result) 125 + } 126 + 127 + t.Logf("Result: %v (port %d)", result, resultPort) 128 + } 129 + 130 + // TestConcatLength tests composing concat and length 131 + // length (concat "hello" "world") should return 10 132 + func TestConcatLength(t *testing.T) { 133 + net := NewNetwork() 134 + 135 + // Register both natives 136 + net.RegisterNative("concat", func(a interface{}) (interface{}, error) { 137 + s1, ok := a.(string) 138 + if !ok { 139 + return nil, fmt.Errorf("concat: first arg must be string, got %T", a) 140 + } 141 + return func(b interface{}) (interface{}, error) { 142 + s2, ok := b.(string) 143 + if !ok { 144 + return nil, fmt.Errorf("concat: second arg must be string, got %T", b) 145 + } 146 + return s1 + s2, nil 147 + }, nil 148 + }) 149 + 150 + net.RegisterNative("length", func(v interface{}) (interface{}, error) { 151 + s, ok := v.(string) 152 + if !ok { 153 + return nil, fmt.Errorf("length: arg must be string, got %T", v) 154 + } 155 + return len(s), nil 156 + }) 157 + 158 + // Build: length (concat "hello" "world") 159 + // Structure: 160 + // 1. Inner: concat "hello" -> partial 161 + // 2. Middle: partial "world" -> "helloworld" 162 + // 3. Outer: length "helloworld" -> 10 163 + 164 + // Build concat application 165 + concatInnerFan := net.NewFan() 166 + concatNode := net.NewNative("concat") 167 + helloData := net.NewData("hello") 168 + net.Link(concatInnerFan, 0, concatNode, 0) 169 + net.Link(concatInnerFan, 2, helloData, 0) 170 + 171 + concatOuterFan := net.NewFan() 172 + worldData := net.NewData("world") 173 + net.Link(concatOuterFan, 0, concatInnerFan, 1) 174 + net.Link(concatOuterFan, 2, worldData, 0) 175 + // concatOuterFan.1 is "helloworld" 176 + 177 + // Build length application 178 + lengthFan := net.NewFan() 179 + lengthNode := net.NewNative("length") 180 + net.Link(lengthFan, 0, lengthNode, 0) 181 + net.Link(lengthFan, 2, concatOuterFan, 1) // Apply to concat result 182 + 183 + // Connect result 184 + output := net.NewVar() 185 + net.Link(lengthFan, 1, output, 0) 186 + 187 + // Reduce 188 + net.ReduceAll() 189 + 190 + // Check result 191 + resultNode, _ := net.GetLink(output, 0) 192 + if resultNode == nil { 193 + t.Fatal("Result node is nil") 194 + } 195 + if resultNode.Type() != NodeTypeData { 196 + t.Errorf("Expected result to be Data node, got %v", resultNode.Type()) 197 + } 198 + 199 + result := resultNode.GetValue() 200 + expected := 10 201 + if result != expected { 202 + t.Errorf("Expected %d, got %v", expected, result) 203 + } 204 + 205 + t.Logf("length (concat \"hello\" \"world\") = %v", result) 206 + }
+1
pkg/deltanet/trace.go
··· 14 14 RuleRepDecay 15 15 RuleRepMerge 16 16 RuleAuxFanRep 17 + RuleFanNative 17 18 ) 18 19 19 20 type TraceEvent struct {