Skip to content
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

Add JSBridgedType and JSBridgedClass #26

Merged
merged 26 commits into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from 13 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
j-f1 marked this conversation as resolved.
Show resolved Hide resolved
var index = 0
j-f1 marked this conversation as resolved.
Show resolved Hide resolved
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
30 changes: 10 additions & 20 deletions Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,24 @@ public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible {
static var typedArrayClass: JSFunction { get }
}

public class JSTypedArray<Element>: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement {
let ref: JSObject
public func jsValue() -> JSValue {
.object(ref)
}

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
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...) {
Expand All @@ -48,7 +38,7 @@ public class JSTypedArray<Element>: JSValueConvertible, ExpressibleByArrayLitera
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()
}
}

j-f1 marked this conversation as resolved.
Show resolved Hide resolved
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
31 changes: 31 additions & 0 deletions Sources/JavaScriptKit/JSBridgedType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Use this protocol when your type has no single JavaScript class.
// For example, a union type of multiple classes.
public protocol JSBridgedType: JSValueCodable, CustomStringConvertible {
var value: JSValue { get }
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 }
}


public protocol JSBridgedClass: JSBridgedType {
static var constructor: JSFunction { get }
var jsObject: JSObject { get }
init(withCompatibleObject jsObject: JSObject)
j-f1 marked this conversation as resolved.
Show resolved Hide resolved
}

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)
}
}
83 changes: 80 additions & 3 deletions Sources/JavaScriptKit/JSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,51 @@ public enum JSValue: Equatable {
}
}

public var isNull: Bool { return self == .null }
public var isUndefined: Bool { return self == .undefined }
public var function: JSFunction? {
switch self {
case let .function(function): return function
default: return nil
}
}

public var isBoolean: Bool {
guard case .boolean = self else { return false }
return true
}

public var isString: Bool {
guard case .string = self else { return false }
return true
}

public var isNumber: Bool {
guard case .number = self else { return false }
return true
}

public var isObject: Bool {
guard case .object = self else { return false }
return true
}

public var isNull: Bool {
return self == .null
}

public var isUndefined: Bool {
return self == .undefined
}

public var isFunction: Bool {
guard case .function = self else { return false }
return true
}
}

extension JSValue {
public func fromJSValue<Type>() -> Type? where Type: JSValueConstructible {
return Type.construct(from: self)
}
}

extension JSValue {
Expand All @@ -60,7 +97,13 @@ extension JSValue: ExpressibleByStringLiteral {
}

extension JSValue: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Double) {
public init(integerLiteral value: Int32) {
self = .number(Double(value))
}
}

extension JSValue: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .number(value)
}
}
Expand Down Expand Up @@ -94,3 +137,37 @@ public func setJSValue(this: JSObject, index: Int32, value: JSValue) {
rawValue.payload1, rawValue.payload2, rawValue.payload3)
}
}

extension JSValue {
public func isInstanceOf(_ constructor: JSFunction) -> Bool {
j-f1 marked this conversation as resolved.
Show resolved Hide resolved
switch self {
case .boolean, .string, .number:
return false
case let .object(ref):
return ref.isInstanceOf(constructor)
case let .function(ref):
return ref.isInstanceOf(constructor)
case .null, .undefined:
fatalError()
}
}
}

extension JSValue: CustomStringConvertible {
public var description: String {
switch self {
case let .boolean(boolean):
return boolean.description
case .string(let string):
return string
case .number(let number):
return number.description
case .object(let object), .function(let object as JSObject):
return object.toString!().fromJSValue()!
case .null:
return "null"
case .undefined:
return "undefined"
}
}
}
Loading