Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
}
}
}
]
],
"moduleName" : "Benchmarks"
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public func _bjs_PlayBridgeJS_deinit(pointer: UnsafeMutableRawPointer) {
Unmanaged<PlayBridgeJS>.fromOpaque(pointer).release()
}

extension PlayBridgeJS: ConvertibleToJSValue {
var jsValue: JSValue {
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJS_wrap")
func _bjs_PlayBridgeJS_wrap(_: UnsafeMutableRawPointer) -> Int32
return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJS_wrap(Unmanaged.passRetained(self).toOpaque()))))
}
}

@_expose(wasm, "bjs_PlayBridgeJSOutput_outputJs")
@_cdecl("bjs_PlayBridgeJSOutput_outputJs")
public func _bjs_PlayBridgeJSOutput_outputJs(_self: UnsafeMutableRawPointer) -> Void {
Expand Down Expand Up @@ -112,4 +120,12 @@ public func _bjs_PlayBridgeJSOutput_exportSwiftGlue(_self: UnsafeMutableRawPoint
@_cdecl("bjs_PlayBridgeJSOutput_deinit")
public func _bjs_PlayBridgeJSOutput_deinit(pointer: UnsafeMutableRawPointer) {
Unmanaged<PlayBridgeJSOutput>.fromOpaque(pointer).release()
}

extension PlayBridgeJSOutput: ConvertibleToJSValue {
var jsValue: JSValue {
@_extern(wasm, module: "PlayBridgeJS", name: "bjs_PlayBridgeJSOutput_wrap")
func _bjs_PlayBridgeJSOutput_wrap(_: UnsafeMutableRawPointer) -> Int32
return .object(JSObject(id: UInt32(bitPattern: _bjs_PlayBridgeJSOutput_wrap(Unmanaged.passRetained(self).toOpaque()))))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,6 @@
],
"functions" : [

]
],
"moduleName" : "PlayBridgeJS"
}
2 changes: 1 addition & 1 deletion Examples/PlayBridgeJS/Sources/PlayBridgeJS/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import class Foundation.JSONDecoder
}

