Skip to content

Introduce protocol-based structure for generating code #249

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 9 commits into from
Jun 10, 2025
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
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
root = true

[*.swift]
indent_style = space
indent_size = 2
insert_final_newline = true
19 changes: 19 additions & 0 deletions Sources/JExtractSwiftLib/AnalysisResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

struct AnalysisResult {
let importedTypes: [String: ImportedNominalType]
let importedGlobalVariables: [ImportedFunc]
let importedGlobalFuncs: [ImportedFunc]
}
2 changes: 2 additions & 0 deletions Sources/JExtractSwiftLib/CodePrinter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import Foundation

let PATH_SEPARATOR = "/" // TODO: Windows

public struct CodePrinter {
var contents: String = ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import JavaTypes
import SwiftSyntax

extension Swift2JavaTranslator {
extension FFMSwift2JavaGenerator {
/// Lower the given function declaration to a C-compatible entrypoint,
/// providing all of the mappings between the parameter and result types
/// of the original function and its `@_cdecl` counterpart.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

import JavaTypes

extension Swift2JavaTranslator {
public func printFunctionDowncallMethods(
extension FFMSwift2JavaGenerator {
func printFunctionDowncallMethods(
_ printer: inout CodePrinter,
_ decl: ImportedFunc
) {
Expand Down Expand Up @@ -211,7 +211,7 @@ extension Swift2JavaTranslator {
let memoryLayout = renderMemoryLayoutValue(for: outParameter.type)

let arena = if let className = outParameter.type.className,
self.importedTypes[className] != nil {
analysis.importedTypes[className] != nil {
// Use passed-in 'SwiftArena' for 'SwiftValue'.
"swiftArena$"
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import JavaTypes

extension Swift2JavaTranslator {
extension FFMSwift2JavaGenerator {
func translatedSignature(
for decl: ImportedFunc
) -> TranslatedFunctionSignature? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
import SwiftSyntax
import SwiftSyntaxBuilder

extension Swift2JavaTranslator {
public func writeSwiftThunkSources(outputDirectory: String) throws {
extension FFMSwift2JavaGenerator {
package func writeSwiftThunkSources() throws {
var printer = CodePrinter()

try writeSwiftThunkSources(outputDirectory: outputDirectory, printer: &printer)
try writeSwiftThunkSources(printer: &printer)
}

public func writeSwiftThunkSources(outputDirectory: String, printer: inout CodePrinter) throws {
package func writeSwiftThunkSources(printer: inout CodePrinter) throws {
let moduleFilenameBase = "\(self.swiftModuleName)Module+SwiftJava"
let moduleFilename = "\(moduleFilenameBase).swift"
do {
Expand All @@ -31,7 +30,7 @@ extension Swift2JavaTranslator {
try printGlobalSwiftThunkSources(&printer)

if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
outputDirectory: self.swiftOutputDirectory,
javaPackagePath: nil,
filename: moduleFilename)
{
Expand All @@ -42,7 +41,7 @@ extension Swift2JavaTranslator {
}

// === All types
for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
for (_, ty) in self.analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
let fileNameBase = "\(ty.swiftNominal.qualifiedName)+SwiftJava"
let filename = "\(fileNameBase).swift"
log.info("Printing contents: \(filename)")
Expand All @@ -51,7 +50,7 @@ extension Swift2JavaTranslator {
try printSwiftThunkSources(&printer, ty: ty)

if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
outputDirectory: self.swiftOutputDirectory,
javaPackagePath: nil,
filename: filename)
{
Expand Down Expand Up @@ -110,23 +109,23 @@ extension Swift2JavaTranslator {

struct SwiftThunkTranslator {

let st: Swift2JavaTranslator
let st: FFMSwift2JavaGenerator

init(_ st: Swift2JavaTranslator) {
init(_ st: FFMSwift2JavaGenerator) {
self.st = st
}

func renderGlobalThunks() -> [DeclSyntax] {
var decls: [DeclSyntax] = []
decls.reserveCapacity(
st.importedGlobalVariables.count + st.importedGlobalFuncs.count
st.analysis.importedGlobalVariables.count + st.analysis.importedGlobalFuncs.count
)

for decl in st.importedGlobalVariables {
for decl in st.analysis.importedGlobalVariables {
decls.append(contentsOf: render(forFunc: decl))
}

for decl in st.importedGlobalFuncs {
for decl in st.analysis.importedGlobalFuncs {
decls.append(contentsOf: render(forFunc: decl))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,90 @@ import JavaTypes
import SwiftSyntax
import SwiftSyntaxBuilder

package class FFMSwift2JavaGenerator: Swift2JavaGenerator {
let log: Logger
let analysis: AnalysisResult
let swiftModuleName: String
let javaPackage: String
let swiftOutputDirectory: String
let javaOutputDirectory: String
let swiftStdlibTypes: SwiftStandardLibraryTypes
let symbolTable: SwiftSymbolTable

var javaPackagePath: String {
javaPackage.replacingOccurrences(of: ".", with: "/")
}

var thunkNameRegistry: ThunkNameRegistry = ThunkNameRegistry()

/// Cached Java translation result. 'nil' indicates failed translation.
var translatedSignatures: [ImportedFunc: TranslatedFunctionSignature?] = [:]

package init(
translator: Swift2JavaTranslator,
javaPackage: String,
swiftOutputDirectory: String,
javaOutputDirectory: String
) {
self.log = Logger(label: "ffm-generator", logLevel: translator.log.logLevel)
self.analysis = translator.result
self.swiftModuleName = translator.swiftModuleName
self.javaPackage = javaPackage
self.swiftOutputDirectory = swiftOutputDirectory
self.javaOutputDirectory = javaOutputDirectory
self.symbolTable = translator.symbolTable
self.swiftStdlibTypes = translator.swiftStdlibTypes
}

func generate() throws {
try writeSwiftThunkSources()
print("[swift-java] Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/")

try writeExportedJavaSources()
print("[swift-java] Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/")
}
}

// ===== --------------------------------------------------------------------------------------------------------------
// MARK: Defaults

extension FFMSwift2JavaGenerator {

/// Default set Java imports for every generated file
static let defaultJavaImports: Array<String> = [
"org.swift.swiftkit.*",
"org.swift.swiftkit.SwiftKit",
"org.swift.swiftkit.util.*",

// Necessary for native calls and type mapping
"java.lang.foreign.*",
"java.lang.invoke.*",
"java.util.Arrays",
"java.util.stream.Collectors",
"java.util.concurrent.atomic.*",
"java.nio.charset.StandardCharsets",
]
}

// ==== ---------------------------------------------------------------------------------------------------------------
// MARK: File writing

let PATH_SEPARATOR = "/" // TODO: Windows

extension Swift2JavaTranslator {

/// Every imported public type becomes a public class in its own file in Java.
public func writeExportedJavaSources(outputDirectory: String) throws {
extension FFMSwift2JavaGenerator {
package func writeExportedJavaSources() throws {
var printer = CodePrinter()
try writeExportedJavaSources(outputDirectory: outputDirectory, printer: &printer)
try writeExportedJavaSources(printer: &printer)
}

public func writeExportedJavaSources(outputDirectory: String, printer: inout CodePrinter) throws {
for (_, ty) in importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
/// Every imported public type becomes a public class in its own file in Java.
package func writeExportedJavaSources(printer: inout CodePrinter) throws {
for (_, ty) in analysis.importedTypes.sorted(by: { (lhs, rhs) in lhs.key < rhs.key }) {
let filename = "\(ty.swiftNominal.name).java"
log.info("Printing contents: \(filename)")
printImportedNominal(&printer, ty)

if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
outputDirectory: javaOutputDirectory,
javaPackagePath: javaPackagePath,
filename: filename
) {
Expand All @@ -50,7 +113,7 @@ extension Swift2JavaTranslator {
printModule(&printer)

if let outputFile = try printer.writeContents(
outputDirectory: outputDirectory,
outputDirectory: javaOutputDirectory,
javaPackagePath: javaPackagePath,
filename: filename)
{
Expand All @@ -63,26 +126,26 @@ extension Swift2JavaTranslator {
// ==== ---------------------------------------------------------------------------------------------------------------
// MARK: Java/text printing

extension Swift2JavaTranslator {
extension FFMSwift2JavaGenerator {

/// Render the Java file contents for an imported Swift module.
///
/// This includes any Swift global functions in that module, and some general type information and helpers.
public func printModule(_ printer: inout CodePrinter) {
func printModule(_ printer: inout CodePrinter) {
printHeader(&printer)
printPackage(&printer)
printImports(&printer)

printModuleClass(&printer) { printer in
// TODO: print all "static" methods
for decl in importedGlobalFuncs {
for decl in analysis.importedGlobalFuncs {
self.log.trace("Print imported decl: \(decl)")
printFunctionDowncallMethods(&printer, decl)
}
}
}

package func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) {
printHeader(&printer)
printPackage(&printer)
printImports(&printer)
Expand Down Expand Up @@ -143,7 +206,7 @@ extension Swift2JavaTranslator {
}
}

public func printHeader(_ printer: inout CodePrinter) {
func printHeader(_ printer: inout CodePrinter) {
printer.print(
"""
// Generated by jextract-swift
Expand All @@ -153,7 +216,7 @@ extension Swift2JavaTranslator {
)
}

public func printPackage(_ printer: inout CodePrinter) {
func printPackage(_ printer: inout CodePrinter) {
printer.print(
"""
package \(javaPackage);
Expand All @@ -162,14 +225,14 @@ extension Swift2JavaTranslator {
)
}

public func printImports(_ printer: inout CodePrinter) {
for i in Swift2JavaTranslator.defaultJavaImports {
func printImports(_ printer: inout CodePrinter) {
for i in FFMSwift2JavaGenerator.defaultJavaImports {
printer.print("import \(i);")
}
printer.print("")
}

package func printNominal(
func printNominal(
_ printer: inout CodePrinter, _ decl: ImportedNominalType, body: (inout CodePrinter) -> Void
) {
let parentProtocol: String
Expand All @@ -188,7 +251,7 @@ extension Swift2JavaTranslator {
}
}

public func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) {
func printModuleClass(_ printer: inout CodePrinter, body: (inout CodePrinter) -> Void) {
printer.printBraceBlock("public final class \(swiftModuleName)") { printer in
printPrivateConstructor(&printer, swiftModuleName)

Expand Down Expand Up @@ -261,7 +324,7 @@ extension Swift2JavaTranslator {
}
}

private func printClassConstants(printer: inout CodePrinter) {
func printClassConstants(printer: inout CodePrinter) {
printer.print(
"""
static final String LIB_NAME = "\(swiftModuleName)";
Expand All @@ -270,7 +333,7 @@ extension Swift2JavaTranslator {
)
}

private func printPrivateConstructor(_ printer: inout CodePrinter, _ typeName: String) {
func printPrivateConstructor(_ printer: inout CodePrinter, _ typeName: String) {
printer.print(
"""
private \(typeName)() {
Expand All @@ -296,7 +359,7 @@ extension Swift2JavaTranslator {
)
}

package func printToStringMethod(
func printToStringMethod(
_ printer: inout CodePrinter, _ decl: ImportedNominalType
) {
printer.print(
Expand All @@ -312,3 +375,4 @@ extension Swift2JavaTranslator {
""")
}
}

15 changes: 10 additions & 5 deletions Sources/JExtractSwiftLib/Swift2Java.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public struct SwiftToJava {
}

let translator = Swift2JavaTranslator(
javaPackage: config.javaPackage ?? "", // no package is ok, we'd generate all into top level
swiftModuleName: swiftModule
)
translator.log.logLevel = config.logLevel ?? .info
Expand Down Expand Up @@ -89,11 +88,17 @@ public struct SwiftToJava {

try translator.analyze()

try translator.writeSwiftThunkSources(outputDirectory: outputSwiftDirectory)
print("[swift-java] Generated Swift sources (module: '\(config.swiftModule ?? "")') in: \(outputSwiftDirectory)/")
switch config.mode {
case .some(.ffm), .none:
let generator = FFMSwift2JavaGenerator(
translator: translator,
javaPackage: config.javaPackage ?? "",
swiftOutputDirectory: outputSwiftDirectory,
javaOutputDirectory: outputJavaDirectory
)

try translator.writeExportedJavaSources(outputDirectory: outputJavaDirectory)
print("[swift-java] Generated Java sources (package: '\(config.javaPackage ?? "")') in: \(outputJavaDirectory)/")
try generator.generate()
}

print("[swift-java] Imported Swift module '\(swiftModule)': " + "done.".green)
}
Expand Down
Loading