Skip to content

Typed array change proposal #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ var printTestNames = false
// This will make it easier to debug any errors that occur on the JS side.
//printTestNames = true

func test(_ name: String, testBlock: () throws -> Void) {
func test(_ name: String, testBlock: () throws -> Void) throws {
if printTestNames { print(name) }
do {
try testBlock()
} catch {
print("Error in \(name)")
print(error)
throw error
}
}

Expand All @@ -37,22 +38,22 @@ func expectEqual<T: Equatable>(
}
}

func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObjectRef {
func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSObject {
switch value {
case let .object(ref): return ref
default:
throw MessageError("Type of \(value) should be \"object\"", file: file, line: line, column: column)
}
}

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

func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunctionRef {
func expectFunction(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSFunction {
switch value {
case let .function(ref): return ref
default:
Expand Down
68 changes: 43 additions & 25 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import JavaScriptKit

test("Literal Conversion") {
let global = JSObjectRef.global
try test("Literal Conversion") {
let global = JSObject.global
let inputs: [JSValue] = [
.boolean(true),
.boolean(false),
Expand Down Expand Up @@ -29,7 +29,7 @@ test("Literal Conversion") {
}
}

test("Object Conversion") {
try test("Object Conversion") {
// Notes: globalObject1 is defined in JavaScript environment
//
// ```js
Expand Down Expand Up @@ -60,7 +60,7 @@ test("Object Conversion") {
let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4")
let prop_4Array = try expectObject(prop_4)
let expectedProp_4: [JSValue] = [
.number(3), .number(4), .string("str_elm_1"), .number(5),
.number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5),
]
for (index, expectedElement) in expectedProp_4.enumerated() {
let actualElement = getJSValue(this: prop_4Array, index: Int32(index))
Expand All @@ -70,7 +70,7 @@ test("Object Conversion") {
try expectEqual(getJSValue(this: globalObject1Ref, name: "undefined_prop"), .undefined)
}

test("Value Construction") {
try test("Value Construction") {
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
let globalObject1Ref = try expectObject(globalObject1)
let prop_2 = getJSValue(this: globalObject1Ref, name: "prop_2")
Expand All @@ -82,29 +82,43 @@ test("Value Construction") {
try expectEqual(Float.construct(from: prop_7), 3.14)
}

test("Array Iterator") {
try test("Array Iterator") {
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
let globalObject1Ref = try expectObject(globalObject1)
let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4")
let array = try expectArray(prop_4)
let array1 = try expectArray(prop_4)
let expectedProp_4: [JSValue] = [
.number(3), .number(4), .string("str_elm_1"), .number(5),
.number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5),
]
try expectEqual(Array(array), expectedProp_4)
try expectEqual(Array(array1), expectedProp_4)

// Ensure that iterator skips empty hole as JavaScript does.
let prop_8 = getJSValue(this: globalObject1Ref, name: "prop_8")
let array2 = try expectArray(prop_8)
let expectedProp_8: [JSValue] = [0, 2, 3, 6]
try expectEqual(Array(array2), expectedProp_8)
}

test("Array RandomAccessCollection") {
try test("Array RandomAccessCollection") {
let globalObject1 = getJSValue(this: .global, name: "globalObject1")
let globalObject1Ref = try expectObject(globalObject1)
let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4")
let array = try expectArray(prop_4)
let array1 = try expectArray(prop_4)
let expectedProp_4: [JSValue] = [
.number(3), .number(4), .string("str_elm_1"), .number(5),
.number(3), .number(4), .string("str_elm_1"), .null, .undefined, .number(5),
]
try expectEqual([array1[0], array1[1], array1[2], array1[3], array1[4], array1[5]], expectedProp_4)

// Ensure that subscript can access empty hole
let prop_8 = getJSValue(this: globalObject1Ref, name: "prop_8")
let array2 = try expectArray(prop_8)
let expectedProp_8: [JSValue] = [
0, .undefined, 2, 3, .undefined, .undefined, 6
]
try expectEqual([array[0], array[1], array[2], array[3]], expectedProp_4)
try expectEqual([array2[0], array2[1], array2[2], array2[3], array2[4], array2[5], array2[6]], expectedProp_8)
}

test("Value Decoder") {
try test("Value Decoder") {
struct GlobalObject1: Codable {
struct Prop1: Codable {
let nested_prop: Int
Expand All @@ -124,7 +138,7 @@ test("Value Decoder") {
try expectEqual(globalObject1.prop_7, 3.14)
}

test("Function Call") {
try test("Function Call") {
// Notes: globalObject1 is defined in JavaScript environment
//
// ```js
Expand Down Expand Up @@ -168,7 +182,7 @@ test("Function Call") {
try expectEqual(func6(true, "OK", 2), .string("OK"))
}

test("Host Function Registration") {
try test("Host Function Registration") {
// ```js
// global.globalObject1 = {
// ...
Expand Down Expand Up @@ -213,7 +227,7 @@ test("Host Function Registration") {
hostFunc2.release()
}

test("New Object Construction") {
try test("New Object Construction") {
// ```js
// global.Animal = function(name, age, isCat) {
// this.name = name
Expand All @@ -237,7 +251,7 @@ test("New Object Construction") {
try expectEqual(dog1Bark(), .string("wan"))
}

test("Call Function With This") {
try test("Call Function With This") {
// ```js
// global.Animal = function(name, age, isCat) {
// this.name = name
Expand All @@ -263,7 +277,7 @@ test("Call Function With This") {
try expectEqual(gotIsCat, .boolean(true))
}

test("Object Conversion") {
try test("Object Conversion") {
let array1 = [1, 2, 3]
let jsArray1 = array1.jsValue().object!
try expectEqual(jsArray1.length, .number(3))
Expand Down Expand Up @@ -292,7 +306,7 @@ test("Object Conversion") {
try expectEqual(jsDict1.prop2, .string("foo"))
}

test("ObjectRef Lifetime") {
try test("ObjectRef Lifetime") {
// ```js
// global.globalObject1 = {
// "prop_1": {
Expand Down Expand Up @@ -322,21 +336,25 @@ func closureScope() -> ObjectIdentifier {
return result
}

test("Closure Identifiers") {
try test("Closure Identifiers") {
let oid1 = closureScope()
let oid2 = closureScope()
try expectEqual(oid1, oid2)
}

func checkArray<T>(_ array: [T]) throws where T: TypedArrayElement {
try expectEqual(JSTypedArray(array).toString!(), .string(jsStringify(array)))
try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array))
}

func toString<T: JSObject>(_ object: T) -> String {
return object.toString!().string!
}

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

test("TypedArray") {
try test("TypedArray") {
let numbers = [UInt8](0 ... 255)
let typedArray = JSTypedArray(numbers)
try expectEqual(typedArray[12], 12)
Expand Down Expand Up @@ -366,13 +384,13 @@ test("TypedArray") {
}
}

test("TypedArray_Mutation") {
try test("TypedArray_Mutation") {
let array = JSTypedArray<Int>(length: 100)
for i in 0..<100 {
array[i] = i
}
for i in 0..<100 {
try expectEqual(i, array[i])
}
try expectEqual(array.toString!(), .string(jsStringify(Array(0..<100))))
try expectEqual(toString(array.jsValue().object!), jsStringify(Array(0..<100)))
}
6 changes: 4 additions & 2 deletions IntegrationTests/bin/primary-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ global.globalObject1 = {
"prop_2": 2,
"prop_3": true,
"prop_4": [
3, 4, "str_elm_1", 5,
3, 4, "str_elm_1", null, undefined, 5,
],
"prop_5": {
"func1": function () { return },
Expand All @@ -23,6 +23,7 @@ global.globalObject1 = {
}
},
"prop_7": 3.14,
"prop_8": [0, , 2, 3, , , 6],
}

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

startWasiTask("./dist/PrimaryTests.wasm").catch(err => {
console.log(err)
});
process.exit(1)
});
6 changes: 3 additions & 3 deletions IntegrationTests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ Can be written in Swift using JavaScriptKit
```swift
import JavaScriptKit

let alert = JSObjectRef.global.alert.function!
let document = JSObjectRef.global.document.object!
let alert = JSObject.global.alert.function!
let document = JSObject.global.document.object!

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

let jsPet = JSObjectRef.global.pet
let jsPet = JSObject.global.pet
let swiftPet: Pet = try JSValueDecoder().decode(from: jsPet)

alert("Swift is running on browser!")
Expand Down
86 changes: 86 additions & 0 deletions Sources/JavaScriptKit/BasicObjects/JSArray.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
public class JSArray {
static let classObject = JSObject.global.Array.function!

static func isArray(_ object: JSObject) -> Bool {
classObject.isArray!(object).boolean!
}

let ref: JSObject

public init?(_ ref: JSObject) {
guard Self.isArray(ref) else { return nil }
self.ref = ref
}
}

extension JSArray: RandomAccessCollection {
public typealias Element = JSValue

public func makeIterator() -> Iterator {
Iterator(ref: ref)
}

public class Iterator: IteratorProtocol {
let ref: JSObject
var index = 0
init(ref: JSObject) {
self.ref = ref
}

public func next() -> Element? {
let currentIndex = index
guard currentIndex < Int(ref.length.number!) else {
return nil
}
index += 1
guard ref.hasOwnProperty!(currentIndex).boolean! else {
return next()
}
let value = ref[currentIndex]
return value
}
}

public subscript(position: Int) -> JSValue {
ref[position]
}

public var startIndex: Int { 0 }

public var endIndex: Int { length }

/// The number of elements in that array including empty hole.
/// Note that `length` respects JavaScript's `Array.prototype.length`
///
/// e.g.
/// ```javascript
/// const array = [1, , 3];
/// ```
/// ```swift
/// let array: JSArray = ...
/// array.length // 3
/// array.count // 2
/// ```
public var length: Int {
return Int(ref.length.number!)
}

/// The number of elements in that array **not** including empty hole.
/// Note that `count` syncs with the number that `Iterator` can iterate.
/// See also: `JSArray.length`
public var count: Int {
return getObjectValuesLength(ref)
}
}

private let alwaysTrue = JSClosure { _ in .boolean(true) }
private func getObjectValuesLength(_ object: JSObject) -> Int {
let values = object.filter!(alwaysTrue).object!
return Int(values.length.number!)
}

extension JSValue {
public var array: JSArray? {
object.flatMap(JSArray.init)
}
}
Loading