Skip to content

Commit

Permalink
Merge pull request #32 from k-kohey/refactor/swift-syntax-builder
Browse files Browse the repository at this point in the history
Refactor EventGen with SwiftSyntaxBuilder
  • Loading branch information
k-kohey authored May 26, 2023
2 parents 1b99789 + 632aac6 commit b58e1eb
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 76 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ on:

jobs:
build:
runs-on: macos-12
runs-on: macos-13

steps:
- uses: actions/checkout@v3
- name: Build
run: |
swift build -v
swift build -v --package-path EventGen
# EventGen requires Xcode14.3
# swift build -v --package-path EventGen
- name: Run tests
run: |
swift test -v
swift test -v --package-path EventGen
# EventGen requires Xcode14.3
# swift test -v --package-path EventGen
18 changes: 18 additions & 0 deletions EventGen/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"pins" : [
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
"version" : "0.14.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
Expand All @@ -25,6 +34,15 @@
"state" : {
"revision" : "87ae1a8fa9180b85630c7b41ddd5aa40ffc87ce3"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "cd793adf5680e138bf2bcbaacc292490175d0dcd",
"version" : "508.0.0"
}
}
],
"version" : 2
Expand Down
6 changes: 4 additions & 2 deletions EventGen/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-markdown", revision: "87ae1a8fa9180b85630c7b41ddd5aa40ffc87ce3"),
.package(name: "Parchment", path: "../")
.package(name: "Parchment", path: "../"),
.package(url: "https://github.com/apple/swift-syntax.git", exact: "508.0.0")
],
targets: [
.executableTarget(
Expand All @@ -26,7 +27,8 @@ let package = Package(
name: "EventGenKit",
dependencies: [
.product(name: "Markdown", package: "swift-markdown"),
.product(name: "Parchment", package: "Parchment")
.product(name: "Parchment", package: "Parchment"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax")
]
),
.testTarget(
Expand Down
254 changes: 199 additions & 55 deletions EventGen/Sources/EventGenKit/SwiftGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,105 +7,249 @@

import Foundation
import Parchment
import SwiftSyntax
import SwiftSyntaxBuilder

struct GeneratedEvent: Loggable {
let eventName: String
let parameters: [String : Sendable]

static func token<T>(_ keyPath: KeyPath<Self, T>) -> TokenSyntax {
switch keyPath {
case \.eventName:
return .identifier("eventName")
case \.parameters:
return .identifier("parameters")
default:
fatalError()
}
}

static func identifierPattern<T>(
_ keyPath: KeyPath<Self, T>
) -> IdentifierPatternSyntax {
switch keyPath {
case \.eventName:
return "eventName"
case \.parameters:
return "parameters"
default:
fatalError()
}
}
}

public struct SwiftGenerator {
enum Error: Swift.Error {
case argumentsIsEmpty
}

public init() {}
public init() {

}

public func run(with definisions: [EventDefinision]) throws -> String {
guard !definisions.isEmpty else { throw Error.argumentsIsEmpty }
return generate(with: definisions)
}

private func generate(with definisions: [EventDefinision]) -> String {
"""
// This file is automatically generated by eventgen.
// Do not edit this file.
SourceFileSyntax {
ImportDeclSyntax(path: [.init(name: "Parchment")])
generatedEventStructDecl
extensionDecl(with: definisions)
}.formatted().description
}

struct GeneratedEvent: \(Loggable.self) {
let eventName: String
let paramerters: [String: Sendasble]
private var generatedEventStructDecl: StructDeclSyntax {
StructDeclSyntax(
identifier: "\(GeneratedEvent.self)",
inheritanceClause: TypeInheritanceClauseSyntax {
InheritedTypeSyntax(
typeName: SimpleTypeIdentifierSyntax(
stringLiteral: "\(Loggable.self)"
)
)
}
) {
VariableDeclSyntax(
.let,
name: GeneratedEvent.identifierPattern(\.eventName),
type: TypeAnnotationSyntax(
type: SimpleTypeIdentifierSyntax(stringLiteral: "\(String.self)")
)
)
VariableDeclSyntax(
.let,
name: GeneratedEvent.identifierPattern(\.parameters),
type: TypeAnnotationSyntax(
type: DictionaryTypeSyntax(
keyType: SimpleTypeIdentifierSyntax(
stringLiteral: "\(String.self)"
),
valueType: SimpleTypeIdentifierSyntax(
stringLiteral: "Sendable"
)
)
)
)
}
}

extension GeneratedEvent {
\(indented: generateFunc(with: definisions.filter { !$0.properties.isEmpty }))
\(indented: generateProperty(with: definisions.filter { $0.properties.isEmpty }))
private func extensionDecl(with definisions: [EventDefinision]) -> ExtensionDeclSyntax {
ExtensionDeclSyntax(
extendedType: SimpleTypeIdentifierSyntax(
stringLiteral: "\(GeneratedEvent.self)"
)
) {
for definision in definisions {
if definision.properties.isEmpty {
propertyEventDecl(with: definision)
} else {
functionEventDecl(with: definision)
}

}
}
"""
}

private func generateFunc(with definision: EventDefinision) -> String {
"""
\(generateCodeDocument(with: definision))
static func \(definision.name)\(generateTupple(with: definision.properties)) -> Self {
.init(
eventName: "\(definision.name)",
parameters: \(generateParamertersDictonary(with: definision))
private func propertyEventDecl(with definision: EventDefinision) -> VariableDeclSyntax {
VariableDeclSyntax(
leadingTrivia: .init(
pieces: generateCodeDocument(with: definision)
),
modifiers: ModifierListSyntax {
DeclModifierSyntax(name: .static)
},
name: .init(stringLiteral: definision.name),
type: TypeAnnotationSyntax(
type: SimpleTypeIdentifier(stringLiteral: "Self")
)
) {
generatedEventInitialization(with: definision)
}
"""
}

private func generateProperty(with definision: EventDefinision) -> String {
"""
\(generateCodeDocument(with: definision))
static var \(definision.name): Self {
.init(eventName: \(definision.name), parameters: [:])
private func functionEventDecl(
with definision: EventDefinision
) -> FunctionDeclSyntax {
FunctionDeclSyntax(
leadingTrivia: .init(
pieces: generateCodeDocument(with: definision)
),
modifiers: ModifierListSyntax {
DeclModifierSyntax(name: .static)
},
identifier: .identifier(definision.name),
signature: FunctionSignatureSyntax(
input: ParameterClauseSyntax {
for property in definision.properties {
FunctionParameterSyntax(
firstName: .identifier(property.name),
colon: .colon,
type: typeSyntax(
property.type, isNullable: property.nullable
)
)
}
},
output: ReturnClause(
returnType: SimpleTypeIdentifierSyntax(
stringLiteral: "Self"
)
)
)
) {
functionEventBodyDecl(with: definision)
}
"""
}

private func generateFunc(with definisions: [EventDefinision]) -> String {
definisions.map(generateFunc(with:)).joined(separator: "\n")
private func functionEventBodyDecl(with definision: EventDefinision) -> CodeBlockItemListSyntax {
CodeBlockItemListSyntax {
generatedEventInitialization(with: definision)
}
}

private func generateProperty(with definisions: [EventDefinision]) -> String {
definisions.map(generateProperty(with:)).joined(separator: "\n")
private func generatedEventInitialization(
with definision: EventDefinision
) -> FunctionCallExprSyntax {
FunctionCallExprSyntax(
callee: IdentifierExprSyntax(stringLiteral: "\(GeneratedEvent.self)")
) {
TupleExprElementSyntax(
label: GeneratedEvent.token(\.eventName),
colon: .colon,
expression: StringLiteralExprSyntax(
content: definision.name
)
)
TupleExprElementSyntax(
label: GeneratedEvent.token(\.parameters),
colon: .colon,
expression: DictionaryExprSyntax {
for property in definision.properties {
DictionaryElementSyntax.init(
keyExpression: StringLiteralExprSyntax(
content: property.name
),
valueExpression: IdentifierExpr(stringLiteral: property.name)
)
}
}
)
}
}

private func generateParamertersDictonary(with definision: EventDefinision) -> String {
"[\(definision.properties.map { "\($0.name): \($0.name)" }.joined(separator: ", "))]"
private func generateCodeDocument(with definision: EventDefinision) -> [TriviaPiece] {
if !definision.properties.isEmpty {
var result: [TriviaPiece] = [
.docLineComment("/// \(definision.description)"),
.newlines(1),
.docLineComment("/// - Parameters:"),
.newlines(1)
]

for property in definision.properties {
result.append(
.docLineComment("/// - \(property.name): \(property.description)")
)
result.append(.newlines(1))
}

return result
} else {
return [
.docLineComment("/// \(definision.description)"),
.newlines(1)
]
}
}

private func generateTupple(with fields: [Field]) -> String {
private func typeSyntax(_ typeString: String, isNullable: Bool) -> TypeSyntaxProtocol {
func type(_ typeString: String, isNullable: Bool) -> String {
var result: String
switch typeString {
case "string":
result = "\(String.self)"
return "\(String.self)"
case "int":
result = "\(Int.self)"
return "\(Int.self)"
case "double":
result = "\(Double.self)"
return "\(Double.self)"
case "boolean":
result = "\(Bool.self)"
return "\(Bool.self)"
default:
fatalError("Detect unsupported type \(typeString)")
}

if isNullable {
result += "?"
}

return result
}
return "(" + fields.map { "\($0.name): \(type($0.type, isNullable: $0.nullable))" }.joined(separator: ", ") + ")"
}

private func generateCodeDocument(with definision: EventDefinision) -> String {
if !definision.properties.isEmpty {
return """
/// \(definision.description)
/// - Parameters:
\(definision.properties.map { "/// - \($0.name): \($0.description)" }.joined(separator: "\n"))
"""
let simpleSyntax = SimpleTypeIdentifierSyntax(
stringLiteral: type(
typeString, isNullable: isNullable
)
)
if isNullable {
return simpleSyntax
} else {
return """
/// \(definision.description)
"""
return OptionalTypeSyntax(wrappedType: simpleSyntax)
}
}
}
Expand Down
Loading

0 comments on commit b58e1eb

Please sign in to comment.