Skip to content

Add JSBridgedType and JSBridgedClass #26

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 26 commits into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
47 changes: 28 additions & 19 deletions Sources/JavaScriptKit/BasicObjects/JSArray.swift
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
/// A wrapper around [the JavaScript Array class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array)
/// that exposes its properties in a type-safe and Swifty way.
public class JSArray {
static let classObject = JSObject.global.Array.function!
public class JSArray: JSBridgedClass {
public static let constructor = JSObject.global.Array.function!

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

public let jsObject: JSObject

public required convenience init?(from value: JSValue) {
guard let object = value.object else { return nil }
self.init(object)
}

let ref: JSObject

/// Construct a `JSArray` from Array `JSObject`.
/// Return `nil` if the object is not an Array.
///
/// - Parameter object: A `JSObject` expected to be a JavaScript Array
public init?(_ ref: JSObject) {
guard Self.isArray(ref) else { return nil }
self.ref = ref
public convenience init?(_ jsObject: JSObject) {
guard Self.isArray(jsObject) else { return nil }
self.init(unsafelyWrapping: jsObject)
}

public required init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}
}

extension JSArray: RandomAccessCollection {
public typealias Element = JSValue

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

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

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

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

public var startIndex: Int { 0 }
Expand All @@ -68,14 +77,14 @@ extension JSArray: RandomAccessCollection {
/// array.count // 2
/// ```
public var length: Int {
return Int(ref.length.number!)
Int(jsObject.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)
getObjectValuesLength(jsObject)
}
}

Expand Down
8 changes: 6 additions & 2 deletions Sources/JavaScriptKit/BasicObjects/JSDate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ in the naming. Parts of the JavaScript `Date` API that are not consistent across
implementations are not exposed in a type-safe manner, you should access the underlying `jsObject`
property if you need those.
*/
public final class JSDate {
public final class JSDate: JSBridgedClass {
/// The constructor function used to create new `Date` objects.
private static let constructor = JSObject.global.Date.function!
public static let constructor = JSObject.global.Date.function!

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

public init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}

/// Year of this date in local time zone.
public var fullYear: Int {
get {
Expand Down
8 changes: 6 additions & 2 deletions Sources/JavaScriptKit/BasicObjects/JSError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that
exposes its properties in a type-safe way.
*/
public final class JSError: Error {
public final class JSError: Error, JSBridgedClass {
/// The constructor function used to create new `Error` objects.
private static let constructor = JSObject.global.Error.function!
public static let constructor = JSObject.global.Error.function!

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

public init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}

/// The error message of the underlying `Error` object.
public var message: String {
jsObject.message.string!
Expand Down
47 changes: 17 additions & 30 deletions Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,42 @@

import _CJavaScriptKit

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

/// A wrapper around [the JavaScript TypedArray class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
/// that exposes its properties in a type-safe and Swifty way.
public class JSTypedArray<Element>: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement {
let ref: JSObject
public func jsValue() -> JSValue {
.object(ref)
}
/// 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.
/// FIXME: the BigInt-based TypedArrays are not supported (https://github.com/swiftwasm/JavaScriptKit/issues/56)
public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement {
public static var constructor: JSFunction { Element.typedArrayClass }
public var jsObject: JSObject

public subscript(_ index: Int) -> Element {
get {
return Element.construct(from: getJSValue(this: ref, index: Int32(index)))!
return Element.construct(from: jsObject[index])!
}
set {
setJSValue(this: ref, index: Int32(index), value: newValue.jsValue())
self.jsObject[index] = newValue.jsValue()
}
}

// This private initializer assumes that the passed object is TypedArray
private init(unsafe object: JSObject) {
self.ref = object
}

/// Construct a `JSTypedArray` from TypedArray `JSObject`.
/// Return `nil` if the object is not TypedArray.
/// Initialize a new instance of TypedArray in JavaScript environment with given length.
/// All the elements will be initialized to zero.
///
/// - Parameter object: A `JSObject` expected to be TypedArray
public init?(_ object: JSObject) {
guard object.isInstanceOf(Element.typedArrayClass) else { return nil }
self.ref = object
/// - Parameter length: The number of elements that will be allocated.
public init(length: Int) {
jsObject = Element.typedArrayClass.new(length)
}

/// Initialize a new instance of TypedArray in JavaScript environment with given length zero value.
///
/// - Parameter length: The length of elements that will be allocated.
public convenience init(length: Int) {
let jsObject = Element.typedArrayClass.new(length)
self.init(unsafe: jsObject)
required public init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}

required public convenience init(arrayLiteral elements: Element...) {
self.init(elements)
}

/// Initialize a new instance of TypedArray in JavaScript environment with given elements.
///
/// - Parameter array: The array that will be copied to create a new instance of TypedArray
Expand All @@ -59,7 +48,7 @@ public class JSTypedArray<Element>: JSValueConvertible, ExpressibleByArrayLitera
array.withUnsafeBufferPointer { ptr in
_create_typed_array(Element.typedArrayClass.id, ptr.baseAddress!, Int32(array.count), &resultObj)
}
self.init(unsafe: JSObject(id: resultObj))
self.init(unsafelyWrapping: JSObject(id: resultObj))
}

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

// MARK: - Concrete TypedArray classes

extension Int8: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Int8Array.function!
}
Expand Down
9 changes: 5 additions & 4 deletions Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ public class JSFunction: JSObject {
let argv = bufferPointer.baseAddress
let argc = bufferPointer.count
var resultObj = JavaScriptObjectRef()
_call_new(
self.id, argv, Int32(argc),
&resultObj
)
_call_new(self.id, argv, Int32(argc), &resultObj)
return JSObject(id: resultObj)
}
}
Expand All @@ -81,6 +78,10 @@ public class JSFunction: JSObject {
fatalError("unavailable")
}

public override class func construct(from value: JSValue) -> Self? {
return value.function as? Self
}

override public func jsValue() -> JSValue {
.function(self)
}
Expand Down
17 changes: 14 additions & 3 deletions Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,20 @@ public class JSObject: Equatable {
/// - Parameter name: The name of this object's member to access.
/// - Returns: The `name` member method binding this object as `this` context.
@_disfavoredOverload
public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? {
public subscript(_ name: String) -> ((JSValueConvertible...) -> JSValue)? {
guard let function = self[name].function else { return nil }
return { (arguments: JSValueConvertible...) in
function(this: self, arguments: arguments)
}
}

/// A convenience method of `subscript(_ name: String) -> ((JSValueConvertible...) -> JSValue)?`
/// to access the member through Dynamic Member Lookup.
@_disfavoredOverload
public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? {
self[name]
}

/// A convenience method of `subscript(_ name: String) -> JSValue`
/// to access the member through Dynamic Member Lookup.
public subscript(dynamicMember name: String) -> JSValue {
Expand All @@ -62,9 +69,9 @@ public class JSObject: Equatable {
set { setJSValue(this: self, index: Int32(index), value: newValue) }
}

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

public class func construct(from value: JSValue) -> Self? {
return value.object as? Self
}

public func jsValue() -> JSValue {
.object(self)
}
Expand Down
41 changes: 41 additions & 0 deletions Sources/JavaScriptKit/JSBridgedType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// Use this protocol when your type has no single JavaScript class.
/// For example, a union type of multiple classes or primitive values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this comment give an example of such type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Would it be OK to link to DOMKit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please do 👍

public protocol JSBridgedType: JSValueCodable, CustomStringConvertible {
/// This is the value your class wraps.
var value: JSValue { get }

/// If your class is incompatible with the provided value, return `nil`.
init?(from value: JSValue)
}

extension JSBridgedType {
public static func construct(from value: JSValue) -> Self? {
return Self.init(from: value)
}

public func jsValue() -> JSValue { value }

public var description: String { value.description }
}

/// Conform to this protocol when your Swift class wraps a JavaScript class.
public protocol JSBridgedClass: JSBridgedType {
/// The constructor function for the JavaScript class
static var constructor: JSFunction { get }

/// The JavaScript object wrapped by this instance.
/// You may assume that `jsObject instanceof Self.constructor == true`
var jsObject: JSObject { get }

/// Create an instannce wrapping the given JavaScript object.
/// You may assume that `jsObject instanceof Self.constructor`
init(unsafelyWrapping jsObject: JSObject)
}

extension JSBridgedClass {
public var value: JSValue { jsObject.jsValue() }
public init?(from value: JSValue) {
guard let object = value.object, object.isInstanceOf(Self.constructor) else { return nil }
self.init(unsafelyWrapping: object)
}
}
Loading