Skip to content

Commit 702dd89

Browse files
authored
Merge pull request #26 from j-f1/webidl
Add JSBridgedType and JSBridgedClass
2 parents 8cdc2d1 + 767d05d commit 702dd89

10 files changed

+266
-86
lines changed

Sources/JavaScriptKit/BasicObjects/JSArray.swift

+28-19
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,63 @@
11
/// A wrapper around [the JavaScript Array class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)
22
/// that exposes its properties in a type-safe and Swifty way.
3-
public class JSArray {
4-
static let classObject = JSObject.global.Array.function!
3+
public class JSArray: JSBridgedClass {
4+
public static let constructor = JSObject.global.Array.function!
55

66
static func isArray(_ object: JSObject) -> Bool {
7-
classObject.isArray!(object).boolean!
7+
constructor.isArray!(object).boolean!
8+
}
9+
10+
public let jsObject: JSObject
11+
12+
public required convenience init?(from value: JSValue) {
13+
guard let object = value.object else { return nil }
14+
self.init(object)
815
}
916

10-
let ref: JSObject
11-
1217
/// Construct a `JSArray` from Array `JSObject`.
1318
/// Return `nil` if the object is not an Array.
1419
///
1520
/// - Parameter object: A `JSObject` expected to be a JavaScript Array
16-
public init?(_ ref: JSObject) {
17-
guard Self.isArray(ref) else { return nil }
18-
self.ref = ref
21+
public convenience init?(_ jsObject: JSObject) {
22+
guard Self.isArray(jsObject) else { return nil }
23+
self.init(unsafelyWrapping: jsObject)
24+
}
25+
26+
public required init(unsafelyWrapping jsObject: JSObject) {
27+
self.jsObject = jsObject
1928
}
2029
}
2130

2231
extension JSArray: RandomAccessCollection {
2332
public typealias Element = JSValue
2433

2534
public func makeIterator() -> Iterator {
26-
Iterator(ref: ref)
35+
Iterator(jsObject: jsObject)
2736
}
2837

2938
public class Iterator: IteratorProtocol {
30-
let ref: JSObject
31-
var index = 0
32-
init(ref: JSObject) {
33-
self.ref = ref
39+
private let jsObject: JSObject
40+
private var index = 0
41+
init(jsObject: JSObject) {
42+
self.jsObject = jsObject
3443
}
3544

3645
public func next() -> Element? {
3746
let currentIndex = index
38-
guard currentIndex < Int(ref.length.number!) else {
47+
guard currentIndex < Int(jsObject.length.number!) else {
3948
return nil
4049
}
4150
index += 1
42-
guard ref.hasOwnProperty!(currentIndex).boolean! else {
51+
guard jsObject.hasOwnProperty!(currentIndex).boolean! else {
4352
return next()
4453
}
45-
let value = ref[currentIndex]
54+
let value = jsObject[currentIndex]
4655
return value
4756
}
4857
}
4958

5059
public subscript(position: Int) -> JSValue {
51-
ref[position]
60+
jsObject[position]
5261
}
5362

5463
public var startIndex: Int { 0 }
@@ -68,14 +77,14 @@ extension JSArray: RandomAccessCollection {
6877
/// array.count // 2
6978
/// ```
7079
public var length: Int {
71-
return Int(ref.length.number!)
80+
Int(jsObject.length.number!)
7281
}
7382

7483
/// The number of elements in that array **not** including empty hole.
7584
/// Note that `count` syncs with the number that `Iterator` can iterate.
7685
/// See also: `JSArray.length`
7786
public var count: Int {
78-
return getObjectValuesLength(ref)
87+
getObjectValuesLength(jsObject)
7988
}
8089
}
8190

Sources/JavaScriptKit/BasicObjects/JSDate.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ in the naming. Parts of the JavaScript `Date` API that are not consistent across
66
implementations are not exposed in a type-safe manner, you should access the underlying `jsObject`
77
property if you need those.
88
*/
9-
public final class JSDate {
9+
public final class JSDate: JSBridgedClass {
1010
/// The constructor function used to create new `Date` objects.
11-
private static let constructor = JSObject.global.Date.function!
11+
public static let constructor = JSObject.global.Date.function!
1212

1313
/// The underlying JavaScript `Date` object.
1414
public let jsObject: JSObject
@@ -39,6 +39,10 @@ public final class JSDate {
3939
jsObject = Self.constructor.new(year, monthIndex, day, hours, minutes, seconds, milliseconds)
4040
}
4141

42+
public init(unsafelyWrapping jsObject: JSObject) {
43+
self.jsObject = jsObject
44+
}
45+
4246
/// Year of this date in local time zone.
4347
public var fullYear: Int {
4448
get {

Sources/JavaScriptKit/BasicObjects/JSError.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that
33
exposes its properties in a type-safe way.
44
*/
5-
public final class JSError: Error {
5+
public final class JSError: Error, JSBridgedClass {
66
/// The constructor function used to create new `Error` objects.
7-
private static let constructor = JSObject.global.Error.function!
7+
public static let constructor = JSObject.global.Error.function!
88

99
/// The underlying JavaScript `Error` object.
1010
public let jsObject: JSObject
@@ -14,6 +14,10 @@ public final class JSError: Error {
1414
jsObject = Self.constructor.new([message])
1515
}
1616

17+
public init(unsafelyWrapping jsObject: JSObject) {
18+
self.jsObject = jsObject
19+
}
20+
1721
/// The error message of the underlying `Error` object.
1822
public var message: String {
1923
jsObject.message.string!

Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift

+17-30
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,42 @@
44

55
import _CJavaScriptKit
66

7+
/// A protocol that allows a Swift numeric type to be mapped to the JavaScript TypedArray that holds integers of its type
78
public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible {
9+
/// The constructor function for the TypedArray class for this particular kind of number
810
static var typedArrayClass: JSFunction { get }
911
}
1012

11-
/// A wrapper around [the JavaScript TypedArray class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
12-
/// that exposes its properties in a type-safe and Swifty way.
13-
public class JSTypedArray<Element>: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement {
14-
let ref: JSObject
15-
public func jsValue() -> JSValue {
16-
.object(ref)
17-
}
13+
/// A wrapper around all JavaScript [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) classes that exposes their properties in a type-safe way.
14+
/// FIXME: the BigInt-based TypedArrays are not supported (https://github.com/swiftwasm/JavaScriptKit/issues/56)
15+
public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement {
16+
public static var constructor: JSFunction { Element.typedArrayClass }
17+
public var jsObject: JSObject
1818

1919
public subscript(_ index: Int) -> Element {
2020
get {
21-
return Element.construct(from: getJSValue(this: ref, index: Int32(index)))!
21+
return Element.construct(from: jsObject[index])!
2222
}
2323
set {
24-
setJSValue(this: ref, index: Int32(index), value: newValue.jsValue())
24+
self.jsObject[index] = newValue.jsValue()
2525
}
2626
}
2727

28-
// This private initializer assumes that the passed object is TypedArray
29-
private init(unsafe object: JSObject) {
30-
self.ref = object
31-
}
32-
33-
/// Construct a `JSTypedArray` from TypedArray `JSObject`.
34-
/// Return `nil` if the object is not TypedArray.
28+
/// Initialize a new instance of TypedArray in JavaScript environment with given length.
29+
/// All the elements will be initialized to zero.
3530
///
36-
/// - Parameter object: A `JSObject` expected to be TypedArray
37-
public init?(_ object: JSObject) {
38-
guard object.isInstanceOf(Element.typedArrayClass) else { return nil }
39-
self.ref = object
31+
/// - Parameter length: The number of elements that will be allocated.
32+
public init(length: Int) {
33+
jsObject = Element.typedArrayClass.new(length)
4034
}
4135

42-
/// Initialize a new instance of TypedArray in JavaScript environment with given length zero value.
43-
///
44-
/// - Parameter length: The length of elements that will be allocated.
45-
public convenience init(length: Int) {
46-
let jsObject = Element.typedArrayClass.new(length)
47-
self.init(unsafe: jsObject)
36+
required public init(unsafelyWrapping jsObject: JSObject) {
37+
self.jsObject = jsObject
4838
}
4939

5040
required public convenience init(arrayLiteral elements: Element...) {
5141
self.init(elements)
5242
}
53-
5443
/// Initialize a new instance of TypedArray in JavaScript environment with given elements.
5544
///
5645
/// - Parameter array: The array that will be copied to create a new instance of TypedArray
@@ -59,7 +48,7 @@ public class JSTypedArray<Element>: JSValueConvertible, ExpressibleByArrayLitera
5948
array.withUnsafeBufferPointer { ptr in
6049
_create_typed_array(Element.typedArrayClass.id, ptr.baseAddress!, Int32(array.count), &resultObj)
6150
}
62-
self.init(unsafe: JSObject(id: resultObj))
51+
self.init(unsafelyWrapping: JSObject(id: resultObj))
6352
}
6453

6554
/// Convenience initializer for `Sequence`.
@@ -90,8 +79,6 @@ extension UInt: TypedArrayElement {
9079
valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObject.global.Uint32Array).function!
9180
}
9281

93-
// MARK: - Concrete TypedArray classes
94-
9582
extension Int8: TypedArrayElement {
9683
public static var typedArrayClass = JSObject.global.Int8Array.function!
9784
}

Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ public class JSFunction: JSObject {
6262
let argv = bufferPointer.baseAddress
6363
let argc = bufferPointer.count
6464
var resultObj = JavaScriptObjectRef()
65-
_call_new(
66-
self.id, argv, Int32(argc),
67-
&resultObj
68-
)
65+
_call_new(self.id, argv, Int32(argc), &resultObj)
6966
return JSObject(id: resultObj)
7067
}
7168
}
@@ -81,6 +78,10 @@ public class JSFunction: JSObject {
8178
fatalError("unavailable")
8279
}
8380

81+
public override class func construct(from value: JSValue) -> Self? {
82+
return value.function as? Self
83+
}
84+
8485
override public func jsValue() -> JSValue {
8586
.function(self)
8687
}

Sources/JavaScriptKit/FundamentalObjects/JSObject.swift

+14-3
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,20 @@ public class JSObject: Equatable {
3232
/// - Parameter name: The name of this object's member to access.
3333
/// - Returns: The `name` member method binding this object as `this` context.
3434
@_disfavoredOverload
35-
public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? {
35+
public subscript(_ name: String) -> ((JSValueConvertible...) -> JSValue)? {
3636
guard let function = self[name].function else { return nil }
3737
return { (arguments: JSValueConvertible...) in
3838
function(this: self, arguments: arguments)
3939
}
4040
}
4141

42+
/// A convenience method of `subscript(_ name: String) -> ((JSValueConvertible...) -> JSValue)?`
43+
/// to access the member through Dynamic Member Lookup.
44+
@_disfavoredOverload
45+
public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? {
46+
self[name]
47+
}
48+
4249
/// A convenience method of `subscript(_ name: String) -> JSValue`
4350
/// to access the member through Dynamic Member Lookup.
4451
public subscript(dynamicMember name: String) -> JSValue {
@@ -62,9 +69,9 @@ public class JSObject: Equatable {
6269
set { setJSValue(this: self, index: Int32(index), value: newValue) }
6370
}
6471

65-
/// Return `true` if this object is an instance of the `constructor`. Return `false`, if not.
72+
/// Return `true` if this value is an instance of the passed `constructor` function.
6673
/// - Parameter constructor: The constructor function to check.
67-
/// - Returns: The result of `instanceof` in JavaScript environment.
74+
/// - Returns: The result of `instanceof` in the JavaScript environment.
6875
public func isInstanceOf(_ constructor: JSFunction) -> Bool {
6976
_instanceof(id, constructor.id)
7077
}
@@ -86,6 +93,10 @@ public class JSObject: Equatable {
8693
return lhs.id == rhs.id
8794
}
8895

96+
public class func construct(from value: JSValue) -> Self? {
97+
return value.object as? Self
98+
}
99+
89100
public func jsValue() -> JSValue {
90101
.object(self)
91102
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/// Use this protocol when your type has no single JavaScript class.
2+
/// For example, a union type of multiple classes or primitive values.
3+
public protocol JSBridgedType: JSValueCodable, CustomStringConvertible {
4+
/// This is the value your class wraps.
5+
var value: JSValue { get }
6+
7+
/// If your class is incompatible with the provided value, return `nil`.
8+
init?(from value: JSValue)
9+
}
10+
11+
extension JSBridgedType {
12+
public static func construct(from value: JSValue) -> Self? {
13+
return Self.init(from: value)
14+
}
15+
16+
public func jsValue() -> JSValue { value }
17+
18+
public var description: String { value.description }
19+
}
20+
21+
/// Conform to this protocol when your Swift class wraps a JavaScript class.
22+
public protocol JSBridgedClass: JSBridgedType {
23+
/// The constructor function for the JavaScript class
24+
static var constructor: JSFunction { get }
25+
26+
/// The JavaScript object wrapped by this instance.
27+
/// You may assume that `jsObject instanceof Self.constructor == true`
28+
var jsObject: JSObject { get }
29+
30+
/// Create an instannce wrapping the given JavaScript object.
31+
/// You may assume that `jsObject instanceof Self.constructor`
32+
init(unsafelyWrapping jsObject: JSObject)
33+
}
34+
35+
extension JSBridgedClass {
36+
public var value: JSValue { jsObject.jsValue() }
37+
public init?(from value: JSValue) {
38+
guard let object = value.object, object.isInstanceOf(Self.constructor) else { return nil }
39+
self.init(unsafelyWrapping: object)
40+
}
41+
}

0 commit comments

Comments
 (0)