diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/LexiconGenerators.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/LexiconGenerators.xcscheme new file mode 100644 index 0000000..133d839 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/LexiconGenerators.xcscheme @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Package.swift b/Package.swift index fcfc97f..7af8c4b 100644 --- a/Package.swift +++ b/Package.swift @@ -47,7 +47,7 @@ let package = Package( "SwiftLexicon", "SwiftStandAlone", "KotlinStandAlone", - "TypeScriptStandAlone" + "TypeScriptStandAlone" ] ), .target( @@ -92,7 +92,7 @@ let package = Package( ], resources: [.copy("Resources")] ), - .target( + .target( name: "TypeScriptStandAlone", dependencies: [ "Lexicon", @@ -106,6 +106,20 @@ let package = Package( ], resources: [.copy("Resources")] ), + .target( + name: "CSharpStandAlone", + dependencies: [ + "Lexicon", + ] + ), + .testTarget( + name: "CSharpStandAloneTests", + dependencies: [ + "Hope", + "CSharpStandAlone" + ], + resources: [.copy("Resources")] + ), .executableTarget( name: "lexicon-generate", dependencies: [ diff --git a/Sources/CSharpStandAlone/Generator.swift b/Sources/CSharpStandAlone/Generator.swift new file mode 100644 index 0000000..fa47c38 --- /dev/null +++ b/Sources/CSharpStandAlone/Generator.swift @@ -0,0 +1,123 @@ +import Lexicon +import UniformTypeIdentifiers + +public enum Generator: CodeGenerator { + + // TODO: prefixes? + + public static let utType = UTType(filenameExtension: "cs", conformingTo: .sourceCode)! + + public static func generate(_ json: Lexicon.Graph.JSON) throws -> Data { + return Data(json.cSharp().utf8) + } +} + +private extension Lexicon.Graph.JSON { + + func cSharp() -> String { + return """ +global using static \(name.capitalized)Lexicon; + +public static class \(name.capitalized)Lexicon +{ + public static I\(name.capitalized) \(name) = new L\(name.capitalized)(nameof(\(name))); +} + +public abstract class LexiconType +{ + protected string Identifier { get; } + + public LexiconType(string identifier) + { + Identifier = identifier; + } + + public override string ToString() + { + return Identifier; + } +} + +// MARK: generated types + +\(classes.flatMap{ $0.cSharp(prefix: ("L", "I")) }.joined(separator: "\n")) + +""" + } +} + +private extension Lexicon.Graph.Node.Class.JSON { + + // TODO: make this more readable + + func cSharp(prefix: (class: String, protocol: String)) -> [String] { + + guard mixin == nil else { + return [] + } + + var lines: [String] = [] + let T = id.split(separator: ".").map{$0.capitalized}.joined().idToClassSuffix + let (L, I) = prefix + + let supertype = supertype? + .split(separator: ".") + .map({$0.capitalized}) + .joined() + .replacingOccurrences(of: "_", with: "__") + .replacingOccurrences(of: ".", with: "") + .replacingOccurrences(of: "__&__", with: ", I") + + if let protonym = protonym?.split(separator: ".").map({$0.capitalized}).joined().idToClassSuffix { + lines += "public sealed class \(L)\(T) : LexiconType, \(I)\(T), \(I)\(protonym)" + + } else { + lines += "public sealed class \(L)\(T) : LexiconType, \(I)\(T)\(supertype.map{ ", \(I)\($0)" } ?? "")" + } + + lines += "{" + + for child in children ?? [] { + let id = "\(T).\(child.capitalized)" + lines += "\tpublic \(I)\(id.idToClassSuffix) \(child) => new \(L)\(id.idToClassSuffix)($\"{Identifier}.{nameof(\(child))}\");" + } + + // TODO: Generate vars for interface'd properties + + for (synonym, protonym) in (synonyms?.sortedByLocalizedStandard(by: \.key) ?? []) { + let id = "\(T).\(synonym.capitalized)" + lines += "\tpublic \(I)\(id.idToClassSuffix) \(synonym) => new \(L)\(id.idToClassSuffix)($\"{Identifier}.{nameof(\(protonym))}\");" + } + + lines += "\n\tpublic \(L)\(T)(string identifier) : base(identifier) { }" + + lines += "}" + + lines += "public interface \(I)\(T)\(supertype.map{ " : \(I)\($0)" } ?? "")" + + lines += "{" + + for child in children ?? [] { + let id = "\(T).\(child.capitalized)" + lines += "\t\(I)\(id.idToClassSuffix) \(child) { get }" + } + + for (synonym, protonym) in (synonyms?.sortedByLocalizedStandard(by: \.key) ?? []) { + let id = "\(T).\(synonym.capitalized)" + lines += "\t\(I)\(id.idToClassSuffix) \(synonym) { get }" + } + + lines += "}" + + return lines + } +} + +private extension String { + + var idToClassSuffix: String { + replacingOccurrences(of: "_", with: "__") + .replacingOccurrences(of: ".", with: "") + .replacingOccurrences(of: "_&_", with: "") + } +} diff --git a/Sources/LexiconGenerators/List.swift b/Sources/LexiconGenerators/List.swift index 5c3a76c..35c0631 100644 --- a/Sources/LexiconGenerators/List.swift +++ b/Sources/LexiconGenerators/List.swift @@ -7,6 +7,7 @@ import Collections import SwiftLexicon import SwiftStandAlone import KotlinStandAlone +import CSharpStandAlone import TypeScriptStandAlone public extension Lexicon.Graph.JSON { @@ -21,6 +22,8 @@ public extension Lexicon.Graph.JSON { "TypeScript Stand-Alone": TypeScriptStandAlone.Generator.self, + "C# Stand-Alone": CSharpStandAlone.Generator.self, + "JSON Classes & Mixins": JSONClasses.self, ] } diff --git "a/Tests/CSharpStandAloneTests/ CSharpStandAlone\342\204\242.swift" "b/Tests/CSharpStandAloneTests/ CSharpStandAlone\342\204\242.swift" new file mode 100644 index 0000000..677fe17 --- /dev/null +++ "b/Tests/CSharpStandAloneTests/ CSharpStandAlone\342\204\242.swift" @@ -0,0 +1,20 @@ +@_exported import Hope +@_exported import Combine +@_exported import Lexicon +@_exported import CSharpStandAlone + +final class CSharpLexicon™: 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.cs".file().string() + } + + func test_code() throws { + } +} diff --git a/Tests/CSharpStandAloneTests/Resources/test.taskpaper b/Tests/CSharpStandAloneTests/Resources/test.taskpaper new file mode 100644 index 0000000..5a4d0da --- /dev/null +++ b/Tests/CSharpStandAloneTests/Resources/test.taskpaper @@ -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: \ No newline at end of file diff --git a/Tests/CSharpStandAloneTests/util.swift b/Tests/CSharpStandAloneTests/util.swift new file mode 100644 index 0000000..ccc7802 --- /dev/null +++ b/Tests/CSharpStandAloneTests/util.swift @@ -0,0 +1,26 @@ +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() + } +}