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

A first stab... #3

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

.DS_Store

## User settings
xcuserdata/

Expand Down
22 changes: 21 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ let package = Package(
.library(name: "Lexicon", targets: ["Lexicon"]),
.library(name: "SwiftLexicon", targets: ["SwiftLexicon"]),
.library(name: "SwiftStandAlone", targets: ["SwiftStandAlone"]),
.library(name: "KotlinStandAlone", targets: ["KotlinStandAlone"]),
.library(name: "LexiconGenerators", targets: ["LexiconGenerators"]),
],
dependencies: [
Expand Down Expand Up @@ -70,7 +71,7 @@ let package = Package(
]
),

// MARK:
// MARK: SwiftStandAlone

.target(
name: "SwiftStandAlone",
Expand All @@ -88,5 +89,24 @@ let package = Package(
.copy("Resources"),
]
),

// MARK: KotlinStandAlones

.target(
name: "KotlinStandAlone",
dependencies: [
"Lexicon",
]
),
.testTarget(
name: "KotlinStandAloneTests",
dependencies: [
"Hope",
"KotlinStandAlone"
],
resources: [
.copy("Resources"),
]
),
]
)
110 changes: 110 additions & 0 deletions Sources/KotlinStandAlone/Generator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//
// github.com/screensailor 2022
//

import Lexicon
import UniformTypeIdentifiers

public enum Generator: CodeGenerator {

// TODO: prefixes?

public static let utType = UTType.sourceCode

public static func generate(_ json: Lexicon.Graph.JSON) throws -> Data {
guard let o = json.swift().data(using: .utf8) else {
throw "Failed to generate Swift file"
}
return o
}
}

private extension Lexicon.Graph.JSON {

func swift() -> String {

return """
interface I: TypeLocalized, SourceCodeIdentifiable

interface TypeLocalized {
val localized: String
}

interface SourceCodeIdentifiable {
val identifier: String
}

val SourceCodeIdentifiable.debugDescription get() = identifier

sealed interface CallAsFunctionExtension<X> {
class From<X>: CallAsFunctionExtension<X>
}

fun <X: I> CallAsFunctionExtension<X>.id(): (I) -> String = { it.identifier }

open class L(override val localized: String = "", override val identifier: String,) : I

// MARK: generated types

val \(name) = L_\(name)("\(name)")

\(classes.flatMap{ $0.swift(prefix: ("L", "I")) }.joined(separator: "\n"))
"""
}
}

private extension Lexicon.Graph.Node.Class.JSON {

// TODO: make this more readable

func swift(prefix: (class: String, protocol: String)) -> [String] {

guard mixin == nil else {
return []
}

var lines: [String] = []
let T = id.idToClassSuffix
let (L, I) = prefix

if let protonym = protonym {
lines += "typealias \(L)_\(T) = \(L)_\(protonym.idToClassSuffix)"
return lines
}

lines += "data class \(L)_\(T)(override val identifier: String): L(identifier = identifier), \(I)_\(T)"

let supertype = supertype?
.replacingOccurrences(of: "_", with: "__")
.replacingOccurrences(of: ".", with: "_")
.replacingOccurrences(of: "__&__", with: ", I_")

lines += "interface \(I)_\(T): \(I)\(supertype.map{ "_\($0)" } ?? "")"

guard hasProperties else {
return lines
}

for child in children ?? [] {
let id = "\(id).\(child)"
lines += "\tval \(I)_\(T).`\(child)`: \(L)_\(id.idToClassSuffix) get() = \(L)_\(id.idToClassSuffix)(\"${identifier}.\(child)\")"
}

for (synonym, protonym) in (synonyms?.sortedByLocalizedStandard(by: \.key) ?? []) {
let id = "\(id).\(synonym)"
lines += "\tval \(I)_\(T).`\(synonym)`: \(L)_\(id.idToClassSuffix) get() = \(protonym)"
}

return lines
}

}

private extension String {

var idToClassSuffix: String {
replacingOccurrences(of: "_", with: "__")
.replacingOccurrences(of: ".", with: "_")
.replacingOccurrences(of: "_&_", with: "_")
}
}
25 changes: 25 additions & 0 deletions Tests/KotlinStandAloneTests/KotlinStandAlone™.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// github.com/screensailor 2022
//

@_exported import Hope
@_exported import Combine
@_exported import Lexicon
@_exported import KotlinStandAlone

