diff --git a/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/UnitTestUtils.swift b/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/UnitTestUtils.swift index 4bb9a59a2..60fb7f34a 100644 --- a/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/UnitTestUtils.swift +++ b/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/UnitTestUtils.swift @@ -30,6 +30,13 @@ func expectObject(_ value: JSValue, file: StaticString = #file, line: UInt = #li } } +func expectArray(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> JSArrayRef { + 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 { switch value { case .function(let ref): return ref @@ -46,7 +53,7 @@ func expectBoolean(_ value: JSValue, file: StaticString = #file, line: UInt = #l } } -func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Int32 { +func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Double { switch value { case .number(let number): return number default: diff --git a/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift b/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift index b283bf141..b28f138c8 100644 --- a/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift +++ b/IntegrationTests/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift @@ -8,8 +8,10 @@ Literal_Conversion: do { .string("foobar"), .string("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง Family Emoji"), .number(0), - .number(.max), - .number(.min), + .number(Double(Int32.max)), + .number(Double(Int32.min)), + .number(Double.infinity), + .number(Double.nan), .null, .undefined, ] @@ -17,7 +19,13 @@ Literal_Conversion: do { let prop = "prop_\(index)" setJSValue(this: global, name: prop, value: input) let got = getJSValue(this: global, name: prop) - try expectEqual(got, input) + switch (got, input) { + case let (.number(lhs), .number(rhs)): + // Compare bitPattern because nan == nan is always false + try expectEqual(lhs.bitPattern, rhs.bitPattern) + default: + try expectEqual(got, input) + } } } catch { print(error) @@ -67,6 +75,50 @@ Object_Conversion: do { print(error) } +Value_Construction: do { + let globalObject1 = getJSValue(this: .global, name: "globalObject1") + let globalObject1Ref = try expectObject(globalObject1) + let prop_2 = getJSValue(this: globalObject1Ref, name: "prop_2") + try expectEqual(Int.construct(from: prop_2), 2) + let prop_3 = getJSValue(this: globalObject1Ref, name: "prop_3") + try expectEqual(Bool.construct(from: prop_3), true) + let prop_7 = getJSValue(this: globalObject1Ref, name: "prop_7") + try expectEqual(Double.construct(from: prop_7), 3.14) + try expectEqual(Float.construct(from: prop_7), 3.14) +} catch { + print(error) +} + +Array_Iterator: do { + 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 expectedProp_4: [JSValue] = [ + .number(3), .number(4), .string("str_elm_1"), .number(5) + ] + try expectEqual(Array(array), expectedProp_4) +} + +Value_Decoder: do { + struct GlobalObject1: Codable { + struct Prop1: Codable { + let nested_prop: Int + } + let prop_1: Prop1 + let prop_2: Int + let prop_3: Bool + let prop_7: Float + } + let decoder = JSValueDecoder() + let rawGlobalObject1 = getJSValue(this: .global, name: "globalObject1") + let globalObject1 = try decoder.decode(GlobalObject1.self, from: rawGlobalObject1) + try expectEqual(globalObject1.prop_1.nested_prop, 1) + try expectEqual(globalObject1.prop_2, 2) + try expectEqual(globalObject1.prop_3, true) + try expectEqual(globalObject1.prop_7, 3.14) +} + Function_Call: do { // Notes: globalObject1 is defined in JavaScript environment // @@ -154,10 +206,8 @@ Host_Function_Registration: do { } try expectEqual(hostFunc2(3), .number(6)) - // FIXME: Crash with latest toolchain -/* _ = try expectString(hostFunc2(true)) -*/ + } catch { print(error) } diff --git a/IntegrationTests/index.js b/IntegrationTests/index.js index 67cb07ba3..432226802 100644 --- a/IntegrationTests/index.js +++ b/IntegrationTests/index.js @@ -9,6 +9,20 @@ const readFile = promisify(fs.readFile); const swift = new SwiftRuntime(); // Instantiate a new WASI Instance const wasmFs = new WasmFs(); +// Output stdout and stderr to console +const originalWriteSync = wasmFs.fs.writeSync; +wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => { + const text = new TextDecoder("utf-8").decode(buffer); + switch (fd) { + case 1: + console.log(text); + break; + case 2: + console.error(text); + break; + } + return originalWriteSync(fd, buffer, offset, length, position); +}; let wasi = new WASI({ args: [], env: {}, @@ -41,7 +55,8 @@ global.globalObject1 = { "call_host_1": () => { return global.globalObject1.prop_6.host_func_1() } - } + }, + "prop_7": 3.14, } global.Animal = function(name, age, isCat) { @@ -69,10 +84,6 @@ const startWasiTask = async () => { swift.setInsance(instance); // Start the WebAssembly WASI instance! wasi.start(instance); - - // Output what's inside of /dev/stdout! - const stdout = await wasmFs.getStdOut(); - console.log(stdout); }; startWasiTask().catch(err => { console.log(err) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index d7642f323..909a0d057 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -102,11 +102,8 @@ export class SwiftRuntime { const argv = exports.swjs_prepare_host_function_call(argc) for (let index = 0; index < args.length; index++) { const argument = args[index] - const value = encodeValue(argument) - const base = argv + 12 * index - writeUint32(base, value.kind) - writeUint32(base + 4, value.payload1) - writeUint32(base + 8, value.payload2) + const base = argv + 24 * index + writeValue(argument, base, base + 4, base + 8, base + 16) } let output: any; const callback_func_ref = this.heap.allocHeap(function (result: any) { @@ -142,6 +139,11 @@ export class SwiftRuntime { + (uint8Memory[ptr + 3] << 24) } + const readFloat64 = (ptr: pointer) => { + const dataView = new DataView(memory().buffer); + return dataView.getFloat64(ptr, true); + } + const writeUint32 = (ptr: pointer, value: number) => { const uint8Memory = new Uint8Array(memory().buffer); uint8Memory[ptr + 0] = (value & 0x000000ff) >> 0 @@ -150,9 +152,14 @@ export class SwiftRuntime { uint8Memory[ptr + 3] = (value & 0xff000000) >> 24 } + const writeFloat64 = (ptr: pointer, value: number) => { + const dataView = new DataView(memory().buffer); + dataView.setFloat64(ptr, value, true); + } + const decodeValue = ( kind: JavaScriptValueKind, - payload1: number, payload2: number + payload1: number, payload2: number, payload3: number ) => { switch (kind) { case JavaScriptValueKind.Boolean: { @@ -162,7 +169,7 @@ export class SwiftRuntime { } } case JavaScriptValueKind.Number: { - return payload1 + return payload3; } case JavaScriptValueKind.String: { return readString(payload1, payload2) @@ -184,57 +191,55 @@ export class SwiftRuntime { } } - const encodeValue = (value: any) => { + const writeValue = ( + value: any, kind_ptr: pointer, + payload1_ptr: pointer, payload2_ptr: pointer, payload3_ptr: pointer + ) => { if (value === null) { - return { - kind: JavaScriptValueKind.Null, - payload1: 0, - payload2: 0, - } + writeUint32(kind_ptr, JavaScriptValueKind.Null); + writeUint32(payload1_ptr, 0); + writeUint32(payload2_ptr, 0); + return; } switch (typeof value) { case "boolean": { - return { - kind: JavaScriptValueKind.Boolean, - payload1: value ? 1 : 0, - payload2: 0, - } + writeUint32(kind_ptr, JavaScriptValueKind.Boolean); + writeUint32(payload1_ptr, value ? 1 : 0); + writeUint32(payload2_ptr, 0); + break; } case "number": { - return { - kind: JavaScriptValueKind.Number, - payload1: value, - payload2: 0, - } + writeUint32(kind_ptr, JavaScriptValueKind.Number); + writeUint32(payload1_ptr, 0); + writeUint32(payload2_ptr, 0); + writeFloat64(payload3_ptr, value); + break; } case "string": { + // FIXME: currently encode twice const bytes = textEncoder.encode(value); - return { - kind: JavaScriptValueKind.String, - payload1: this.heap.allocHeap(value), - payload2: bytes.length, - } + writeUint32(kind_ptr, JavaScriptValueKind.String); + writeUint32(payload1_ptr, this.heap.allocHeap(value)); + writeUint32(payload2_ptr, bytes.length); + break; } case "undefined": { - return { - kind: JavaScriptValueKind.Undefined, - payload1: 0, - payload2: 0, - } + writeUint32(kind_ptr, JavaScriptValueKind.Undefined); + writeUint32(payload1_ptr, 0); + writeUint32(payload2_ptr, 0); + break; } case "object": { - return { - kind: JavaScriptValueKind.Object, - payload1: this.heap.allocHeap(value), - payload2: 0, - } + writeUint32(kind_ptr, JavaScriptValueKind.Object); + writeUint32(payload1_ptr, this.heap.allocHeap(value)); + writeUint32(payload2_ptr, 0); + break; } case "function": { - return { - kind: JavaScriptValueKind.Function, - payload1: this.heap.allocHeap(value), - payload2: 0, - } + writeUint32(kind_ptr, JavaScriptValueKind.Function); + writeUint32(payload1_ptr, this.heap.allocHeap(value)); + writeUint32(payload2_ptr, 0); + break; } default: throw new Error(`Type "${typeof value}" is not supported yet`) @@ -242,16 +247,17 @@ export class SwiftRuntime { } // Note: - // `decodeValues` assumes that the size of RawJSValue is 12 - // and the alignment of it is 4 + // `decodeValues` assumes that the size of RawJSValue is 24 + // and the alignment of it is 8 const decodeValues = (ptr: pointer, length: number) => { let result = [] for (let index = 0; index < length; index++) { - const base = ptr + 12 * index + const base = ptr + 24 * index const kind = readUInt32(base) const payload1 = readUInt32(base + 4) const payload2 = readUInt32(base + 8) - result.push(decodeValue(kind, payload1, payload2)) + const payload3 = readFloat64(base + 16) + result.push(decodeValue(kind, payload1, payload2, payload3)) } return result } @@ -260,42 +266,36 @@ export class SwiftRuntime { swjs_set_prop: ( ref: ref, name: pointer, length: number, kind: JavaScriptValueKind, - payload1: number, payload2: number + payload1: number, payload2: number, payload3: number ) => { const obj = this.heap.referenceHeap(ref); - Reflect.set(obj, readString(name, length), decodeValue(kind, payload1, payload2)) + Reflect.set(obj, readString(name, length), decodeValue(kind, payload1, payload2, payload3)) }, swjs_get_prop: ( ref: ref, name: pointer, length: number, kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + payload1_ptr: pointer, payload2_ptr: pointer, payload3_ptr: number ) => { const obj = this.heap.referenceHeap(ref); const result = Reflect.get(obj, readString(name, length)); - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, payload3_ptr); }, swjs_set_subscript: ( ref: ref, index: number, kind: JavaScriptValueKind, - payload1: number, payload2: number + payload1: number, payload2: number, payload3: number ) => { const obj = this.heap.referenceHeap(ref); - Reflect.set(obj, index, decodeValue(kind, payload1, payload2)) + Reflect.set(obj, index, decodeValue(kind, payload1, payload2, payload3)) }, swjs_get_subscript: ( ref: ref, index: number, kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + payload1_ptr: pointer, payload2_ptr: pointer, payload3_ptr: pointer ) => { const obj = this.heap.referenceHeap(ref); const result = Reflect.get(obj, index); - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, payload3_ptr); }, swjs_load_string: (ref: ref, buffer: pointer) => { const string = this.heap.referenceHeap(ref); @@ -304,28 +304,22 @@ export class SwiftRuntime { swjs_call_function: ( ref: ref, argv: pointer, argc: number, kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + payload1_ptr: pointer, payload2_ptr: pointer, payload3_ptr: pointer ) => { const func = this.heap.referenceHeap(ref) const result = Reflect.apply(func, undefined, decodeValues(argv, argc)) - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, payload3_ptr); }, swjs_call_function_with_this: ( obj_ref: ref, func_ref: ref, argv: pointer, argc: number, kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + payload1_ptr: pointer, payload2_ptr: pointer, payload3_ptr: pointer ) => { const obj = this.heap.referenceHeap(obj_ref) const func = this.heap.referenceHeap(func_ref) const result = Reflect.apply(func, obj, decodeValues(argv, argc)) - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, payload3_ptr); }, swjs_create_function: ( host_func_id: number, diff --git a/Sources/JavaScriptKit/JSArrayRef.swift b/Sources/JavaScriptKit/JSArrayRef.swift new file mode 100644 index 000000000..a1eea210a --- /dev/null +++ b/Sources/JavaScriptKit/JSArrayRef.swift @@ -0,0 +1,41 @@ + +public class JSArrayRef { + + static let classObject = JSObjectRef.global.Array.function! + + static func isArray(_ object: JSObjectRef) -> Bool { + classObject.isArray.function!(object).boolean! + } + + let ref: JSObjectRef + + public init?(_ ref: JSObjectRef) { + guard Self.isArray(ref) else { return nil } + self.ref = ref + } +} + + +extension JSArrayRef: Sequence { + public typealias Element = JSValue + + public func makeIterator() -> Iterator { + Iterator(ref: ref) + } + + public class Iterator: IteratorProtocol { + let ref: JSObjectRef + var index = 0 + init(ref: JSObjectRef) { + self.ref = ref + } + public func next() -> Element? { + defer { index += 1 } + guard index < Int(ref.length.number!) else { + return nil + } + let value = ref.get(index) + return value.isNull ? nil : value + } + } +} diff --git a/Sources/JavaScriptKit/JSFunction.swift b/Sources/JavaScriptKit/JSFunction.swift index 211988d90..4d2acaf95 100644 --- a/Sources/JavaScriptKit/JSFunction.swift +++ b/Sources/JavaScriptKit/JSFunction.swift @@ -5,14 +5,14 @@ public class JSFunctionRef: JSObjectRef { @discardableResult public func dynamicallyCall(withArguments arguments: [JSValueConvertible]) -> JSValue { - let result = arguments.withRawJSValues { rawValues in - rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in + let result = arguments.withRawJSValues { rawValues -> RawJSValue in + return rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in let argv = bufferPointer.baseAddress let argc = bufferPointer.count var result = RawJSValue() _call_function( self.id, argv, Int32(argc), - &result.kind, &result.payload1, &result.payload2 + &result.kind, &result.payload1, &result.payload2, &result.payload3 ) return result } @@ -31,7 +31,7 @@ public class JSFunctionRef: JSObjectRef { var result = RawJSValue() _call_function_with_this(this.id, self.id, argv, Int32(argc), - &result.kind, &result.payload1, &result.payload2 + &result.kind, &result.payload1, &result.payload2, &result.payload3 ) return result } @@ -44,7 +44,7 @@ public class JSFunctionRef: JSObjectRef { rawValues.withUnsafeBufferPointer { bufferPointer in let argv = bufferPointer.baseAddress let argc = bufferPointer.count - var resultObj = JavaScriptPayload() + var resultObj = JavaScriptObjectRef() _call_new( self.id, argv, Int32(argc), &resultObj @@ -85,7 +85,7 @@ public func _cleanup_host_function_call(_ pointer: UnsafeMutableRawPointer) { public func _call_host_function( _ hostFuncRef: JavaScriptHostFuncRef, _ argv: UnsafePointer, _ argc: Int32, - _ callbackFuncRef: JavaScriptPayload) { + _ callbackFuncRef: JavaScriptObjectRef) { let hostFunc = JSFunctionRef.sharedFunctions[Int(hostFuncRef)] let args = UnsafeBufferPointer(start: argv, count: Int(argc)).map { $0.jsValue() diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index f52394427..3a359cf4a 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -3,7 +3,7 @@ import _CJavaScriptKit public enum JSValue: Equatable { case boolean(Bool) case string(String) - case number(Int32) + case number(Double) case object(JSObjectRef) case null case undefined @@ -22,7 +22,7 @@ public enum JSValue: Equatable { default: return nil } } - public var number: Int32? { + public var number: Double? { switch self { case let .number(number): return number default: return nil @@ -34,6 +34,9 @@ public enum JSValue: Equatable { default: return nil } } + public var array: JSArrayRef? { + object.flatMap { JSArrayRef($0) } + } public var isNull: Bool { return self == .null } public var isUndefined: Bool { return self == .undefined } public var function: JSFunctionRef? { @@ -57,7 +60,7 @@ extension JSValue: ExpressibleByStringLiteral { } extension JSValue: ExpressibleByIntegerLiteral { - public init(integerLiteral value: Int32) { + public init(integerLiteral value: Double) { self = .number(value) } } @@ -66,13 +69,13 @@ public func getJSValue(this: JSObjectRef, name: String) -> JSValue { var rawValue = RawJSValue() _get_prop(this.id, name, Int32(name.count), &rawValue.kind, - &rawValue.payload1, &rawValue.payload2) + &rawValue.payload1, &rawValue.payload2, &rawValue.payload3) return rawValue.jsValue() } public func setJSValue(this: JSObjectRef, name: String, value: JSValue) { value.withRawJSValue { rawValue in - _set_prop(this.id, name, Int32(name.count), rawValue.kind, rawValue.payload1, rawValue.payload2) + _set_prop(this.id, name, Int32(name.count), rawValue.kind, rawValue.payload1, rawValue.payload2, rawValue.payload3) } } @@ -81,7 +84,7 @@ public func getJSValue(this: JSObjectRef, index: Int32) -> JSValue { var rawValue = RawJSValue() _get_subscript(this.id, index, &rawValue.kind, - &rawValue.payload1, &rawValue.payload2) + &rawValue.payload1, &rawValue.payload2, &rawValue.payload3) return rawValue.jsValue() } @@ -90,7 +93,7 @@ public func setJSValue(this: JSObjectRef, index: Int32, value: JSValue) { value.withRawJSValue { rawValue in _set_subscript(this.id, index, rawValue.kind, - rawValue.payload1, rawValue.payload2) + rawValue.payload1, rawValue.payload2, rawValue.payload3) } } diff --git a/Sources/JavaScriptKit/JSValueConstructible.swift b/Sources/JavaScriptKit/JSValueConstructible.swift new file mode 100644 index 000000000..2a312bf65 --- /dev/null +++ b/Sources/JavaScriptKit/JSValueConstructible.swift @@ -0,0 +1,87 @@ +public protocol JSValueConstructible { + static func construct(from value: JSValue) -> Self? +} + +extension Bool: JSValueConstructible { + public static func construct(from value: JSValue) -> Bool? { + value.boolean + } +} + +extension String: JSValueConstructible { + public static func construct(from value: JSValue) -> String? { + value.string + } +} + +extension Double: JSValueConstructible { + public static func construct(from value: JSValue) -> Double? { + return value.number + } +} + +extension Float: JSValueConstructible { + public static func construct(from value: JSValue) -> Float? { + return value.number.map(Float.init) + } +} + +extension Int: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension Int8: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension Int16: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension Int32: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension Int64: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension UInt: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension UInt8: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension UInt16: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension UInt32: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} + +extension UInt64: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + value.number.map(Self.init) + } +} diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index ae6ea22ae..2c23d89dc 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -13,7 +13,7 @@ extension Bool: JSValueConvertible { } extension Int: JSValueConvertible { - public func jsValue() -> JSValue { .number(Int32(self)) } + public func jsValue() -> JSValue { .number(Double(self)) } } extension String: JSValueConvertible { @@ -69,23 +69,23 @@ extension RawJSValue: JSValueConvertible { case JavaScriptValueKind_Boolean: return .boolean(payload1 != 0) case JavaScriptValueKind_Number: - return .number(Int32(bitPattern: payload1)) + return .number(payload3) case JavaScriptValueKind_String: // +1 for null terminator let buffer = malloc(Int(payload2 + 1))!.assumingMemoryBound(to: UInt8.self) defer { free(buffer) } - _load_string(payload1 as JavaScriptObjectRef, buffer) + _load_string(JavaScriptObjectRef(payload1), buffer) buffer[Int(payload2)] = 0 let string = String(decodingCString: UnsafePointer(buffer), as: UTF8.self) return .string(string) case JavaScriptValueKind_Object: - return .object(JSObjectRef(id: payload1)) + return .object(JSObjectRef(id: UInt32(payload1))) case JavaScriptValueKind_Null: return .null case JavaScriptValueKind_Undefined: return .undefined case JavaScriptValueKind_Function: - return .function(JSFunctionRef(id: payload1)) + return .function(JSFunctionRef(id: UInt32(payload1))) default: fatalError("unreachable") } @@ -95,8 +95,9 @@ extension RawJSValue: JSValueConvertible { extension JSValue { func withRawJSValue(_ body: (RawJSValue) -> T) -> T { let kind: JavaScriptValueKind - let payload1: JavaScriptPayload - let payload2: JavaScriptPayload + let payload1: JavaScriptPayload1 + let payload2: JavaScriptPayload2 + var payload3: JavaScriptPayload3 = 0 switch self { case let .boolean(boolValue): kind = JavaScriptValueKind_Boolean @@ -104,18 +105,19 @@ extension JSValue { payload2 = 0 case let .number(numberValue): kind = JavaScriptValueKind_Number - payload1 = JavaScriptPayload(bitPattern: numberValue) + payload1 = 0 payload2 = 0 + payload3 = numberValue case var .string(stringValue): kind = JavaScriptValueKind_String return stringValue.withUTF8 { bufferPtr in let ptrValue = UInt32(UInt(bitPattern: bufferPtr.baseAddress!)) - let rawValue = RawJSValue(kind: kind, payload1: ptrValue, payload2: JavaScriptPayload(bufferPtr.count)) + let rawValue = RawJSValue(kind: kind, payload1: JavaScriptPayload1(ptrValue), payload2: JavaScriptPayload2(bufferPtr.count), payload3: 0) return body(rawValue) } case let .object(ref): kind = JavaScriptValueKind_Object - payload1 = ref.id + payload1 = JavaScriptPayload1(ref.id) payload2 = 0 case .null: kind = JavaScriptValueKind_Null @@ -127,10 +129,10 @@ extension JSValue { payload2 = 0 case let .function(functionRef): kind = JavaScriptValueKind_Function - payload1 = functionRef.id + payload1 = JavaScriptPayload1(functionRef.id) payload2 = 0 } - let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2) + let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2, payload3: payload3) return body(rawValue) } } diff --git a/Sources/JavaScriptKit/JSValueDecoder.swift b/Sources/JavaScriptKit/JSValueDecoder.swift new file mode 100644 index 000000000..8d46de31a --- /dev/null +++ b/Sources/JavaScriptKit/JSValueDecoder.swift @@ -0,0 +1,249 @@ +private struct _Decoder: Decoder { + + fileprivate let node: JSValue + + init(referencing node: JSValue, userInfo: [CodingUserInfoKey: Any], codingPath: [CodingKey] = []) { + self.node = node + self.userInfo = userInfo + self.codingPath = codingPath + } + + let codingPath: [CodingKey] + let userInfo: [CodingUserInfoKey : Any] + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer where Key : CodingKey { + guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObjectRef.self, reality: node) } + return KeyedDecodingContainer(_KeyedDecodingContainer(decoder: self, ref: ref)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + guard let ref = node.object else { throw _typeMismatch(at: codingPath, JSObjectRef.self, reality: node) } + return _UnkeyedDecodingContainer(decoder: self, ref: ref) + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + self + } + + func decoder(referencing node: JSValue, with key: CodingKey) -> _Decoder { + _Decoder(referencing: node, userInfo: userInfo, codingPath: codingPath + [key]) + } + + func superDecoder(referencing node: JSValue) -> _Decoder { + _Decoder(referencing: node, userInfo: userInfo, codingPath: codingPath.dropLast()) + } + +} + +private enum Object { + static let ref = JSObjectRef.global.get("Object").object! + static func keys(_ object: JSObjectRef) -> [String] { + let keysFn = ref.get("keys").function! + let keys = keysFn(object).array! + return keys.map { $0.string! } + } +} + +private func _keyNotFound(at codingPath: [CodingKey], _ key: CodingKey) -> DecodingError { + let description = "No value associated with key \(key) (\"\(key.stringValue)\")." + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + return .keyNotFound(key, context) +} + +private func _typeMismatch(at codingPath: [CodingKey], _ type: Any.Type, reality: Any) -> DecodingError { + let description = "Expected to decode \(type) but found \(reality) instead." + let context = DecodingError.Context(codingPath: codingPath, debugDescription: description) + return .typeMismatch(type, context) +} + +struct _JSCodingKey: CodingKey { + var stringValue: String + var intValue: Int? + + init?(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init?(intValue: Int) { + self.stringValue = "\(intValue)" + self.intValue = intValue + } + + init(index: Int) { + self.stringValue = "Index \(index)" + self.intValue = index + } + + static let `super` = _JSCodingKey(stringValue: "super")! +} + +private struct _KeyedDecodingContainer: KeyedDecodingContainerProtocol { + + private let decoder: _Decoder + private let ref: JSObjectRef + + var codingPath: [CodingKey] { return decoder.codingPath } + var allKeys: [Key] { + Object.keys(ref).compactMap(Key.init(stringValue: )) + } + + init(decoder: _Decoder, ref: JSObjectRef) { + self.decoder = decoder + self.ref = ref + } + + func _decode(forKey key: CodingKey) throws -> JSValue { + let result = ref.get(key.stringValue) + guard !result.isUndefined else { + throw _keyNotFound(at: codingPath, key) + } + return result + } + + func _throwTypeMismatchIfNil(forKey key: CodingKey, _ transform: (JSValue) -> T?) throws -> T { + let jsValue = try _decode(forKey: key) + guard let value = transform(jsValue) else { + throw _typeMismatch(at: codingPath, T.self, reality: jsValue) + } + return value + } + + func contains(_ key: Key) -> Bool { + !ref.get(key.stringValue).isUndefined + } + + func decodeNil(forKey key: Key) throws -> Bool { + try _decode(forKey: key).isNull + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T: JSValueConstructible & Decodable { + return try _throwTypeMismatchIfNil(forKey: key) { T.construct(from: $0) } + } + + func decode(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable { + return try T(from: _decoder(forKey: key)) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + try _decoder(forKey: key).container(keyedBy: NestedKey.self) + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + try _decoder(forKey: key).unkeyedContainer() + } + + func superDecoder() throws -> Decoder { + try _decoder(forKey: _JSCodingKey.super) + } + + func superDecoder(forKey key: Key) throws -> Decoder { + try _decoder(forKey: key) + } + + func _decoder(forKey key: CodingKey) throws -> Decoder { + let value = try _decode(forKey: key) + return decoder.decoder(referencing: value, with: key) + } +} + +private struct _UnkeyedDecodingContainer: UnkeyedDecodingContainer { + var codingPath: [CodingKey] { decoder.codingPath } + let count: Int? + var isAtEnd: Bool { currentIndex >= count ?? 0 } + var currentIndex: Int = 0 + + private var currentKey: CodingKey { return _JSCodingKey(index: currentIndex) } + + let decoder: _Decoder + let ref: JSObjectRef + + init(decoder: _Decoder, ref: JSObjectRef) { + self.decoder = decoder + self.count = ref.length.number.map(Int.init) + self.ref = ref + } + + mutating func _currentValue() -> JSValue { + defer { currentIndex += 1 } + return ref.get(currentIndex) + } + + mutating func _throwTypeMismatchIfNil(_ transform: (JSValue) -> T?) throws -> T { + let value = _currentValue() + guard let jsValue = transform(value) else { + throw _typeMismatch(at: codingPath, T.self, reality: value) + } + return jsValue + } + + mutating func decodeNil() throws -> Bool { + return _currentValue().isNull + } + + mutating func decode(_ type: T.Type) throws -> T where T: JSValueConstructible & Decodable { + try _throwTypeMismatchIfNil { T.construct(from: $0) } + } + + mutating func decode(_ type: T.Type) throws -> T where T : Decodable { + return try T(from: _decoder()) + } + + mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey : CodingKey { + return try _decoder().container(keyedBy: NestedKey.self) + } + + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + return try _decoder().unkeyedContainer() + } + + mutating func superDecoder() throws -> Decoder { + _decoder() + } + + mutating func _decoder() -> Decoder { + decoder.decoder(referencing: _currentValue(), with: currentKey) + } +} + + +extension _Decoder: SingleValueDecodingContainer { + func _throwTypeMismatchIfNil(_ transform: (JSValue) -> T?) throws -> T { + guard let jsValue = transform(node) else { + throw _typeMismatch(at: codingPath, T.self, reality: node) + } + return jsValue + } + + func decodeNil() -> Bool { + node.isNull + } + + func decode(_ type: T.Type) throws -> T where T: JSValueConstructible & Decodable { + try _throwTypeMismatchIfNil { T.construct(from: $0) } + } + + func decode(_ type: T.Type) throws -> T where T : Decodable { + let primitive = { (node: JSValue) -> T? in + guard let constructibleType = type as? JSValueConstructible.Type else { + return nil + } + return constructibleType.construct(from: node) as? T + } + return try primitive(node) ?? type.init(from: self) + } +} + +public class JSValueDecoder { + + public init() {} + + public func decode( + _ type: T.Type = T.self, + from value: JSValue, + userInfo: [CodingUserInfoKey: Any] = [:] + ) throws -> T where T: Decodable { + let decoder = _Decoder(referencing: value, userInfo: userInfo) + return try T(from: decoder) + } +} diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 96abee01f..a2a2318ce 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -10,26 +10,30 @@ func _set_prop( _ _this: JavaScriptObjectRef, _ prop: UnsafePointer!, _ length: Int32, _ kind: JavaScriptValueKind, - _ payload1: JavaScriptPayload, - _ payload2: JavaScriptPayload) { fatalError() } + _ payload1: JavaScriptPayload1, + _ payload2: JavaScriptPayload2, + _ payload3: JavaScriptPayload3) { fatalError() } func _get_prop( _ _this: JavaScriptObjectRef, _ prop: UnsafePointer!, _ length: Int32, _ kind: UnsafeMutablePointer!, - _ payload1: UnsafeMutablePointer!, - _ payload2: UnsafeMutablePointer!) { fatalError() } + _ payload1: UnsafeMutablePointer!, + _ payload2: UnsafeMutablePointer!, + _ payload3: UnsafeMutablePointer!) { fatalError() } func _set_subscript( _ _this: JavaScriptObjectRef, _ index: Int32, _ kind: JavaScriptValueKind, - _ payload1: JavaScriptPayload, - _ payload2: JavaScriptPayload) { fatalError() } + _ payload1: JavaScriptPayload1, + _ payload2: JavaScriptPayload2, + _ payload3: JavaScriptPayload3) { fatalError() } func _get_subscript( _ _this: JavaScriptObjectRef, _ index: Int32, _ kind: UnsafeMutablePointer!, - _ payload1: UnsafeMutablePointer!, - _ payload2: UnsafeMutablePointer!) { fatalError() } + _ payload1: UnsafeMutablePointer!, + _ payload2: UnsafeMutablePointer!, + _ payload3: UnsafeMutablePointer!) { fatalError() } func _load_string( _ ref: JavaScriptObjectRef, _ buffer: UnsafeMutablePointer!) { fatalError() } @@ -37,19 +41,21 @@ func _call_function( _ ref: JavaScriptObjectRef, _ argv: UnsafePointer!, _ argc: Int32, _ result_kind: UnsafeMutablePointer!, - _ result_payload1: UnsafeMutablePointer!, - _ result_payload2: UnsafeMutablePointer!) { fatalError() } + _ result_payload1: UnsafeMutablePointer!, + _ result_payload2: UnsafeMutablePointer!, + _ result_payload3: UnsafeMutablePointer!) { fatalError() } func _call_function_with_this( _ _this: JavaScriptObjectRef, _ func_ref: JavaScriptObjectRef, _ argv: UnsafePointer!, _ argc: Int32, _ result_kind: UnsafeMutablePointer!, - _ result_payload1: UnsafeMutablePointer!, - _ result_payload2: UnsafeMutablePointer!) { fatalError() } + _ result_payload1: UnsafeMutablePointer!, + _ result_payload2: UnsafeMutablePointer!, + _ result_payload3: UnsafeMutablePointer!) { fatalError() } func _call_new( _ ref: JavaScriptObjectRef, _ argv: UnsafePointer!, _ argc: Int32, - _ result_obj: UnsafeMutablePointer!) { fatalError() } + _ result_obj: UnsafeMutablePointer!) { fatalError() } func _create_function( _ host_func_id: JavaScriptHostFuncRef, _ func_ref_ptr: UnsafePointer!) { fatalError() } diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index fb81f928f..d79ec38b5 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -17,12 +17,15 @@ typedef enum { JavaScriptValueKind_Function = 6, } JavaScriptValueKind; -typedef unsigned JavaScriptPayload; +typedef unsigned JavaScriptPayload1; +typedef unsigned JavaScriptPayload2; +typedef double JavaScriptPayload3; typedef struct { JavaScriptValueKind kind; - JavaScriptPayload payload1; - JavaScriptPayload payload2; + JavaScriptPayload1 payload1; + JavaScriptPayload2 payload2; + JavaScriptPayload3 payload3; } RawJSValue; @@ -36,8 +39,9 @@ extern void _set_prop( const JavaScriptObjectRef _this, const char *prop, const int length, const JavaScriptValueKind kind, - const JavaScriptPayload payload1, - const JavaScriptPayload payload2 + const JavaScriptPayload1 payload1, + const JavaScriptPayload2 payload2, + const JavaScriptPayload3 payload3 ); __attribute__(( @@ -48,8 +52,9 @@ extern void _get_prop( const JavaScriptObjectRef _this, const char *prop, const int length, JavaScriptValueKind *kind, - JavaScriptPayload *payload1, - JavaScriptPayload *payload2 + JavaScriptPayload1 *payload1, + JavaScriptPayload2 *payload2, + JavaScriptPayload3 *payload3 ); __attribute__(( @@ -60,8 +65,9 @@ extern void _set_subscript( const JavaScriptObjectRef _this, const int length, const JavaScriptValueKind kind, - const JavaScriptPayload payload1, - const JavaScriptPayload payload2 + const JavaScriptPayload1 payload1, + const JavaScriptPayload2 payload2, + const JavaScriptPayload3 payload3 ); __attribute__(( @@ -72,8 +78,9 @@ extern void _get_subscript( const JavaScriptObjectRef _this, const int length, JavaScriptValueKind *kind, - JavaScriptPayload *payload1, - JavaScriptPayload *payload2 + JavaScriptPayload1 *payload1, + JavaScriptPayload2 *payload2, + JavaScriptPayload3 *payload3 ); __attribute__(( @@ -93,8 +100,9 @@ extern void _call_function( const JavaScriptObjectRef ref, const RawJSValue *argv, const int argc, JavaScriptValueKind *result_kind, - JavaScriptPayload *result_payload1, - JavaScriptPayload *result_payload2 + JavaScriptPayload1 *result_payload1, + JavaScriptPayload2 *result_payload2, + JavaScriptPayload3 *result_payload3 ); __attribute__(( @@ -106,8 +114,9 @@ extern void _call_function_with_this( const JavaScriptObjectRef func_ref, const RawJSValue *argv, const int argc, JavaScriptValueKind *result_kind, - JavaScriptPayload *result_payload1, - JavaScriptPayload *result_payload2 + JavaScriptPayload1 *result_payload1, + JavaScriptPayload2 *result_payload2, + JavaScriptPayload3 *result_payload3 ); __attribute__(( @@ -117,7 +126,7 @@ __attribute__(( extern void _call_new( const JavaScriptObjectRef ref, const RawJSValue *argv, const int argc, - JavaScriptPayload *result_obj + JavaScriptObjectRef *result_obj ); __attribute__((