@swiftbrowser/swift 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Package.swift +23 -0
- package/README.md +12 -2
- package/Sources/Charts/Charts.swift +65 -0
- package/Sources/FoundationBridge/FoundationBridge.swift +77 -0
- package/Sources/FoundationBridge/Locale.swift +86 -0
- package/Sources/FoundationBridge/Timer.swift +48 -0
- package/Sources/FoundationBridge/UITextChecker.swift +30 -0
- package/Sources/FoundationLite/Common.swift +3 -0
- package/Sources/FoundationLite/FoundationLite.swift +171 -0
- package/Sources/FoundationLite/Interaction.swift +146 -0
- package/Sources/FoundationLite/Timer.swift +48 -0
- package/Sources/SwiftUI/API/Accessibility.swift +132 -0
- package/Sources/SwiftUI/API/Alert.swift +221 -0
- package/Sources/SwiftUI/API/Animation.swift +253 -0
- package/Sources/SwiftUI/API/App.swift +57 -0
- package/Sources/SwiftUI/API/Color.swift +161 -0
- package/Sources/SwiftUI/API/Controls.swift +268 -0
- package/Sources/SwiftUI/API/CustomLayout.swift +403 -0
- package/Sources/SwiftUI/API/Decorations.swift +92 -0
- package/Sources/SwiftUI/API/EditMode.swift +269 -0
- package/Sources/SwiftUI/API/Effects.swift +290 -0
- package/Sources/SwiftUI/API/Environment.swift +355 -0
- package/Sources/SwiftUI/API/Focus.swift +129 -0
- package/Sources/SwiftUI/API/Font.swift +131 -0
- package/Sources/SwiftUI/API/Gauge.swift +289 -0
- package/Sources/SwiftUI/API/Geometry.swift +89 -0
- package/Sources/SwiftUI/API/GeometryReader.swift +124 -0
- package/Sources/SwiftUI/API/Gestures.swift +342 -0
- package/Sources/SwiftUI/API/Grid.swift +188 -0
- package/Sources/SwiftUI/API/LabelStyle.swift +117 -0
- package/Sources/SwiftUI/API/Layout.swift +165 -0
- package/Sources/SwiftUI/API/Lists.swift +276 -0
- package/Sources/SwiftUI/API/Menu.swift +51 -0
- package/Sources/SwiftUI/API/Modifiers.swift +243 -0
- package/Sources/SwiftUI/API/Navigation.swift +371 -0
- package/Sources/SwiftUI/API/ObservableObject.swift +279 -0
- package/Sources/SwiftUI/API/OnChange.swift +48 -0
- package/Sources/SwiftUI/API/Phase8Controls.swift +394 -0
- package/Sources/SwiftUI/API/Picker.swift +176 -0
- package/Sources/SwiftUI/API/Presentation.swift +109 -0
- package/Sources/SwiftUI/API/Primitives.swift +390 -0
- package/Sources/SwiftUI/API/Publishers.swift +107 -0
- package/Sources/SwiftUI/API/RowActions.swift +83 -0
- package/Sources/SwiftUI/API/ScrollTargets.swift +104 -0
- package/Sources/SwiftUI/API/ShapeStyle.swift +311 -0
- package/Sources/SwiftUI/API/State.swift +127 -0
- package/Sources/SwiftUI/API/StrokeStyle.swift +30 -0
- package/Sources/SwiftUI/API/Tabs.swift +119 -0
- package/Sources/SwiftUI/API/Tasks.swift +35 -0
- package/Sources/SwiftUI/API/Timeline.swift +212 -0
- package/Sources/SwiftUI/API/Toolbar.swift +215 -0
- package/Sources/SwiftUI/API/View.swift +207 -0
- package/Sources/SwiftUI/API/ViewModifier.swift +40 -0
- package/Sources/SwiftUI/API/VisualEffect.swift +82 -0
- package/Sources/SwiftUI/Charts/Axis.swift +319 -0
- package/Sources/SwiftUI/Charts/Chart.swift +646 -0
- package/Sources/SwiftUI/Charts/ChartContent.swift +376 -0
- package/Sources/SwiftUI/Charts/Marks.swift +204 -0
- package/Sources/SwiftUI/Charts/Plottable.swift +99 -0
- package/Sources/SwiftUI/Charts/Scales.swift +326 -0
- package/Sources/SwiftUI/Runtime/Executor.swift +83 -0
- package/Sources/SwiftUI/Runtime/Exports.swift +65 -0
- package/Sources/SwiftUI/Runtime/JSON.swift +71 -0
- package/Sources/SwiftUI/Runtime/JSONParser.swift +443 -0
- package/Sources/SwiftUI/Runtime/Ops.swift +106 -0
- package/Sources/SwiftUI/Runtime/Reconciler.swift +210 -0
- package/Sources/SwiftUI/Runtime/Runtime.swift +1367 -0
- package/Sources/_FoundationCommon/Bundle.swift +82 -0
- package/Sources/_FoundationCommon/UserDefaults.swift +170 -0
- package/Sources/_FoundationCommon/_FoundationCommon.swift +10 -0
- package/index.d.ts +2 -0
- package/index.mjs +16 -0
- package/package.json +28 -4
- package/sync-sources.mjs +19 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/// RFC 8259 parser for `_JSON`. Like the serializer in JSON.swift it uses no
|
|
2
|
+
/// Foundation (see Geometry.swift): it walks the UTF-8 bytes of the input with
|
|
3
|
+
/// an index and builds numbers by hand.
|
|
4
|
+
///
|
|
5
|
+
/// Rejected on purpose: trailing garbage, trailing commas, leading zeros
|
|
6
|
+
/// (`01`), control characters inside strings, unknown escapes, lone UTF-16
|
|
7
|
+
/// surrogates, unterminated input, and nesting deeper than 512 levels (a
|
|
8
|
+
/// recursive parser would otherwise overflow the stack on hostile input).
|
|
9
|
+
|
|
10
|
+
/// Error thrown by `_JSON.parse`. `offset` is the UTF-8 byte offset into the
|
|
11
|
+
/// input at which the problem was detected.
|
|
12
|
+
public struct _JSONParseError: Error, CustomStringConvertible, Sendable {
|
|
13
|
+
public let message: String
|
|
14
|
+
public let offset: Int
|
|
15
|
+
|
|
16
|
+
public init(message: String, offset: Int) {
|
|
17
|
+
self.message = message
|
|
18
|
+
self.offset = offset
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public var description: String { "\(message) at byte offset \(offset)" }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
extension _JSON {
|
|
25
|
+
/// Parses one JSON text. Any number of whitespace characters may surround
|
|
26
|
+
/// the value; anything else before or after it is an error.
|
|
27
|
+
public static func parse(_ text: String) throws(_JSONParseError) -> _JSON {
|
|
28
|
+
var parser = _JSONParser(bytes: Array(text.utf8))
|
|
29
|
+
parser.skipWhitespace()
|
|
30
|
+
let value = try parser.parseValue()
|
|
31
|
+
parser.skipWhitespace()
|
|
32
|
+
if parser.index < parser.bytes.count {
|
|
33
|
+
throw parser.error("unexpected characters after the JSON value")
|
|
34
|
+
}
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private struct _JSONParser {
|
|
40
|
+
/// Nesting limit for arrays and objects.
|
|
41
|
+
static let maxDepth = 512
|
|
42
|
+
|
|
43
|
+
let bytes: [UInt8]
|
|
44
|
+
var index = 0
|
|
45
|
+
private var depth = 0
|
|
46
|
+
|
|
47
|
+
init(bytes: [UInt8]) {
|
|
48
|
+
self.bytes = bytes
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// MARK: Helpers
|
|
52
|
+
|
|
53
|
+
private var peek: UInt8? { index < bytes.count ? bytes[index] : nil }
|
|
54
|
+
|
|
55
|
+
func error(_ message: String) -> _JSONParseError {
|
|
56
|
+
error(message, at: index)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
func error(_ message: String, at offset: Int) -> _JSONParseError {
|
|
60
|
+
_JSONParseError(message: message, offset: offset)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private func endOfInput() -> _JSONParseError {
|
|
64
|
+
error("unexpected end of input", at: bytes.count)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
mutating func skipWhitespace() {
|
|
68
|
+
while index < bytes.count {
|
|
69
|
+
switch bytes[index] {
|
|
70
|
+
case 0x20, 0x09, 0x0A, 0x0D: index += 1 // space, tab, LF, CR
|
|
71
|
+
default: return
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private static func isDigit(_ byte: UInt8) -> Bool {
|
|
77
|
+
byte >= 0x30 && byte <= 0x39 // '0'...'9'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// MARK: Values
|
|
81
|
+
|
|
82
|
+
mutating func parseValue() throws(_JSONParseError) -> _JSON {
|
|
83
|
+
guard let byte = peek else { throw endOfInput() }
|
|
84
|
+
switch byte {
|
|
85
|
+
case 0x7B: // {
|
|
86
|
+
return try parseObject()
|
|
87
|
+
case 0x5B: // [
|
|
88
|
+
return try parseArray()
|
|
89
|
+
case 0x22: // "
|
|
90
|
+
return .string(try parseString())
|
|
91
|
+
case 0x74: // t
|
|
92
|
+
try expectLiteral("true")
|
|
93
|
+
return .bool(true)
|
|
94
|
+
case 0x66: // f
|
|
95
|
+
try expectLiteral("false")
|
|
96
|
+
return .bool(false)
|
|
97
|
+
case 0x6E: // n
|
|
98
|
+
try expectLiteral("null")
|
|
99
|
+
return .null
|
|
100
|
+
case 0x2D, 0x30...0x39: // - or digit
|
|
101
|
+
return .number(try parseNumber())
|
|
102
|
+
default:
|
|
103
|
+
throw error("unexpected character")
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private mutating func expectLiteral(_ literal: StaticString) throws(_JSONParseError) {
|
|
108
|
+
let start = index
|
|
109
|
+
var matched = true
|
|
110
|
+
literal.withUTF8Buffer { expected in
|
|
111
|
+
if index + expected.count > bytes.count {
|
|
112
|
+
matched = false
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
for (offset, byte) in expected.enumerated() where bytes[index + offset] != byte {
|
|
116
|
+
matched = false
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
index += expected.count
|
|
120
|
+
}
|
|
121
|
+
if !matched {
|
|
122
|
+
throw error("invalid literal; expected '\(literal)'", at: start)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// MARK: Containers
|
|
127
|
+
|
|
128
|
+
private mutating func enterContainer() throws(_JSONParseError) {
|
|
129
|
+
depth += 1
|
|
130
|
+
if depth > Self.maxDepth {
|
|
131
|
+
throw error("nesting deeper than \(Self.maxDepth) levels")
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private mutating func parseArray() throws(_JSONParseError) -> _JSON {
|
|
136
|
+
try enterContainer()
|
|
137
|
+
defer { depth -= 1 }
|
|
138
|
+
index += 1 // [
|
|
139
|
+
var items: [_JSON] = []
|
|
140
|
+
skipWhitespace()
|
|
141
|
+
if peek == 0x5D { // ]
|
|
142
|
+
index += 1
|
|
143
|
+
return .array(items)
|
|
144
|
+
}
|
|
145
|
+
while true {
|
|
146
|
+
skipWhitespace()
|
|
147
|
+
items.append(try parseValue())
|
|
148
|
+
skipWhitespace()
|
|
149
|
+
guard let byte = peek else { throw endOfInput() }
|
|
150
|
+
switch byte {
|
|
151
|
+
case 0x2C: // ,
|
|
152
|
+
index += 1
|
|
153
|
+
skipWhitespace()
|
|
154
|
+
if peek == 0x5D { throw error("trailing comma in array") }
|
|
155
|
+
case 0x5D: // ]
|
|
156
|
+
index += 1
|
|
157
|
+
return .array(items)
|
|
158
|
+
default:
|
|
159
|
+
throw error("expected ',' or ']' in array")
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private mutating func parseObject() throws(_JSONParseError) -> _JSON {
|
|
165
|
+
try enterContainer()
|
|
166
|
+
defer { depth -= 1 }
|
|
167
|
+
index += 1 // {
|
|
168
|
+
var members: [String: _JSON] = [:]
|
|
169
|
+
skipWhitespace()
|
|
170
|
+
if peek == 0x7D { // }
|
|
171
|
+
index += 1
|
|
172
|
+
return .object(members)
|
|
173
|
+
}
|
|
174
|
+
while true {
|
|
175
|
+
skipWhitespace()
|
|
176
|
+
guard let byte = peek else { throw endOfInput() }
|
|
177
|
+
guard byte == 0x22 else {
|
|
178
|
+
throw error(peek == 0x7D ? "trailing comma in object" : "expected string key in object")
|
|
179
|
+
}
|
|
180
|
+
let key = try parseString()
|
|
181
|
+
skipWhitespace()
|
|
182
|
+
guard let separator = peek else { throw endOfInput() }
|
|
183
|
+
guard separator == 0x3A else { throw error("expected ':' after object key") } // :
|
|
184
|
+
index += 1
|
|
185
|
+
skipWhitespace()
|
|
186
|
+
members[key] = try parseValue()
|
|
187
|
+
skipWhitespace()
|
|
188
|
+
guard let next = peek else { throw endOfInput() }
|
|
189
|
+
switch next {
|
|
190
|
+
case 0x2C: // ,
|
|
191
|
+
index += 1
|
|
192
|
+
case 0x7D: // }
|
|
193
|
+
index += 1
|
|
194
|
+
return .object(members)
|
|
195
|
+
default:
|
|
196
|
+
throw error("expected ',' or '}' in object")
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// MARK: Strings
|
|
202
|
+
|
|
203
|
+
/// Parses a string starting at the opening quote and returns its contents.
|
|
204
|
+
private mutating func parseString() throws(_JSONParseError) -> String {
|
|
205
|
+
let start = index
|
|
206
|
+
index += 1 // opening quote
|
|
207
|
+
var buffer: [UInt8] = []
|
|
208
|
+
while true {
|
|
209
|
+
// Copy a run of ordinary bytes in one go.
|
|
210
|
+
let runStart = index
|
|
211
|
+
while index < bytes.count {
|
|
212
|
+
let byte = bytes[index]
|
|
213
|
+
if byte == 0x22 || byte == 0x5C || byte < 0x20 { break }
|
|
214
|
+
index += 1
|
|
215
|
+
}
|
|
216
|
+
guard index < bytes.count else {
|
|
217
|
+
throw error("unterminated string", at: start)
|
|
218
|
+
}
|
|
219
|
+
let byte = bytes[index]
|
|
220
|
+
if byte == 0x22 { // closing quote
|
|
221
|
+
index += 1
|
|
222
|
+
if buffer.isEmpty {
|
|
223
|
+
return String(decoding: bytes[runStart..<(index - 1)], as: UTF8.self)
|
|
224
|
+
}
|
|
225
|
+
buffer.append(contentsOf: bytes[runStart..<(index - 1)])
|
|
226
|
+
return String(decoding: buffer, as: UTF8.self)
|
|
227
|
+
}
|
|
228
|
+
buffer.append(contentsOf: bytes[runStart..<index])
|
|
229
|
+
if byte == 0x5C { // backslash
|
|
230
|
+
try parseEscape(into: &buffer)
|
|
231
|
+
} else {
|
|
232
|
+
throw error("control character in string must be escaped")
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// Parses one escape sequence starting at the backslash.
|
|
238
|
+
private mutating func parseEscape(into buffer: inout [UInt8]) throws(_JSONParseError) {
|
|
239
|
+
let start = index
|
|
240
|
+
index += 1 // backslash
|
|
241
|
+
guard let byte = peek else { throw endOfInput() }
|
|
242
|
+
index += 1
|
|
243
|
+
switch byte {
|
|
244
|
+
case 0x22: buffer.append(0x22) // \"
|
|
245
|
+
case 0x5C: buffer.append(0x5C) // \\
|
|
246
|
+
case 0x2F: buffer.append(0x2F) // \/
|
|
247
|
+
case 0x62: buffer.append(0x08) // \b
|
|
248
|
+
case 0x66: buffer.append(0x0C) // \f
|
|
249
|
+
case 0x6E: buffer.append(0x0A) // \n
|
|
250
|
+
case 0x72: buffer.append(0x0D) // \r
|
|
251
|
+
case 0x74: buffer.append(0x09) // \t
|
|
252
|
+
case 0x75: // \u
|
|
253
|
+
var scalar = try parseHex4()
|
|
254
|
+
if scalar >= 0xD800 && scalar <= 0xDBFF {
|
|
255
|
+
// High surrogate: a low surrogate escape must follow.
|
|
256
|
+
guard index + 1 < bytes.count, bytes[index] == 0x5C, bytes[index + 1] == 0x75 else {
|
|
257
|
+
throw error("high surrogate without a following low surrogate", at: start)
|
|
258
|
+
}
|
|
259
|
+
index += 2
|
|
260
|
+
let low = try parseHex4()
|
|
261
|
+
guard low >= 0xDC00 && low <= 0xDFFF else {
|
|
262
|
+
throw error("invalid low surrogate in escape", at: start)
|
|
263
|
+
}
|
|
264
|
+
scalar = 0x10000 + ((scalar - 0xD800) << 10) + (low - 0xDC00)
|
|
265
|
+
} else if scalar >= 0xDC00 && scalar <= 0xDFFF {
|
|
266
|
+
throw error("lone low surrogate in escape", at: start)
|
|
267
|
+
}
|
|
268
|
+
Self.appendUTF8(scalar, to: &buffer)
|
|
269
|
+
default:
|
|
270
|
+
throw error("invalid escape sequence", at: start)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Reads exactly four hex digits.
|
|
275
|
+
private mutating func parseHex4() throws(_JSONParseError) -> UInt32 {
|
|
276
|
+
guard index + 4 <= bytes.count else { throw endOfInput() }
|
|
277
|
+
var value: UInt32 = 0
|
|
278
|
+
for _ in 0..<4 {
|
|
279
|
+
let byte = bytes[index]
|
|
280
|
+
let digit: UInt32
|
|
281
|
+
switch byte {
|
|
282
|
+
case 0x30...0x39: digit = UInt32(byte - 0x30)
|
|
283
|
+
case 0x41...0x46: digit = UInt32(byte - 0x41 + 10)
|
|
284
|
+
case 0x61...0x66: digit = UInt32(byte - 0x61 + 10)
|
|
285
|
+
default: throw error("invalid hex digit in \\u escape")
|
|
286
|
+
}
|
|
287
|
+
value = value << 4 | digit
|
|
288
|
+
index += 1
|
|
289
|
+
}
|
|
290
|
+
return value
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private static func appendUTF8(_ scalar: UInt32, to buffer: inout [UInt8]) {
|
|
294
|
+
if scalar < 0x80 {
|
|
295
|
+
buffer.append(UInt8(scalar))
|
|
296
|
+
} else if scalar < 0x800 {
|
|
297
|
+
buffer.append(0xC0 | UInt8(scalar >> 6))
|
|
298
|
+
buffer.append(0x80 | UInt8(scalar & 0x3F))
|
|
299
|
+
} else if scalar < 0x10000 {
|
|
300
|
+
buffer.append(0xE0 | UInt8(scalar >> 12))
|
|
301
|
+
buffer.append(0x80 | UInt8((scalar >> 6) & 0x3F))
|
|
302
|
+
buffer.append(0x80 | UInt8(scalar & 0x3F))
|
|
303
|
+
} else {
|
|
304
|
+
buffer.append(0xF0 | UInt8(scalar >> 18))
|
|
305
|
+
buffer.append(0x80 | UInt8((scalar >> 12) & 0x3F))
|
|
306
|
+
buffer.append(0x80 | UInt8((scalar >> 6) & 0x3F))
|
|
307
|
+
buffer.append(0x80 | UInt8(scalar & 0x3F))
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// MARK: Numbers
|
|
312
|
+
|
|
313
|
+
/// Powers of ten as correctly rounded literals. Entries 1e0...1e22 are
|
|
314
|
+
/// exactly representable, so for every realistic number (at most 15-16
|
|
315
|
+
/// significant digits, a modest exponent) the single multiplication or
|
|
316
|
+
/// division below yields the correctly rounded Double. Beyond that the
|
|
317
|
+
/// result is within an ulp, which is accurate enough for this use.
|
|
318
|
+
private static let powersOfTen: [Double] = [
|
|
319
|
+
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10,
|
|
320
|
+
1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21,
|
|
321
|
+
1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32,
|
|
322
|
+
1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39, 1e40, 1e41, 1e42, 1e43,
|
|
323
|
+
1e44, 1e45, 1e46, 1e47, 1e48, 1e49, 1e50, 1e51, 1e52, 1e53, 1e54,
|
|
324
|
+
1e55, 1e56, 1e57, 1e58, 1e59, 1e60, 1e61, 1e62, 1e63, 1e64, 1e65,
|
|
325
|
+
1e66, 1e67, 1e68, 1e69, 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76,
|
|
326
|
+
1e77, 1e78, 1e79, 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87,
|
|
327
|
+
1e88, 1e89, 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98,
|
|
328
|
+
1e99, 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
|
|
329
|
+
1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119, 1e120,
|
|
330
|
+
1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129, 1e130, 1e131,
|
|
331
|
+
1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139, 1e140, 1e141, 1e142,
|
|
332
|
+
1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149, 1e150, 1e151, 1e152, 1e153,
|
|
333
|
+
1e154, 1e155, 1e156, 1e157, 1e158, 1e159, 1e160, 1e161, 1e162, 1e163, 1e164,
|
|
334
|
+
1e165, 1e166, 1e167, 1e168, 1e169, 1e170, 1e171, 1e172, 1e173, 1e174, 1e175,
|
|
335
|
+
1e176, 1e177, 1e178, 1e179, 1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186,
|
|
336
|
+
1e187, 1e188, 1e189, 1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, 1e197,
|
|
337
|
+
1e198, 1e199, 1e200, 1e201, 1e202, 1e203, 1e204, 1e205, 1e206, 1e207, 1e208,
|
|
338
|
+
1e209, 1e210, 1e211, 1e212, 1e213, 1e214, 1e215, 1e216, 1e217, 1e218, 1e219,
|
|
339
|
+
1e220, 1e221, 1e222, 1e223, 1e224, 1e225, 1e226, 1e227, 1e228, 1e229, 1e230,
|
|
340
|
+
1e231, 1e232, 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239, 1e240, 1e241,
|
|
341
|
+
1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249, 1e250, 1e251, 1e252,
|
|
342
|
+
1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259, 1e260, 1e261, 1e262, 1e263,
|
|
343
|
+
1e264, 1e265, 1e266, 1e267, 1e268, 1e269, 1e270, 1e271, 1e272, 1e273, 1e274,
|
|
344
|
+
1e275, 1e276, 1e277, 1e278, 1e279, 1e280, 1e281, 1e282, 1e283, 1e284, 1e285,
|
|
345
|
+
1e286, 1e287, 1e288, 1e289, 1e290, 1e291, 1e292, 1e293, 1e294, 1e295, 1e296,
|
|
346
|
+
1e297, 1e298, 1e299, 1e300, 1e301, 1e302, 1e303, 1e304, 1e305, 1e306, 1e307,
|
|
347
|
+
1e308,
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
/// Parses `-?int frac? exp?` into a Double. Up to 19 significant digits
|
|
351
|
+
/// are accumulated exactly in a UInt64; further digits only shift the
|
|
352
|
+
/// decimal exponent. The exponent is then applied with a single
|
|
353
|
+
/// multiplication or division by a power of ten from the table above.
|
|
354
|
+
private mutating func parseNumber() throws(_JSONParseError) -> Double {
|
|
355
|
+
let start = index
|
|
356
|
+
var negative = false
|
|
357
|
+
if peek == 0x2D { // -
|
|
358
|
+
negative = true
|
|
359
|
+
index += 1
|
|
360
|
+
}
|
|
361
|
+
guard let first = peek else { throw endOfInput() }
|
|
362
|
+
guard Self.isDigit(first) else { throw error("expected a digit") }
|
|
363
|
+
|
|
364
|
+
var mantissa: UInt64 = 0
|
|
365
|
+
var exponent = 0 // decimal exponent to apply to the mantissa
|
|
366
|
+
let mantissaLimit: UInt64 = 1_000_000_000_000_000_000 // 1e18; below it, *10+9 fits
|
|
367
|
+
|
|
368
|
+
// Integer part.
|
|
369
|
+
if first == 0x30 {
|
|
370
|
+
index += 1
|
|
371
|
+
if let next = peek, Self.isDigit(next) {
|
|
372
|
+
throw error("leading zeros are not allowed", at: start)
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
while let byte = peek, Self.isDigit(byte) {
|
|
376
|
+
if mantissa < mantissaLimit {
|
|
377
|
+
mantissa = mantissa * 10 + UInt64(byte - 0x30)
|
|
378
|
+
} else {
|
|
379
|
+
exponent += 1
|
|
380
|
+
}
|
|
381
|
+
index += 1
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Fraction.
|
|
386
|
+
if peek == 0x2E { // .
|
|
387
|
+
index += 1
|
|
388
|
+
guard let byte = peek, Self.isDigit(byte) else {
|
|
389
|
+
throw error("expected a digit after the decimal point")
|
|
390
|
+
}
|
|
391
|
+
while let byte = peek, Self.isDigit(byte) {
|
|
392
|
+
if mantissa < mantissaLimit {
|
|
393
|
+
mantissa = mantissa * 10 + UInt64(byte - 0x30)
|
|
394
|
+
exponent -= 1
|
|
395
|
+
}
|
|
396
|
+
index += 1
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Exponent.
|
|
401
|
+
if let byte = peek, byte == 0x65 || byte == 0x45 { // e or E
|
|
402
|
+
index += 1
|
|
403
|
+
var exponentNegative = false
|
|
404
|
+
if peek == 0x2B { // +
|
|
405
|
+
index += 1
|
|
406
|
+
} else if peek == 0x2D { // -
|
|
407
|
+
exponentNegative = true
|
|
408
|
+
index += 1
|
|
409
|
+
}
|
|
410
|
+
guard let digit = peek, Self.isDigit(digit) else {
|
|
411
|
+
throw error("expected a digit in the exponent")
|
|
412
|
+
}
|
|
413
|
+
var explicitExponent = 0
|
|
414
|
+
while let byte = peek, Self.isDigit(byte) {
|
|
415
|
+
// Clamp so absurd exponents cannot overflow Int; the value
|
|
416
|
+
// saturates to infinity or zero below anyway.
|
|
417
|
+
if explicitExponent < 100_000 {
|
|
418
|
+
explicitExponent = explicitExponent * 10 + Int(byte - 0x30)
|
|
419
|
+
}
|
|
420
|
+
index += 1
|
|
421
|
+
}
|
|
422
|
+
exponent += exponentNegative ? -explicitExponent : explicitExponent
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
var value = Double(mantissa)
|
|
426
|
+
if mantissa != 0 {
|
|
427
|
+
if exponent > 308 {
|
|
428
|
+
// Even a mantissa of 1 exceeds Double.greatestFiniteMagnitude.
|
|
429
|
+
value = .infinity
|
|
430
|
+
} else if exponent > 0 {
|
|
431
|
+
value *= Self.powersOfTen[exponent] // may overflow to +infinity
|
|
432
|
+
} else if exponent < -308 {
|
|
433
|
+
// Step down in two divisions; the mantissa has at most 19
|
|
434
|
+
// digits, so anything below 1e-330 underflows to zero.
|
|
435
|
+
let remaining = -exponent - 308
|
|
436
|
+
value = remaining > 22 ? 0 : value / Self.powersOfTen[308] / Self.powersOfTen[remaining]
|
|
437
|
+
} else if exponent < 0 {
|
|
438
|
+
value /= Self.powersOfTen[-exponent]
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return negative ? -value : value
|
|
442
|
+
}
|
|
443
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/// One instruction for a renderer. See docs/ops-protocol.md.
|
|
2
|
+
public enum _Op: Equatable, Sendable {
|
|
3
|
+
case create(id: Int, kind: String, props: _JSON)
|
|
4
|
+
case update(id: Int, props: _JSON)
|
|
5
|
+
case insert(parent: Int, id: Int, index: Int)
|
|
6
|
+
case remove(id: Int)
|
|
7
|
+
/// End of a render pass, optionally animated (`withAnimation`).
|
|
8
|
+
case commit(animation: _JSON? = nil)
|
|
9
|
+
/// Headless mode only: echoes a replayed event (an object with at least
|
|
10
|
+
/// `type`) before its ops.
|
|
11
|
+
case event(_JSON)
|
|
12
|
+
/// The app's `UserDefaults`, whole, after a change; the host stores them
|
|
13
|
+
/// and hands them back at the next launch (`SB_DEFAULTS`).
|
|
14
|
+
case defaults(_JSON)
|
|
15
|
+
|
|
16
|
+
public var json: _JSON {
|
|
17
|
+
switch self {
|
|
18
|
+
case .create(let id, let kind, let props):
|
|
19
|
+
return .object(["op": .string("create"), "id": .number(Double(id)), "kind": .string(kind), "props": props])
|
|
20
|
+
case .update(let id, let props):
|
|
21
|
+
return .object(["op": .string("update"), "id": .number(Double(id)), "props": props])
|
|
22
|
+
case .insert(let parent, let id, let index):
|
|
23
|
+
return .object(["op": .string("insert"), "parent": .number(Double(parent)),
|
|
24
|
+
"id": .number(Double(id)), "index": .number(Double(index))])
|
|
25
|
+
case .remove(let id):
|
|
26
|
+
return .object(["op": .string("remove"), "id": .number(Double(id))])
|
|
27
|
+
case .commit(let animation):
|
|
28
|
+
if let animation { return .object(["op": .string("commit"), "animation": animation]) }
|
|
29
|
+
return .object(["op": .string("commit")])
|
|
30
|
+
case .event(let event):
|
|
31
|
+
if case .object(var fields) = event {
|
|
32
|
+
fields["op"] = .string("event")
|
|
33
|
+
return .object(fields)
|
|
34
|
+
}
|
|
35
|
+
return .object(["op": .string("event")])
|
|
36
|
+
case .defaults(let values):
|
|
37
|
+
return .object(["op": .string("defaults"), "values": values])
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Where ops go. The browser build buffers them for JavaScript; headless mode
|
|
43
|
+
/// prints them; tests record them.
|
|
44
|
+
@MainActor
|
|
45
|
+
public protocol _RenderHost: AnyObject {
|
|
46
|
+
func apply(_ ops: [_Op])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Prints one op per line (JSON Lines).
|
|
50
|
+
@MainActor
|
|
51
|
+
final class _HeadlessHost: _RenderHost {
|
|
52
|
+
func apply(_ ops: [_Op]) {
|
|
53
|
+
for op in ops { print(op.json.serialized) }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Keeps pending ops serialized as a JSON array in a stable heap buffer that
|
|
58
|
+
/// the browser reads out of linear memory via the `sb_ops_*` exports.
|
|
59
|
+
@MainActor
|
|
60
|
+
final class _BufferedHost: _RenderHost {
|
|
61
|
+
static let shared = _BufferedHost()
|
|
62
|
+
|
|
63
|
+
private(set) var pending: [_Op] = []
|
|
64
|
+
private var buffer: UnsafeMutableBufferPointer<UInt8> = .allocate(capacity: 2)
|
|
65
|
+
|
|
66
|
+
init() {
|
|
67
|
+
reserialize()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func apply(_ ops: [_Op]) {
|
|
71
|
+
pending.append(contentsOf: ops)
|
|
72
|
+
reserialize()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func clear() {
|
|
76
|
+
pending.removeAll()
|
|
77
|
+
reserialize()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var pointer: Int32 { Int32(Int(bitPattern: buffer.baseAddress)) }
|
|
81
|
+
var length: Int32 { Int32(buffer.count) }
|
|
82
|
+
|
|
83
|
+
private func reserialize() {
|
|
84
|
+
let bytes = Array(_JSON.array(pending.map(\.json)).serialized.utf8)
|
|
85
|
+
buffer.deallocate()
|
|
86
|
+
buffer = .allocate(capacity: bytes.count)
|
|
87
|
+
_ = buffer.initialize(from: bytes)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Holds the serialized state snapshot for the `sb_state_*` exports.
|
|
92
|
+
@MainActor
|
|
93
|
+
final class _StateBuffer {
|
|
94
|
+
static let shared = _StateBuffer()
|
|
95
|
+
private var buffer: UnsafeMutableBufferPointer<UInt8> = .allocate(capacity: 0)
|
|
96
|
+
|
|
97
|
+
func refresh(with json: _JSON) {
|
|
98
|
+
let bytes = Array(json.serialized.utf8)
|
|
99
|
+
buffer.deallocate()
|
|
100
|
+
buffer = .allocate(capacity: bytes.count)
|
|
101
|
+
_ = buffer.initialize(from: bytes)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var pointer: Int32 { Int32(Int(bitPattern: buffer.baseAddress)) }
|
|
105
|
+
var length: Int32 { Int32(buffer.count) }
|
|
106
|
+
}
|