func _update(swiftSource: String, dtsSource: String) throws -> PlayBridgeJSOutput {
let exportSwift = ExportSwift(progress: .silent)
let exportSwift = ExportSwift(progress: .silent, moduleName: "Playground")
let sourceFile = Parser.parse(source: swiftSource)
try exportSwift.addSourceFile(sourceFile, "Playground.swift")
let exportResult = try exportSwift.finalize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ struct BridgeJSBuildPlugin: BuildToolPlugin {
executable: try context.tool(named: "BridgeJSTool").url,
arguments: [
"export",
"--module-name",
target.name,
"--output-skeleton",
outputSkeletonPath.path,
"--output-swift",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ extension BridgeJSCommandPlugin.Context {
try runBridgeJSTool(
arguments: [
"export",
"--module-name",
target.name,
"--output-skeleton",
generatedJavaScriptDirectory.appending(path: "BridgeJS.ExportSwift.json").path,
"--output-swift",
Expand Down
43 changes: 41 additions & 2 deletions Plugins/BridgeJS/Sources/BridgeJSCore/ExportSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import BridgeJSSkeleton
/// JavaScript glue code and TypeScript definitions.
public class ExportSwift {
let progress: ProgressReporting
let moduleName: String

private var exportedFunctions: [ExportedFunction] = []
private var exportedClasses: [ExportedClass] = []
private var typeDeclResolver: TypeDeclResolver = TypeDeclResolver()

public init(progress: ProgressReporting) {
public init(progress: ProgressReporting, moduleName: String) {
self.progress = progress
self.moduleName = moduleName
}

/// Processes a Swift source file to find declarations marked with @JS
Expand Down Expand Up @@ -53,7 +55,11 @@ public class ExportSwift {
}
return (
outputSwift: outputSwift,
outputSkeleton: ExportedSkeleton(functions: exportedFunctions, classes: exportedClasses)
outputSkeleton: ExportedSkeleton(
moduleName: moduleName,
functions: exportedFunctions,
classes: exportedClasses
)
)
}

Expand Down Expand Up @@ -676,8 +682,41 @@ public class ExportSwift {
)
}

// Generate ConvertibleToJSValue extension
decls.append(renderConvertibleToJSValueExtension(klass: klass))

return decls
}

/// Generates a ConvertibleToJSValue extension for the exported class
///
/// # Example
///
/// For a class named `Greeter`, this generates:
///
/// ```swift
/// extension Greeter: ConvertibleToJSValue {
/// var jsValue: JSValue {
/// @_extern(wasm, module: "MyModule", name: "bjs_Greeter_wrap")
/// func _bjs_Greeter_wrap(_: UnsafeMutableRawPointer) -> Int32
/// return JSObject(id: UInt32(bitPattern: _bjs_Greeter_wrap(Unmanaged.passRetained(self).toOpaque())))
/// }
/// }
/// ```
func renderConvertibleToJSValueExtension(klass: ExportedClass) -> DeclSyntax {
let wrapFunctionName = "_bjs_\(klass.name)_wrap"
let externFunctionName = "bjs_\(klass.name)_wrap"

return """
extension \(raw: klass.name): ConvertibleToJSValue {
var jsValue: JSValue {
@_extern(wasm, module: "\(raw: moduleName)", name: "\(raw: externFunctionName)")
func \(raw: wrapFunctionName)(_: UnsafeMutableRawPointer) -> Int32
return .object(JSObject(id: UInt32(bitPattern: \(raw: wrapFunctionName)(Unmanaged.passRetained(self).toOpaque()))))
}
}
"""
}
}

extension AttributeListSyntax {
Expand Down
38 changes: 37 additions & 1 deletion Plugins/BridgeJS/Sources/BridgeJSLink/BridgeJSLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ struct BridgeJSLink {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
\(renderSwiftClassWrappers().map { $0.indent(count: 12) }.joined(separator: "\n"))
\(importObjectBuilders.flatMap { $0.importedLines }.map { $0.indent(count: 12) }.joined(separator: "\n"))
},
setInstance: (i) => {
Expand Down Expand Up @@ -249,6 +250,39 @@ struct BridgeJSLink {
return (outputJs, outputDts)
}

private func renderSwiftClassWrappers() -> [String] {
var wrapperLines: [String] = []
var modulesByName: [String: [ExportedClass]] = [:]

// Group classes by their module name
for skeleton in exportedSkeletons {
if skeleton.classes.isEmpty { continue }

if modulesByName[skeleton.moduleName] == nil {
modulesByName[skeleton.moduleName] = []
}
modulesByName[skeleton.moduleName]?.append(contentsOf: skeleton.classes)
}

// Generate wrapper functions for each module
for (moduleName, classes) in modulesByName {
wrapperLines.append("// Wrapper functions for module: \(moduleName)")
wrapperLines.append("if (!importObject[\"\(moduleName)\"]) {")
wrapperLines.append(" importObject[\"\(moduleName)\"] = {};")
wrapperLines.append("}")

for klass in classes {
let wrapperFunctionName = "bjs_\(klass.name)_wrap"
wrapperLines.append("importObject[\"\(moduleName)\"][\"\(wrapperFunctionName)\"] = function(pointer) {")
wrapperLines.append(" const obj = \(klass.name).__construct(pointer);")
wrapperLines.append(" return swift.memory.retain(obj);")
wrapperLines.append("};")
}
}

return wrapperLines
}

private func generateImportedTypeDefinitions() -> [String] {
var typeDefinitions: [String] = []

Expand Down Expand Up @@ -736,7 +770,9 @@ struct BridgeJSLink {

init(moduleName: String) {
self.moduleName = moduleName
importedLines.append("const \(moduleName) = importObject[\"\(moduleName)\"] = {};")
importedLines.append(
"const \(moduleName) = importObject[\"\(moduleName)\"] = importObject[\"\(moduleName)\"] || {};"
)
}

func assignToImportObject(name: String, function: [String]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,12 @@ public struct ExportedConstructor: Codable {
}

public struct ExportedSkeleton: Codable {
public let moduleName: String
public let functions: [ExportedFunction]
public let classes: [ExportedClass]

public init(functions: [ExportedFunction], classes: [ExportedClass]) {
public init(moduleName: String, functions: [ExportedFunction], classes: [ExportedClass]) {
self.moduleName = moduleName
self.functions = functions
self.classes = classes
}
Expand Down
6 changes: 5 additions & 1 deletion Plugins/BridgeJS/Sources/BridgeJSTool/BridgeJSTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ import TS2Skeleton
let parser = ArgumentParser(
singleDashOptions: [:],
doubleDashOptions: [
"module-name": OptionRule(
help: "The name of the module for external function references",
required: true
),
"output-skeleton": OptionRule(
help: "The output file path for the skeleton of the exported Swift APIs",
required: true
Expand All @@ -168,7 +172,7 @@ import TS2Skeleton
arguments: Array(arguments.dropFirst())
)
let progress = ProgressReporting(verbose: doubleDashOptions["verbose"] == "true")
let exporter = ExportSwift(progress: progress)
let exporter = ExportSwift(progress: progress, moduleName: doubleDashOptions["module-name"]!)
for inputFile in positionalArguments.sorted() {
let sourceURL = URL(fileURLWithPath: inputFile)
guard sourceURL.pathExtension == "swift" else { continue }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import Testing
func snapshotExport(input: String) throws {
let url = Self.inputsDirectory.appendingPathComponent(input)
let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8))
let swiftAPI = ExportSwift(progress: .silent)
let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule")
try swiftAPI.addSourceFile(sourceFile, input)
let name = url.deletingPathExtension().lastPathComponent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import Testing

@Test(arguments: collectInputs())
func snapshot(input: String) throws {
let swiftAPI = ExportSwift(progress: .silent)
let swiftAPI = ExportSwift(progress: .silent, moduleName: "TestModule")
let url = Self.inputsDirectory.appendingPathComponent(input)
let sourceFile = Parser.parse(source: try String(contentsOf: url, encoding: .utf8))
try swiftAPI.addSourceFile(sourceFile, input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_checkArray"] = function bjs_checkArray(a) {
try {
options.imports.checkArray(swift.memory.getObject(a));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_returnAnimatable"] = function bjs_returnAnimatable() {
try {
let ret = options.imports.returnAnimatable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_createDatabaseConnection"] = function bjs_createDatabaseConnection(config) {
try {
let ret = options.imports.createDatabaseConnection(swift.memory.getObject(config));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
// Wrapper functions for module: TestModule
if (!importObject["TestModule"]) {
importObject["TestModule"] = {};
}
importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) {
const obj = Greeter.__construct(pointer);
return swift.memory.retain(obj);
};
importObject["TestModule"]["bjs_Converter_wrap"] = function(pointer) {
const obj = Converter.__construct(pointer);
return swift.memory.retain(obj);
};
importObject["TestModule"]["bjs_UUID_wrap"] = function(pointer) {
const obj = UUID.__construct(pointer);
return swift.memory.retain(obj);
};

},
setInstance: (i) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) {
swift.memory.release(id);
}


},
setInstance: (i) => {
instance = i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_check"] = function bjs_check(a, b) {
try {
options.imports.check(a, b);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) {
swift.memory.release(id);
}


},
setInstance: (i) => {
instance = i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_checkNumber"] = function bjs_checkNumber() {
try {
let ret = options.imports.checkNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) {
swift.memory.release(id);
}


},
setInstance: (i) => {
instance = i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_checkString"] = function bjs_checkString(a) {
try {
const aObject = swift.memory.getObject(a);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function createInstantiator(options, swift) {
swift.memory.release(id);
}


},
setInstance: (i) => {
instance = i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
const TestModule = importObject["TestModule"] = {};

const TestModule = importObject["TestModule"] = importObject["TestModule"] || {};
TestModule["bjs_checkString"] = function bjs_checkString() {
try {
let ret = options.imports.checkString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export async function createInstantiator(options, swift) {
bjs["swift_js_release"] = function(id) {
swift.memory.release(id);
}
// Wrapper functions for module: TestModule
if (!importObject["TestModule"]) {
importObject["TestModule"] = {};
}
importObject["TestModule"]["bjs_Greeter_wrap"] = function(pointer) {
const obj = Greeter.__construct(pointer);
return swift.memory.retain(obj);
};

},
setInstance: (i) => {
Expand Down
Loading