Skip to content

Commit d4bce64

Browse files
committed
initial commit
0 parents  commit d4bce64

File tree

6 files changed

+208
-0
lines changed

6 files changed

+208
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 objc.io
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "AttributeGraph",
8+
products: [
9+
// Products define the executables and libraries a package produces, making them visible to other packages.
10+
.library(
11+
name: "AttributeGraph",
12+
targets: ["AttributeGraph"]),
13+
],
14+
targets: [
15+
// Targets are the basic building blocks of a package, defining a module or a test suite.
16+
// Targets can depend on other targets in this package and products from dependencies.
17+
.target(
18+
name: "AttributeGraph"),
19+
.testTarget(
20+
name: "AttributeGraphTests",
21+
dependencies: ["AttributeGraph"]
22+
),
23+
]
24+
)

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Swift Talk
2+
## Attribute Graph (Part 2)
3+
4+
This is the code that accompanies Swift Talk Episode 430: [Attribute Graph (Part 2)](https://talk.objc.io/episodes/S01E430-attribute-graph-part-2)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Based on https://www.semanticscholar.org/paper/A-System-for-Efficient-and-Flexible-One-Way-in-C%2B%2B-Hudson/9609985dbef43633f4deb88c949a9776e0cd766b
2+
// https://repository.gatech.edu/server/api/core/bitstreams/3117139f-5de2-4f1f-9662-8723bae97a6d/content
3+
4+
final class AttributeGraph {
5+
var nodes: [AnyNode] = []
6+
var currentNode: AnyNode?
7+
8+
func input<A>(name: String, _ value: A) -> Node<A> {
9+
let n = Node(name: name, in: self, wrappedValue: value)
10+
nodes.append(n)
11+
return n
12+
}
13+
14+
func rule<A>(name: String, _ rule: @escaping () -> A) -> Node<A> {
15+
let n = Node(name: name, in: self, rule: rule)
16+
nodes.append(n)
17+
return n
18+
}
19+
20+
func graphViz() -> String {
21+
let nodesStr = nodes.map {
22+
"\($0.name)\($0.potentiallyDirty ? " [style=dashed]" : "")"
23+
}.joined(separator: "\n")
24+
let edges = nodes.flatMap(\.outgoingEdges).map {
25+
"\($0.from.name) -> \($0.to.name)\($0.pending ? " [style=dashed]" : "")"
26+
}.joined(separator: "\n")
27+
return """
28+
digraph {
29+
\(nodesStr)
30+
\(edges)
31+
}
32+
"""
33+
}
34+
}
35+
36+
protocol AnyNode: AnyObject {
37+
var name: String { get }
38+
var outgoingEdges: [Edge] { get }
39+
var incomingEdges: [Edge] { get set }
40+
var potentiallyDirty: Bool { get set }
41+
}
42+
43+
final class Edge {
44+
unowned var from: AnyNode
45+
unowned var to: AnyNode
46+
var pending = false
47+
48+
init(from: AnyNode, to: AnyNode) {
49+
self.from = from
50+
self.to = to
51+
}
52+
}
53+
54+
final class Node<A>: AnyNode {
55+
unowned var graph: AttributeGraph
56+
var name: String
57+
var rule: (() -> A)?
58+
var incomingEdges: [Edge] = []
59+
var outgoingEdges: [Edge] = []
60+
var potentiallyDirty: Bool = false {
61+
didSet {
62+
guard potentiallyDirty, potentiallyDirty != oldValue else { return }
63+
for e in outgoingEdges {
64+
e.to.potentiallyDirty = true
65+
}
66+
}
67+
}
68+
69+
private var _cachedValue: A?
70+
71+
var wrappedValue: A {
72+
get {
73+
recomputeIfNeeded()
74+
return _cachedValue!
75+
}
76+
set {
77+
assert(rule == nil)
78+
_cachedValue = newValue
79+
for e in outgoingEdges {
80+
e.pending = true
81+
e.to.potentiallyDirty = true
82+
}
83+
}
84+
}
85+
86+
func recomputeIfNeeded() {
87+
if let c = graph.currentNode {
88+
let edge = Edge(from: self, to: c)
89+
outgoingEdges.append(edge)
90+
c.incomingEdges.append(edge)
91+
}
92+
if _cachedValue == nil, let rule {
93+
let previousNode = graph.currentNode
94+
defer { graph.currentNode = previousNode }
95+
graph.currentNode = self
96+
_cachedValue = rule()
97+
}
98+
}
99+
100+
init(name: String, in graph: AttributeGraph, wrappedValue: A) {
101+
self.name = name
102+
self.graph = graph
103+
self._cachedValue = wrappedValue
104+
}
105+
106+
init(name: String, in graph: AttributeGraph, rule: @escaping () -> A) {
107+
self.name = name
108+
self.graph = graph
109+
self.rule = rule
110+
}
111+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Testing
2+
@testable import AttributeGraph
3+
4+
@Test func example() async throws {
5+
let graph = AttributeGraph()
6+
let a = graph.input(name: "A", 10)
7+
let b = graph.input(name: "B", 20)
8+
let c = graph.rule(name: "C") { a.wrappedValue + b.wrappedValue }
9+
let d = graph.rule(name: "D") { c.wrappedValue * 2 }
10+
#expect(d.wrappedValue == 60)
11+
12+
let str = """
13+
digraph {
14+
A
15+
B
16+
C
17+
D
18+
A -> C
19+
B -> C
20+
C -> D
21+
}
22+
"""
23+
#expect(str == graph.graphViz())
24+
25+
a.wrappedValue = 40
26+
#expect(d.wrappedValue == 120)
27+
28+
let str2 = """
29+
digraph {
30+
A
31+
B
32+
C [style=dashed]
33+
D [style=dashed]
34+
A -> C [style=dashed]
35+
B -> C
36+
C -> D
37+
}
38+
"""
39+
#expect(str2 == graph.graphViz())
40+
}

0 commit comments

Comments
 (0)