Skip to content

Commit 2370a1f

Browse files
authored
Merge pull request #1 from kateinoigakukun/typed-array-change-proposal
Typed array change proposal
2 parents 7836ac2 + f83a84c commit 2370a1f

File tree

17 files changed

+213
-146
lines changed

17 files changed

+213
-146
lines changed

Example/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ var printTestNames = false
55
// This will make it easier to debug any errors that occur on the JS side.
66
//printTestNames = true
77

8-
func test(_ name: String, testBlock: () throws -> Void) {
8+
func test(_ name: String, testBlock: () throws -> Void) throws {
99
if printTestNames { print(name) }
1010
do {
1111
try testBlock()
1212
} catch {
1313
print("Error in \(name)")
1414
print(error)
15+
throw error
1516
}
1617
}
1718

@@ -37,22 +38,22 @@ func expectEqual<T: Equatable>(
3738
}
3839
}
3940

40-
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObjectRef {
41+
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObject {
4142
switch value {
4243
case let .object(ref): return ref
4344
default:
4445
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column)
4546
}
4647
}
4748

48-
func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArrayRef {
49+
func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArray {
4950
guard let array = value.array else {
5051
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column)
5152
}
5253
return array
5354
}
5455

55-
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunctionRef {
56+
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunction {
5657
switch value {
5758
case let .function(ref): return ref
5859
default:

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import JavaScriptKit
22

3-
test("Literal Conversion") {
4-
let global = JSObjectRef.global
3+
try test("Literal Conversion") {
4+
let global = JSObject.global
55
let inputs: [JSValue] = [
66
.boolean(true),
77
.boolean(false),
@@ -29,7 +29,7 @@ test("Literal Conversion") {
2929
}
3030
}
3131

32-
test("Object Conversion") {
32+
try test("Object Conversion") {
3333
// Notes: globalObject1 is defined in JavaScript environment
3434
//
3535
// ```js
@@ -60,7 +60,7 @@ test("Object Conversion") {
6060
let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4")
6161
let prop_4Array = try expectObject(prop_4)
6262
let expectedProp_4: [JSValue] = [
63-
.number(3), .number(4), .string("str_elm_1"), .number(5),
63+
.number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5),
6464
]
6565
for (index, expectedElement) in expectedProp_4.enumerated() {
6666
let actualElement = getJSValue(this: prop_4Array, index: Int32(index))
@@ -70,7 +70,7 @@ test("Object Conversion") {
7070
try expectEqual(getJSValue(this: globalObject1Ref, name: "undefined_prop"), .undefined)
7171
}
7272

73-
test("Value Construction") {
73+
try test("Value Construction") {
7474
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
7575
let globalObject1Ref = try expectObject(globalObject1)
7676
let prop_2 = getJSValue(this: globalObject1Ref, name: "prop_2")
@@ -82,29 +82,43 @@ test("Value Construction") {
8282
try expectEqual(Float.construct(from: prop_7), 3.14)
8383
}
8484

85-
test("Array Iterator") {
85+
try test("Array Iterator") {
8686
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
8787
let globalObject1Ref = try expectObject(globalObject1)
8888
let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4")
89-
let array = try expectArray(prop_4)
89+
let array1 = try expectArray(prop_4)
9090
let expectedProp_4: [JSValue] = [
91-
.number(3), .number(4), .string("str_elm_1"), .number(5),
91+
.number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5),
9292
]
93-
try expectEqual(Array(array), expectedProp_4)
93+
try expectEqual(Array(array1), expectedProp_4)
94+
95+
// Ensure that iterator skips empty hole as JavaScript does.
96+
let prop_8 = getJSValue(this: globalObject1Ref, name: "prop_8")
97+
let array2 = try expectArray(prop_8)
98+
let expectedProp_8: [JSValue] = [0, 2, 3, 6]
99+
try expectEqual(Array(array2), expectedProp_8)
94100
}
95101

96-
test("Array RandomAccessCollection") {
102+
try test("Array RandomAccessCollection") {
97103
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
98104
let globalObject1Ref = try expectObject(globalObject1)
99105
let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4")
100-
let array = try expectArray(prop_4)
106+
let array1 = try expectArray(prop_4)
101107
let expectedProp_4: [JSValue] = [
102-
.number(3), .number(4), .string("str_elm_1"), .number(5),
108+
.number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5),
109+
]
110+
try expectEqual([array1[0], array1[1], array1[2], array1[3], array1[4], array1[5]], expectedProp_4)
111+
112+
// Ensure that subscript can access empty hole
113+
let prop_8 = getJSValue(this: globalObject1Ref, name: "prop_8")
114+
let array2 = try expectArray(prop_8)
115+
let expectedProp_8: [JSValue] = [
116+
0, .undefined, 2, 3, .undefined, .undefined, 6
103117
]
104-
try expectEqual([array[0], array[1], array[2], array[3]], expectedProp_4)
118+
try expectEqual([array2[0], array2[1], array2[2], array2[3], array2[4], array2[5], array2[6]], expectedProp_8)
105119
}
106120

107-
test("Value Decoder") {
121+
try test("Value Decoder") {
108122
struct GlobalObject1: Codable {
109123
struct Prop1: Codable {
110124
let nested_prop: Int
@@ -124,7 +138,7 @@ test("Value Decoder") {
124138
try expectEqual(globalObject1.prop_7, 3.14)
125139
}
126140

127-
test("Function Call") {
141+
try test("Function Call") {
128142
// Notes: globalObject1 is defined in JavaScript environment
129143
//
130144
// ```js
@@ -168,7 +182,7 @@ test("Function Call") {
168182
try expectEqual(func6(true, "OK", 2), .string("OK"))
169183
}
170184

171-
test("Host Function Registration") {
185+
try test("Host Function Registration") {
172186
// ```js
173187
// global.globalObject1 = {
174188
// ...
@@ -213,7 +227,7 @@ test("Host Function Registration") {
213227
hostFunc2.release()
214228
}
215229

216-
test("New Object Construction") {
230+
try test("New Object Construction") {
217231
// ```js
218232
// global.Animal = function(name, age, isCat) {
219233
// this.name = name
@@ -237,7 +251,7 @@ test("New Object Construction") {
237251
try expectEqual(dog1Bark(), .string("wan"))
238252
}
239253

240-
test("Call Function With This") {
254+
try test("Call Function With This") {
241255
// ```js
242256
// global.Animal = function(name, age, isCat) {
243257
// this.name = name
@@ -263,7 +277,7 @@ test("Call Function With This") {
263277
try expectEqual(gotIsCat, .boolean(true))
264278
}
265279

266-
test("Object Conversion") {
280+
try test("Object Conversion") {
267281
let array1 = [1, 2, 3]
268282
let jsArray1 = array1.jsValue().object!
269283
try expectEqual(jsArray1.length, .number(3))
@@ -292,7 +306,7 @@ test("Object Conversion") {
292306
try expectEqual(jsDict1.prop2, .string("foo"))
293307
}
294308

295-
test("ObjectRef Lifetime") {
309+
try test("ObjectRef Lifetime") {
296310
// ```js
297311
// global.globalObject1 = {
298312
// "prop_1": {
@@ -322,21 +336,25 @@ func closureScope() -> ObjectIdentifier {
322336
return result
323337
}
324338

325-
test("Closure Identifiers") {
339+
try test("Closure Identifiers") {
326340
let oid1 = closureScope()
327341
let oid2 = closureScope()
328342
try expectEqual(oid1, oid2)
329343
}
330344

331345
func checkArray<T>(_ array: [T]) throws where T: TypedArrayElement {
332-
try expectEqual(JSTypedArray(array).toString!(), .string(jsStringify(array)))
346+
try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array))
347+
}
348+
349+
func toString<T: JSObject>(_ object: T) -> String {
350+
return object.toString!().string!
333351
}
334352

335353
func jsStringify(_ array: [Any]) -> String {
336354
array.map({ String(describing: $0) }).joined(separator: ",")
337355
}
338356

339-
test("TypedArray") {
357+
try test("TypedArray") {
340358
let numbers = [UInt8](0 ... 255)
341359
let typedArray = JSTypedArray(numbers)
342360
try expectEqual(typedArray[12], 12)
@@ -366,13 +384,13 @@ test("TypedArray") {
366384
}
367385
}
368386

369-
test("TypedArray_Mutation") {
387+
try test("TypedArray_Mutation") {
370388
let array = JSTypedArray<Int>(length: 100)
371389
for i in 0..<100 {
372390
array[i] = i
373391
}
374392
for i in 0..<100 {
375393
try expectEqual(i, array[i])
376394
}
377-
try expectEqual(array.toString!(), .string(jsStringify(Array(0..<100))))
395+
try expectEqual(toString(array.jsValue().object!), jsStringify(Array(0..<100)))
378396
}

IntegrationTests/bin/primary-tests.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ global.globalObject1 = {
55
"prop_2": 2,
66
"prop_3": true,
77
"prop_4": [
8-
3, 4, "str_elm_1", 5,
8+
3, 4, "str_elm_1", null, undefined, 5,
99
],
1010
"prop_5": {
1111
"func1": function () { return },
@@ -23,6 +23,7 @@ global.globalObject1 = {
2323
}
2424
},
2525
"prop_7": 3.14,
26+
"prop_8": [0, , 2, 3, , , 6],
2627
}
2728

2829
global.Animal = function(name, age, isCat) {
@@ -41,4 +42,5 @@ const { startWasiTask } = require("../lib")
4142

4243
startWasiTask("./dist/PrimaryTests.wasm").catch(err => {
4344
console.log(err)
44-
});
45+
process.exit(1)
46+
});

IntegrationTests/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ Can be written in Swift using JavaScriptKit
4747
```swift
4848
import JavaScriptKit
4949

50-
let alert = JSObjectRef.global.alert.function!
51-
let document = JSObjectRef.global.document.object!
50+
let alert = JSObject.global.alert.function!
51+
let document = JSObject.global.document.object!
5252

5353
let divElement = document.createElement!("div").object!
5454
divElement.innerText = "Hello, world"
@@ -64,7 +64,7 @@ struct Pet: Codable {
6464
let owner: Owner
6565
}
6666

67-
let jsPet = JSObjectRef.global.pet
67+
let jsPet = JSObject.global.pet
6868
let swiftPet: Pet = try JSValueDecoder().decode(from: jsPet)
6969

7070
alert("Swift is running on browser!")
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
public class JSArray {
2+
static let classObject = JSObject.global.Array.function!
3+
4+
static func isArray(_ object: JSObject) -> Bool {
5+
classObject.isArray!(object).boolean!
6+
}
7+
8+
let ref: JSObject
9+
10+
public init?(_ ref: JSObject) {
11+
guard Self.isArray(ref) else { return nil }
12+
self.ref = ref
13+
}
14+
}
15+
16+
extension JSArray: RandomAccessCollection {
17+
public typealias Element = JSValue
18+
19+
public func makeIterator() -> Iterator {
20+
Iterator(ref: ref)
21+
}
22+
23+
public class Iterator: IteratorProtocol {
24+
let ref: JSObject
25+
var index = 0
26+
init(ref: JSObject) {
27+
self.ref = ref
28+
}
29+
30+
public func next() -> Element? {
31+
let currentIndex = index
32+
guard currentIndex < Int(ref.length.number!) else {
33+
return nil
34+
}
35+
index += 1
36+
guard ref.hasOwnProperty!(currentIndex).boolean! else {
37+
return next()
38+
}
39+
let value = ref[currentIndex]
40+
return value
41+
}
42+
}
43+
44+
public subscript(position: Int) -> JSValue {
45+
ref[position]
46+
}
47+
48+
public var startIndex: Int { 0 }
49+
50+
public var endIndex: Int { length }
51+
52+
/// The number of elements in that array including empty hole.
53+
/// Note that `length` respects JavaScript's `Array.prototype.length`
54+
///
55+
/// e.g.
56+
/// ```javascript
57+
/// const array = [1, , 3];
58+
/// ```
59+
/// ```swift
60+
/// let array: JSArray = ...
61+
/// array.length // 3
62+
/// array.count // 2
63+
/// ```
64+
public var length: Int {
65+
return Int(ref.length.number!)
66+
}
67+
68+
/// The number of elements in that array **not** including empty hole.
69+
/// Note that `count` syncs with the number that `Iterator` can iterate.
70+
/// See also: `JSArray.length`
71+
public var count: Int {
72+
return getObjectValuesLength(ref)
73+
}
74+
}
75+
76+
private let alwaysTrue = JSClosure { _ in .boolean(true) }
77+
private func getObjectValuesLength(_ object: JSObject) -> Int {
78+
let values = object.filter!(alwaysTrue).object!
79+
return Int(values.length.number!)
80+
}
81+
82+
extension JSValue {
83+
public var array: JSArray? {
84+
object.flatMap(JSArray.init)
85+
}
86+
}

0 commit comments

Comments
 (0)