A Golang runtime and compilation backend for Delta Interaction Nets.
at main 513 lines 17 kB view raw
1package lambda 2 3import ( 4 "fmt" 5 "github.com/vic/godnet/pkg/deltanet" 6 "os" 7) 8 9var deltaDebug = os.Getenv("DELTA_DEBUG") != "" 10 11// Context for variables: name -> {Node, Port, Level} 12type varInfo struct { 13 node deltanet.Node 14 port int 15 level int 16} 17 18// ToDeltaNet converts a lambda term to a Delta Net. 19// Returns: root node, root port, and a map from Var node IDs to variable names. 20func ToDeltaNet(term Term, net *deltanet.Network) (deltanet.Node, int, map[uint64]string) { 21 // We return the Node and Port index that represents the "root" of the term. 22 // This port should be connected to the "parent". 23 24 vars := make(map[string]*varInfo) 25 varNames := make(map[uint64]string) 26 27 node, port := buildTerm(term, net, vars, 0, 0, varNames) 28 return node, port, varNames 29} 30 31func buildTerm(term Term, net *deltanet.Network, vars map[string]*varInfo, level int, depth uint64, varNames map[uint64]string) (deltanet.Node, int) { 32 switch t := term.(type) { 33 case Var: 34 if info, ok := vars[t.Name]; ok { 35 // Variable is bound 36 37 if info.node.Type() == deltanet.NodeTypeReplicator { 38 // Subsequent use 39 // info.node is the Replicator. 40 // We need to add a port to it. 41 // Create new Replicator with +1 port. 42 oldRep := info.node 43 oldDeltas := oldRep.Deltas() 44 newDelta := level - (info.level + 1) 45 newDeltas := append(oldDeltas, newDelta) 46 47 newRep := net.NewReplicator(oldRep.Level(), newDeltas) 48 // fmt.Printf("ToDeltaNet: Expand Replicator ID %d level=%d oldDeltas=%v -> newDeltas=%v (usage level=%d, binder level=%d)\n", oldRep.ID(), oldRep.Level(), oldDeltas, newDeltas, level, info.level) 49 50 // Move connections 51 // Rep.0 -> Source 52 sourceNode, sourcePort := net.GetLink(oldRep, 0) 53 net.LinkAt(newRep, 0, sourceNode, sourcePort, depth) 54 55 // Move existing aux ports 56 for i := 0; i < len(oldDeltas); i++ { 57 // Get what oldRep.i+1 is connected to 58 destNode, destPort := net.GetLink(oldRep, i+1) 59 if destNode != nil { 60 net.LinkAt(newRep, i+1, destNode, destPort, depth) 61 } 62 } 63 64 // Update info 65 info.node = newRep 66 info.port = 0 67 68 // Return new port 69 return newRep, len(newDeltas) // Index is len (1-based? No, 0 is principal. 1..len) 70 } 71 72 linkNode, _ := net.GetLink(info.node, info.port) 73 74 if linkNode.Type() == deltanet.NodeTypeEraser { 75 // First use 76 // Remove Eraser (linkNode) 77 // In `deltanet`, `removeNode` is no-op, but we should disconnect. 78 // Actually `Link` overwrites. 79 80 // Create Replicator 81 delta := level - (info.level + 1) 82 83 repLevel := info.level + 1 84 85 // Link Rep.0 to Source (info.node, info.port) 86 rep := net.NewReplicator(repLevel, []int{delta}) 87 net.LinkAt(rep, 0, info.node, info.port, depth) 88 // fmt.Printf("ToDeltaNet: First-use: created Replicator ID %d level=%d deltas=%v for binder level=%d usage level=%d\n", rep.ID(), rep.Level(), rep.Deltas(), info.level, level) 89 90 // Update info to point to Rep 91 info.node = rep 92 info.port = 0 // Rep.0 is the input 93 94 // Return Rep.1 95 return rep, 1 96 97 } else { 98 // Should not happen if logic is correct (either Eraser or Replicator) 99 panic(fmt.Sprintf("Unexpected node type on variable binding: %v", linkNode.Type())) 100 } 101 102 } else { 103 // Free variable 104 // Create Var node 105 v := net.NewVar() 106 // Store the variable name for later reconstruction 107 varNames[v.ID()] = t.Name 108 // Create Replicator to share it (as per deltanets.ts) 109 // "Create free variable node... Create a replicator fan-in... link... return rep.1" 110 // Level 0 for free vars. 111 // Debug: record replicator parameters for free var 112 // fmt.Printf("ToDeltaNet: Free var '%s' at level=%d -> Rep(level=%d, deltas=%v)\n", t.Name, level, 0, []int{level - 1}) 113 rep := net.NewReplicator(0, []int{level - 1}) // level - (0 + 1) ? 114 net.LinkAt(rep, 0, v, 0, depth) 115 116 // Register in vars so we can share it if used again 117 vars[t.Name] = &varInfo{node: rep, port: 0, level: 0} 118 119 return rep, 1 120 } 121 122 case Abs: 123 // Create Fan 124 fan := net.NewFan() 125 // fan.0 is Result (returned) 126 // fan.1 is Body 127 // fan.2 is Var 128 129 // Create Eraser for Var initially 130 era := net.NewEraser() 131 net.LinkAt(era, 0, fan, 2, depth) 132 133 // Register var 134 // Save old var info if shadowing 135 oldVar := vars[t.Arg] 136 vars[t.Arg] = &varInfo{node: fan, port: 2, level: level} 137 138 // Build Body 139 bodyNode, bodyPort := buildTerm(t.Body, net, vars, level, depth, varNames) 140 net.LinkAt(fan, 1, bodyNode, bodyPort, depth) 141 142 // Restore var 143 if oldVar != nil { 144 vars[t.Arg] = oldVar 145 } else { 146 delete(vars, t.Arg) 147 } 148 149 return fan, 0 150 151 case App: 152 // Create Fan 153 fan := net.NewFan() 154 // fan.0 is Function 155 // fan.1 is Result (returned) 156 // fan.2 is Argument 157 158 // Build Function 159 funNode, funPort := buildTerm(t.Fun, net, vars, level, depth, varNames) 160 net.LinkAt(fan, 0, funNode, funPort, depth) 161 162 // Build Argument (level + 1) 163 argNode, argPort := buildTerm(t.Arg, net, vars, level+1, depth+1, varNames) 164 net.LinkAt(fan, 2, argNode, argPort, depth+1) 165 166 return fan, 1 167 168 case Let: 169 // Should have been desugared by parser, but if we encounter it: 170 // let x = Val in Body -> (\x. Body) Val 171 desugared := App{ 172 Fun: Abs{Arg: t.Name, Body: t.Body}, 173 Arg: t.Val, 174 } 175 return buildTerm(desugared, net, vars, level, depth, varNames) 176 177 default: 178 panic("Unknown term type") 179 } 180} 181 182// FromDeltaNet reconstructs a lambda term from the network. 183// varNames maps Var node IDs to their original variable names. 184func FromDeltaNet(net *deltanet.Network, rootNode deltanet.Node, rootPort int, varNames map[uint64]string) Term { 185 // Debug 186 // fmt.Printf("FromDeltaNet: Root %v Port %d\n", rootNode.Type(), rootPort) 187 188 // We traverse from the root. 189 // We need to track visited nodes to handle loops (though lambda terms shouldn't have loops unless we have recursion combinators). 190 // But we also need to track bound variables. 191 192 // Map from (NodeID, Port) to Variable Name for bound variables. 193 // When we enter Abs at 0, we assign a name to Abs.2. 194 195 bindings := make(map[uint64]string) // Key: Node ID of the binder (Fan), Value: Name 196 197 // We need a name generator 198 nameGen := 0 199 nextName := func() string { 200 name := fmt.Sprintf("x%d", nameGen) 201 nameGen++ 202 return name 203 } 204 205 visited := make(map[string]bool) 206 return readTerm(net, rootNode, rootPort, bindings, nextName, visited, varNames) 207} 208 209func readTerm(net *deltanet.Network, node deltanet.Node, port int, bindings map[uint64]string, nextName func() string, visited map[string]bool, varNames map[uint64]string) Term { 210 if node == nil { 211 return Var{Name: "<nil>"} 212 } 213 214 // Handle Phase 2 rotation 215 // Phys 0 -> Log 1 216 // Phys 1 -> Log 2 217 // Phys 2 -> Log 0 218 logicalPort := port 219 if net.Phase() == 2 && node.Type() == deltanet.NodeTypeFan { 220 switch port { 221 case 0: 222 logicalPort = 1 223 case 1: 224 logicalPort = 2 225 case 2: 226 logicalPort = 0 227 } 228 } 229 230 key := fmt.Sprintf("%d:%d", node.ID(), port) 231 if visited[key] { 232 if deltaDebug { 233 fmt.Printf("readTerm: detected revisit %s -> returning <loop>\n", key) 234 } 235 return Var{Name: "<loop>"} 236 } 237 visited[key] = true 238 defer func() { delete(visited, key) }() 239 240 if deltaDebug { 241 fmt.Printf("readTerm: nodeType=%v id=%d port=%d phase=%d\n", node.Type(), node.ID(), port, net.Phase()) 242 } 243 if deltaDebug && node.Type() == deltanet.NodeTypeFan { 244 // Print where each physical port links to (nodeID:port) 245 for i := 0; i < 3; i++ { 246 n, p := net.GetLink(node, i) 247 if n != nil { 248 fmt.Printf(" Fan link[%d] -> %v id=%d port=%d\n", i, n.Type(), n.ID(), p) 249 } else { 250 fmt.Printf(" Fan link[%d] -> <nil>\n", i) 251 } 252 } 253 } 254 255 switch node.Type() { 256 case deltanet.NodeTypeFan: 257 if logicalPort == 0 { 258 // Entering Abs at Result -> Abs 259 // Or Entering App at Fun -> App (Wait, App Result is 1) 260 261 // If we enter at Logical 0: 262 // For Abs: 0 is Result. We are reading the Abs term. 263 // For App: 0 is Fun. We are reading the Function part of an App? 264 // No, if we are reading a term, we enter at its "Output" port. 265 // Abs Output is 0. 266 // App Output is 1. 267 268 // So if LogicalPort == 0, it MUST be Abs. 269 name := nextName() 270 bindings[node.ID()] = name 271 272 // Body is at Logical 1 273 // We need to find the PHYSICAL port for Logical 1. 274 // If Phase 2: Log 1 -> Phys 0. 275 // If Phase 1: Log 1 -> Phys 1. 276 bodyPortIdx := 1 277 if net.Phase() == 2 { 278 bodyPortIdx = 0 279 } 280 281 body := readTerm(net, getLinkNode(net, node, bodyPortIdx), getLinkPort(net, node, bodyPortIdx), bindings, nextName, visited, varNames) 282 return Abs{Arg: name, Body: body} 283 284 } else if logicalPort == 1 { 285 // Entering at Logical 1. 286 // Abs: 1 is Body. (We are reading body? No, we enter at Output). 287 // App: 1 is Result. We are reading the App term. 288 289 // So if LogicalPort == 1, it MUST be App. 290 291 // Fun is at Logical 0. 292 // Arg is at Logical 2. 293 294 funPortIdx := 0 295 argPortIdx := 2 296 if net.Phase() == 2 { 297 funPortIdx = 2 298 argPortIdx = 1 299 } 300 301 funNode := getLinkNode(net, node, funPortIdx) 302 funP := getLinkPort(net, node, funPortIdx) 303 argNode := getLinkNode(net, node, argPortIdx) 304 argP := getLinkPort(net, node, argPortIdx) 305 if deltaDebug { 306 fmt.Printf(" App at Fan id=%d funLink=(%v id=%d port=%d) argLink=(%v id=%d port=%d)\n", node.ID(), funNode.Type(), funNode.ID(), funP, argNode.Type(), argNode.ID(), argP) 307 } 308 fun := readTerm(net, funNode, funP, bindings, nextName, visited, varNames) 309 arg := readTerm(net, argNode, argP, bindings, nextName, visited, varNames) 310 return App{Fun: fun, Arg: arg} 311 312 } else { 313 // Entering at Logical 2. 314 // Abs: 2 is Var. 315 // App: 2 is Arg. 316 // This means we are traversing UP a variable binding or argument? 317 // Should not happen when reading a term from root. 318 // Unless we are tracing a variable. 319 if name, ok := bindings[node.ID()]; ok { 320 return Var{Name: name} 321 } 322 return Var{Name: "<binding>"} 323 } 324 325 case deltanet.NodeTypeReplicator: 326 // We entered a Replicator. 327 // If we entered at Aux port (>= 1), we are reading a variable usage. 328 // We need to trace back to the source (Port 0). 329 if deltaDebug { 330 fmt.Printf(" Replicator(id=%d) Deltas=%v Level=%d entered at port=%d\n", node.ID(), node.Deltas(), node.Level(), port) 331 } 332 333 if port > 0 { 334 sourceNode := getLinkNode(net, node, 0) 335 sourcePort := getLinkPort(net, node, 0) 336 337 // Trace back until we hit a Fan.2 (Binder) or Var (Free) 338 // If the source is a Fan (Abs/App), traceVariable will delegate 339 // to readTerm to reconstruct the full subterm. 340 return traceVariable(net, sourceNode, sourcePort, bindings, nextName, visited, varNames) 341 } else { 342 // Entered at 0? 343 // Reading the value being shared? 344 // This happens if we have `(\x. x) M`. `M` connects to `Rep.0`. 345 // If we read `M`, we traverse `M`. 346 // But here we are reading the *term* that `Rep` is part of. 347 // If `Rep` is part of the term structure (e.g. sharing a subterm), 348 // then `Rep.0` points to the subterm. 349 // So we just recurse on `Rep.0`? 350 // No, `Rep.0` is the *input* to the Replicator. 351 // If we enter at 0, we are going *upstream*? 352 // Wait, `Rep` directionality: 353 // 0 is Input. 1..N are Outputs. 354 // If we enter at 0, we are looking at the Output of `Rep`? No. 355 // If we enter at 0, we came from the Input side. 356 // This means we are traversing *into* the Replicator from the source. 357 // This implies the Replicator is sharing the *result* of something. 358 // e.g. `let x = M in ...`. `M` connects to `Rep.0`. 359 // If we are reading `M`, we don't hit `Rep`. 360 // If we are reading the body, we hit `Rep` at aux ports. 361 // So when do we hit `Rep` at 0? 362 // Only if we are traversing `M` and `M` *is* the Replicator? 363 // No, `Rep` is not a term constructor like Abs/App. It's a structural node. 364 // If `M` is `x`, and `x` is shared, then `M` *is* a wire to `Rep`. 365 // But `Rep` is connected to `x`'s binder. 366 // So `M` connects to `Rep` aux port. 367 // So we enter at aux. 368 369 // What if `M` is `\y. y` and it is shared? 370 // `Abs` (M) connects to `Rep.0`. 371 // `Rep` aux ports connect to usages. 372 // If we read `M` (e.g. if we are reading the `let` value), we hit `Rep.0`. 373 // So we should just read what `Rep` is connected to? 374 // No, `Rep` *is* the sharing mechanism. 375 // If we are reading the term `M`, and `M` is shared, we see `Abs`. 376 // We don't see `Rep` unless we are reading the *usages*. 377 // Wait. `Abs.0` connects to `Rep.0`. 378 // If we read `M`, we start at `Abs.0`. 379 // We don't start at `Rep`. 380 // Unless `M` is *defined* as `Rep`? No. 381 382 // Ah, `FromDeltaNet` takes `rootNode, rootPort`. 383 // This is the "output" of the term. 384 // If the term is `\x. x`, output is `Abs.0`. 385 // If the term is `x`, output is `Rep` aux port (or `Abs.2`). 386 // If the term is `M N`, output is `App.1`. 387 388 // So we should never enter `Rep` at 0 during normal read-back of a term, 389 // unless the term *itself* is being shared and we are reading the *source*? 390 // But `rootNode` is the *result* of the reduction. 391 // If the result is shared, then `rootNode` might be `Rep`? 392 // If the result is `x` (free var), and it's shared? 393 // `Var` -> `Rep.0`. `Rep.1` -> Output. 394 // So Output is `Rep.1`. We enter at 1. 395 396 // So entering at 0 should be rare/impossible for "Result". 397 if deltaDebug { 398 fmt.Printf(" Replicator(id=%d) entered at 0, returning <rep-0>\n", node.ID()) 399 } 400 return Var{Name: "<rep-0>"} 401 } 402 403 case deltanet.NodeTypeVar: 404 // Free variable or wire 405 // If it's a named var, return it. 406 // But `Var` nodes don't store names in `deltanet` package? 407 // `deltanet.NewVar()` creates `NodeTypeVar`. 408 // It doesn't store a name. 409 // We lost the name! 410 // We need to store names for free variables if we want to read them back. 411 // But `deltanet` doesn't support labels. 412 // I can't modify `deltanet` package (user reverted). 413 // So I can't store names in `Var` nodes. 414 // I'll return "<free>" or generate a name. 415 if deltaDebug { 416 fmt.Printf(" Var node encountered (id=%d) -> <free>\n", node.ID()) 417 } 418 return Var{Name: "<free>"} 419 420 case deltanet.NodeTypeEraser: 421 return Var{Name: "<erased>"} 422 423 default: 424 return Var{Name: fmt.Sprintf("<? %v>", node.Type())} 425 } 426} 427 428func traceVariable(net *deltanet.Network, node deltanet.Node, port int, bindings map[uint64]string, nextName func() string, visited map[string]bool, varNames map[uint64]string) Term { 429 // Follow wires up through Replicators (entering at 0, leaving at 0?) 430 // No, `Rep.0` connects to Source. 431 // So if we are at `Rep`, we go to `Rep.0`'s link. 432 433 currNode := node 434 currPort := port 435 436 for { 437 if currNode == nil { 438 return Var{Name: "<nil-trace>"} 439 } 440 441 if deltaDebug { 442 fmt.Printf("traceVariable: at nodeType=%v id=%d port=%d phase=%d\n", currNode.Type(), currNode.ID(), currPort, net.Phase()) 443 } 444 445 switch currNode.Type() { 446 case deltanet.NodeTypeFan: 447 // Handle Rotation 448 logicalPort := currPort 449 if net.Phase() == 2 { 450 switch currPort { 451 case 0: 452 logicalPort = 1 453 case 1: 454 logicalPort = 2 455 case 2: 456 logicalPort = 0 457 } 458 } 459 460 // Hit a Fan. 461 // If Logical 2, it's a binder (Abs Var). 462 if logicalPort == 2 { 463 if name, ok := bindings[currNode.ID()]; ok { 464 return Var{Name: name} 465 } 466 return Var{Name: "<unbound-fan>"} 467 } 468 // If Logical 0 or 1, reconstruct the full term (Abs or App) 469 // readTerm handles rotation internally based on port passed. 470 return readTerm(net, currNode, currPort, bindings, nextName, visited, varNames) 471 472 case deltanet.NodeTypeReplicator: 473 // Continue trace from Rep.0 474 if currPort == 0 { 475 return Var{Name: "<rep-trace-0>"} 476 } 477 if deltaDebug { 478 fmt.Printf(" traceVariable: traversing Replicator id=%d -> follow .0\n", currNode.ID()) 479 } 480 nextNode, nextPort := net.GetLink(currNode, 0) 481 currNode = nextNode 482 currPort = nextPort 483 484 case deltanet.NodeTypeVar: 485 if deltaDebug { 486 fmt.Printf(" traceVariable: hit Var id=%d, varNames has %d entries\n", currNode.ID(), len(varNames)) 487 for id, name := range varNames { 488 fmt.Printf(" varNames[%d] = %s\n", id, name) 489 } 490 } 491 if name, ok := varNames[currNode.ID()]; ok { 492 return Var{Name: name} 493 } 494 return Var{Name: "<free>"} 495 496 case deltanet.NodeTypeEraser: 497 return Var{Name: "<erased>"} 498 499 default: 500 return Var{Name: fmt.Sprintf("<? %v>", currNode.Type())} 501 } 502 } 503} 504 505func getLinkNode(net *deltanet.Network, node deltanet.Node, port int) deltanet.Node { 506 n, _ := net.GetLink(node, port) 507 return n 508} 509 510func getLinkPort(net *deltanet.Network, node deltanet.Node, port int) int { 511 _, p := net.GetLink(node, port) 512 return p 513}