Skip to content

Commit ff2b1e1

Browse files
authored
Merge pull request rarestype#124 from rarestype/jq
[minor]: jq style decoding-free query language
2 parents 42a1391 + 61fb557 commit ff2b1e1

12 files changed

Lines changed: 1182 additions & 1 deletion

Package.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ let package: Package = .init(
66
name: "swift-json",
77
platforms: [.macOS(.v15), .iOS(.v18), .tvOS(.v18), .watchOS(.v11), .visionOS(.v2)],
88
products: [
9+
.library(name: "JavaScriptPersistence", targets: ["JavaScriptPersistence"]),
10+
.library(name: "JQ", targets: ["JQ"]),
911
.library(name: "JSON", targets: ["JSON"]),
1012
.library(name: "JSONAST", targets: ["JSONAST"]),
1113
.library(name: "JSONLegacy", targets: ["JSONLegacy"]),
12-
.library(name: "JavaScriptPersistence", targets: ["JavaScriptPersistence"]),
1314

1415
.library(name: "_JSON_SnippetsAnchor", targets: ["_JSON_SnippetsAnchor"]),
1516
],
17+
traits: [
18+
.trait(name: "_ExperimentalJQ"),
19+
],
1620
dependencies: [
1721
.package(url: "https://github.com/ordo-one/dollup", from: "1.0.1"),
1822
.package(url: "https://github.com/rarestype/gram", from: "2.0.0"),
@@ -59,6 +63,13 @@ let package: Package = .init(
5963
]
6064
),
6165

66+
.target(
67+
name: "JQ",
68+
dependencies: [
69+
.target(name: "JSONAST"),
70+
]
71+
),
72+
6273
.target(
6374
name: "JavaScriptPersistence",
6475
dependencies: [
@@ -72,6 +83,13 @@ let package: Package = .init(
7283
.target(name: "JSON"),
7384
]
7485
),
86+
.testTarget(
87+
name: "JQTests",
88+
dependencies: [
89+
.target(name: "JSON"),
90+
.target(name: "JQ"),
91+
]
92+
),
7593

7694

7795
.target(

Sources/JQ/JSON.Array (ext).swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
public import JSONAST
2+
3+
extension JSON.Array {
4+
@inlinable init(value: JSON.Node, at index: Int) {
5+
var values: [JSON.Node]
6+
if index > 0 {
7+
values = []
8+
values.reserveCapacity(1 + index)
9+
values += repeatElement(.null, count: index)
10+
values.append(value)
11+
} else {
12+
values = [value]
13+
}
14+
self.init(values)
15+
}
16+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
public import JSONAST
2+
3+
extension JSON {
4+
@frozen public struct ArrayAccessor: ~Copyable {
5+
@usableFromInline let crumb: PathComponent?
6+
@usableFromInline var state: NodeAccess<Array>
7+
8+
@inlinable init(crumb: PathComponent?, state: NodeAccess<Array>) {
9+
self.crumb = crumb
10+
self.state = state
11+
}
12+
}
13+
}
14+
extension JSON.ArrayAccessor {
15+
@inlinable static func protected(_ crumb: JSON.PathComponent?) -> Self {
16+
.init(crumb: crumb, state: .protected)
17+
}
18+
19+
@inlinable static func reserved(_ crumb: JSON.PathComponent?, _ index: Int) -> Self {
20+
.init(crumb: crumb, state: .reserved(index))
21+
}
22+
23+
@inlinable static var writable: Self {
24+
.init(crumb: nil, state: .writable)
25+
}
26+
27+
@inlinable static func occupied(_ value: consuming JSON.Array) -> Self {
28+
.init(crumb: nil, state: .occupied(value))
29+
}
30+
}
31+
extension JSON.ArrayAccessor {
32+
@discardableResult
33+
@inlinable public static func &? <E, T>(
34+
self: inout Self,
35+
yield: (inout JSON.Array) throws(E) -> T
36+
) throws(E) -> T? {
37+
switch self.state {
38+
case .protected:
39+
return nil
40+
case .reserved:
41+
return nil
42+
case .writable:
43+
return nil
44+
case .occupied(var array):
45+
self.state = .writable
46+
defer {
47+
self.state = .occupied(array)
48+
}
49+
return try yield(&array)
50+
}
51+
}
52+
53+
@inlinable public static func & (
54+
self: inout Self,
55+
yield: (inout JSON.Array?) throws -> ()
56+
) throws {
57+
switch self.state {
58+
case .protected:
59+
var array: JSON.Array? = nil
60+
try yield(&array)
61+
if array != nil {
62+
throw JSON.NodeAccessError.protected(self.crumb)
63+
}
64+
65+
case .reserved(let offender):
66+
var array: JSON.Array? = nil
67+
try yield(&array)
68+
if array != nil {
69+
throw JSON.NodeAccessError.reserved(self.crumb, offender)
70+
}
71+
72+
case .writable:
73+
var array: JSON.Array? = []
74+
try yield(&array)
75+
if let array: JSON.Array {
76+
self.state = .occupied(array)
77+
}
78+
79+
case .occupied(let value):
80+
var array: JSON.Array? = consume value
81+
82+
self.state = .writable
83+
defer {
84+
self.state = array.map { .occupied($0) } ?? .writable
85+
}
86+
try yield(&array)
87+
}
88+
}
89+
90+
@inlinable public static func |? <E, T>(
91+
self: borrowing Self,
92+
yield: (JSON.Node) throws(E) -> T
93+
) throws(E) -> [T]? {
94+
if case .occupied(let array) = self.state {
95+
return try array.elements.map(yield)
96+
} else {
97+
return nil
98+
}
99+
}
100+
101+
@inlinable public static func | <T>(
102+
self: borrowing Self,
103+
yield: (JSON.Node) throws -> T
104+
) throws -> [T] {
105+
switch self.state {
106+
case .protected:
107+
throw JSON.NodeAccessError.protected(self.crumb)
108+
case .reserved(let offender):
109+
throw JSON.NodeAccessError.reserved(self.crumb, offender)
110+
case .writable:
111+
return []
112+
case .occupied(let array):
113+
return try array.elements.map(yield)
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)