final class KotlinLexicon™: Hopes {

func test_generator() async throws {

var json = try await "test".taskpaper().lexicon().json()
json.date = Date(timeIntervalSinceReferenceDate: 0)

let code = try Generator.generate(json).string()

try hope(code) == "test.kt".file().string()
}

func test_code() throws {
// Probably need a Kotlin project/repo for this bit, did test it though and it works fine
}
}
63 changes: 63 additions & 0 deletions Tests/KotlinStandAloneTests/Resources/test.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

interface I: TypeLocalized, SourceCodeIdentifiable

interface TypeLocalized {
val localized: String
}

interface SourceCodeIdentifiable {
val identifier: String
}

val SourceCodeIdentifiable.debugDescription get() = identifier

sealed interface CallAsFunctionExtension<X> {
class From<X>: CallAsFunctionExtension<X>
}

fun <X: I> CallAsFunctionExtension<X>.id(): (I) -> String = { it.identifier }

open class L(override val localized: String = "", override val identifier: String,) : I

// MARK: generated types

val test = L_test("test")

data class L_test(override val identifier: String): L(identifier = identifier), I_test
interface I_test: I
val I_test.`one`: L_test_one get() = L_test_one("${identifier}.one")
val I_test.`two`: L_test_two get() = L_test_two("${identifier}.two")
val I_test.`type`: L_test_type get() = L_test_type("${identifier}.type")
data class L_test_one(override val identifier: String): L(identifier = identifier), I_test_one
interface I_test_one: I_test_type_odd
val I_test_one.`more`: L_test_one_more get() = L_test_one_more("${identifier}.more")
data class L_test_one_more(override val identifier: String): L(identifier = identifier), I_test_one_more
interface I_test_one_more: I
val I_test_one_more.`time`: L_test_one_more_time get() = L_test_one_more_time("${identifier}.time")
data class L_test_one_more_time(override val identifier: String): L(identifier = identifier), I_test_one_more_time
interface I_test_one_more_time: I_test
data class L_test_two(override val identifier: String): L(identifier = identifier), I_test_two
interface I_test_two: I_test_type_even
val I_test_two.`timing`: L_test_two_timing get() = L_test_two_timing("${identifier}.timing")
data class L_test_two_timing(override val identifier: String): L(identifier = identifier), I_test_two_timing
interface I_test_two_timing: I
data class L_test_type(override val identifier: String): L(identifier = identifier), I_test_type
interface I_test_type: I
val I_test_type.`even`: L_test_type_even get() = L_test_type_even("${identifier}.even")
val I_test_type.`odd`: L_test_type_odd get() = L_test_type_odd("${identifier}.odd")
data class L_test_type_even(override val identifier: String): L(identifier = identifier), I_test_type_even
interface I_test_type_even: I
val I_test_type_even.`no`: L_test_type_even_no get() = L_test_type_even_no("${identifier}.no")
val I_test_type_even.`bad`: L_test_type_even_bad get() = no.good
typealias L_test_type_even_bad = L_test_type_even_no_good
data class L_test_type_even_no(override val identifier: String): L(identifier = identifier), I_test_type_even_no
interface I_test_type_even_no: I
val I_test_type_even_no.`good`: L_test_type_even_no_good get() = L_test_type_even_no_good("${identifier}.good")
data class L_test_type_even_no_good(override val identifier: String): L(identifier = identifier), I_test_type_even_no_good
interface I_test_type_even_no_good: I
data class L_test_type_odd(override val identifier: String): L(identifier = identifier), I_test_type_odd
interface I_test_type_odd: I
val I_test_type_odd.`good`: L_test_type_odd_good get() = L_test_type_odd_good("${identifier}.good")
data class L_test_type_odd_good(override val identifier: String): L(identifier = identifier), I_test_type_odd_good
interface I_test_type_odd_good: Is

17 changes: 17 additions & 0 deletions Tests/KotlinStandAloneTests/Resources/test.taskpaper
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
test:
one:
+ test.type.odd
more:
time:
+ test
two:
+ test.type.even
timing:
type:
even:
bad:
= no.good
no:
good:
odd:
good:
30 changes: 30 additions & 0 deletions Tests/KotlinStandAloneTests/util.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// github.com/screensailor 2022
//

import Foundation

extension String {

func taskpaper() throws -> String {
try "\(self).taskpaper".file().string()
}

func file() throws -> Data {
guard let url = Bundle.module.url(forResource: "Resources/\(self)", withExtension: nil) else {
throw "Could not find '\(self)'"
}
return try Data(contentsOf: url)
}

func lexicon() async throws -> Lexicon {
try await Lexicon.from(TaskPaper(self).decode())
}
}

extension Data {

func string(encoding: String.Encoding = .utf8) throws -> String {
try String(data: self, encoding: encoding).try()
}
}