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 16 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
42 changes: 25 additions & 17 deletions Sources/JavaScriptKit/BasicObjects/JSArray.swift
Original file line number Diff line number Diff line change
@@ -1,48 +1,56 @@
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!
}

let ref: JSObject
public let jsObject: JSObject

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

public convenience init?(_ jsObject: JSObject) {
guard Self.isArray(jsObject) else { return nil }
self.init(withCompatibleObject: jsObject)
}
public required init(withCompatibleObject 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
let jsObject: JSObject
var index = 0
init(ref: JSObject) {
self.ref = ref
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 @@ -62,14 +70,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(withCompatibleObject 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(withCompatibleObject jsObject: JSObject) {
self.jsObject = jsObject
}

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

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 kind of typed array that should be created on the JS side
static var typedArrayKind: JavaScriptTypedArrayKind { get }
/// The constructor function for the TypedArray class for this particular kind of number
static var typedArrayClass: JSFunction { get }
}

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.
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
}

public init?(_ object: JSObject) {
guard object.isInstanceOf(Element.typedArrayClass) else { return nil }
self.ref = object
/// Create a TypedArray with the provided number of elements allocated. All the elements will be initialized to zero.
public init(length: Int) {
jsObject = Element.typedArrayClass.new(length)
}

public convenience init(length: Int) {
let jsObject = Element.typedArrayClass.new(length)
self.init(unsafe: jsObject)
required public init(withCompatibleObject jsObject: JSObject) {
self.jsObject = jsObject
}

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

/// Convert an array of numbers into a JavaScript TypedArray
public convenience init(_ array: [Element]) {
var resultObj = JavaScriptObjectRef()
array.withUnsafeBufferPointer { ptr in
_create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj)
}
self.init(unsafe: JSObject(id: resultObj))
self.init(withCompatibleObject: JSObject(id: resultObj))
}

public convenience init(_ stride: StrideTo<Element>) where Element: Strideable {
Expand Down
18 changes: 14 additions & 4 deletions Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,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 @@ -57,6 +54,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 Expand Up @@ -89,6 +90,15 @@ public class JSClosure: JSFunction {
}
}

public required convenience init(jsValue: JSValue) {
switch jsValue {
case let .function(fun):
self.init { fun(arguments: $0) }
default:
fatalError()
}
}

public func release() {
Self.sharedFunctions[hostFuncRef] = nil
isReleased = true
Expand Down
11 changes: 10 additions & 1 deletion Sources/JavaScriptKit/FundamentalObjects/JSObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ public class JSObject: Equatable {
}

@_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)
}
}

@_disfavoredOverload
public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? {
self[name]
}

public subscript(dynamicMember name: String) -> JSValue {
get { self[name] }
set { self[name] = newValue }
Expand Down Expand Up @@ -43,6 +48,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`
var jsObject: JSObject { get }

/// Create an instannce wrapping the given JavaScript object.
/// You may assume that `jsObject instanceof Self.constructor`
init(withCompatibleObject 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(withCompatibleObject: object)
}
}
Loading