Skip to content

Commit ad87202

Browse files
Merge pull request #3 from tomsugarev/TypeScript-generator
TypeScript Standalone
2 parents a939924 + d7dc193 commit ad87202

7 files changed

Lines changed: 321 additions & 1 deletion

File tree

Package.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ let package = Package(
1313
.library(name: "SwiftLexicon", targets: ["SwiftLexicon"]),
1414
.library(name: "SwiftStandAlone", targets: ["SwiftStandAlone"]),
1515
.library(name: "KotlinStandAlone", targets: ["KotlinStandAlone"]),
16+
.library(name: "TypeScriptStandAlone", targets: ["TypeScriptStandAlone"]),
1617
.library(name: "LexiconGenerators", targets: ["LexiconGenerators"]),
1718
.executable(name: "lexicon-generate", targets: ["lexicon-generate"]),
1819
.plugin(name: "SwiftStandAloneGeneratorPlugin", targets: ["SwiftStandAloneGeneratorPlugin"]),
@@ -45,7 +46,8 @@ let package = Package(
4546
"Lexicon",
4647
"SwiftLexicon",
4748
"SwiftStandAlone",
48-
"KotlinStandAlone"
49+
"KotlinStandAlone",
50+
"TypeScriptStandAlone"
4951
]
5052
),
5153
.target(
@@ -90,6 +92,20 @@ let package = Package(
9092
],
9193
resources: [.copy("Resources")]
9294
),
95+
.target(
96+
name: "TypeScriptStandAlone",
97+
dependencies: [
98+
"Lexicon",
99+
]
100+
),
101+
.testTarget(
102+
name: "TypeScriptStandAloneTests",
103+
dependencies: [
104+
"Hope",
105+
"TypeScriptStandAlone"
106+
],
107+
resources: [.copy("Resources")]
108+
),
93109
.executableTarget(
94110
name: "lexicon-generate",
95111
dependencies: [

Sources/LexiconGenerators/List.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Collections
77
import SwiftLexicon
88
import SwiftStandAlone
99
import KotlinStandAlone
10+
import TypeScriptStandAlone
1011

1112
public extension Lexicon.Graph.JSON {
1213

@@ -18,6 +19,8 @@ public extension Lexicon.Graph.JSON {
1819

1920
"Kotlin Stand-Alone": KotlinStandAlone.Generator.self,
2021

22+
"TypeScript Stand-Alone": TypeScriptStandAlone.Generator.self,
23+
2124
"JSON Classes & Mixins": JSONClasses.self,
2225
]
2326
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//
2+
// github.com/screensailor 2022
3+
//
4+
5+
import Lexicon
6+
import UniformTypeIdentifiers
7+
8+
public extension UTType {
9+
static var typescript = UTType(filenameExtension: "ts", conformingTo: .sourceCode)!
10+
}
11+
12+
public enum Generator: CodeGenerator {
13+
14+
// TODO: prefixes?
15+
16+
public static let utType = UTType.typescript
17+
public static let command = "ts"
18+
19+
public static func generate(_ json: Lexicon.Graph.JSON) throws -> Data {
20+
return Data(json.ts().utf8)
21+
}
22+
}
23+
24+
private extension Lexicon.Graph.JSON {
25+
26+
func ts() -> String {
27+
return """
28+
interface I { }
29+
30+
// L
31+
class L implements I {
32+
protected id: string;
33+
constructor(id: string) {
34+
this.id = id;
35+
}
36+
get ['__']() {
37+
return this.id;
38+
}
39+
}
40+
41+
// MARK: generated types
42+
\(classes.flatMap{ $0.ts(prefix: ("L", "I"), classes: classes) }.joined(separator: "\n"))
43+
const \(name) = new L_\(name)("\(name)");
44+
45+
"""
46+
}
47+
}
48+
49+
private extension Lexicon.Graph.Node.Class.JSON {
50+
51+
// TODO: make this more readable
52+
53+
func ts(prefix: (class: String, protocol: String), classes: [Lexicon.Graph.Node.Class.JSON]) -> [String] {
54+
55+
guard mixin == nil else {
56+
return []
57+
}
58+
59+
var lines: [String] = []
60+
let T = id.idToClassSuffix
61+
let (L, I) = prefix
62+
63+
if let protonym = protonym {
64+
lines += "type \(L)_\(T) = \(L)_\(protonym.idToClassSuffix)"
65+
return lines
66+
}
67+
68+
lines += "class \(L)_\(T) extends \(L) implements \(I)_\(T) {"
69+
70+
let supertype = supertype?
71+
.replacingOccurrences(of: "_", with: "__")
72+
.replacingOccurrences(of: ".", with: "_")
73+
.replacingOccurrences(of: "__&__", with: ", I_")
74+
75+
if hasNoProperties {
76+
if supertype != nil {
77+
let superChildren = classes.filter {$0.id == supertype}.first?.children
78+
79+
for child in superChildren ?? [] {
80+
lines += " \(child)!: \(L)_\(supertype!)_\(child);"
81+
}
82+
lines += "}"
83+
84+
lines += "type \(I)_\(T) = I_\(supertype!);"
85+
} else {
86+
lines += "}"
87+
lines += "type \(I)_\(T) = I;"
88+
}
89+
}
90+
91+
guard hasProperties else {
92+
return lines
93+
}
94+
95+
for t in type ?? [] {
96+
let subClass = classes.filter{$0.id == t}.first
97+
for child in subClass?.children ?? [] {
98+
let id = "L.\(t).\(child)"
99+
lines += " \(child)!: \(id.idToClassSuffix);"
100+
}
101+
if let keys = subClass?.synonyms?.keys {
102+
for synonym in keys {
103+
let id = "L.\(t).\(synonym)"
104+
lines += " \(synonym)!: \(id.idToClassSuffix);"
105+
}
106+
}
107+
108+
}
109+
110+
for child in children ?? [] {
111+
let id = "\(id).\(child)"
112+
lines += " \(child) = new \(L)_\(id.idToClassSuffix)(`${this.__}.\(child)`);"
113+
}
114+
115+
for (synonym, protonym) in (synonyms?.sortedByLocalizedStandard(by: \.key) ?? []) {
116+
lines += " \(synonym) = this.\(protonym);"
117+
}
118+
lines += "}"
119+
120+
lines += "interface \(I)_\(T) extends \(I)\(supertype.map{ "_\($0)" } ?? "") {"
121+
122+
for child in children ?? [] {
123+
let id = "\(id).\(child)"
124+
lines += " \(child): \(I)_\(id.idToClassSuffix);"
125+
}
126+
lines += "}"
127+
return lines
128+
}
129+
}
130+
131+
private extension String {
132+
var idToClassSuffix: String {
133+
replacingOccurrences(of: "_", with: "__")
134+
.replacingOccurrences(of: ".", with: "_")
135+
.replacingOccurrences(of: "_&_", with: "_")
136+
}
137+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
test:
2+
one:
3+
+ test.type.odd
4+
more:
5+
time:
6+
+ test
7+
two:
8+
+ test.type.even
9+
timing:
10+
type:
11+
even:
12+
bad:
13+
= no.good
14+
no:
15+
good:
16+
odd:
17+
good:
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
interface I { }
2+
3+
// L
4+
class L implements I {
5+
protected id: string;
6+
constructor(id: string) {
7+
this.id = id;
8+
}
9+
get ['__']() {
10+
return this.id;
11+
}
12+
}
13+
14+
// MARK: generated types
15+
class L_test extends L implements I_test {
16+
one = new L_test_one(`${this.__}.one`);
17+
two = new L_test_two(`${this.__}.two`);
18+
type = new L_test_type(`${this.__}.type`);
19+
}
20+
interface I_test extends I {
21+
one: I_test_one;
22+
two: I_test_two;
23+
type: I_test_type;
24+
}
25+
class L_test_one extends L implements I_test_one {
26+
good!: L_test_type_odd_good;
27+
more = new L_test_one_more(`${this.__}.more`);
28+
}
29+
interface I_test_one extends I_test_type_odd {
30+
more: I_test_one_more;
31+
}
32+
class L_test_one_more extends L implements I_test_one_more {
33+
time = new L_test_one_more_time(`${this.__}.time`);
34+
}
35+
interface I_test_one_more extends I {
36+
time: I_test_one_more_time;
37+
}
38+
class L_test_one_more_time extends L implements I_test_one_more_time {
39+
one!: L_test_one;
40+
two!: L_test_two;
41+
type!: L_test_type;
42+
}
43+
type I_test_one_more_time = I_test;
44+
class L_test_two extends L implements I_test_two {
45+
no!: L_test_type_even_no;
46+
bad!: L_test_type_even_bad;
47+
timing = new L_test_two_timing(`${this.__}.timing`);
48+
}
49+
interface I_test_two extends I_test_type_even {
50+
timing: I_test_two_timing;
51+
}
52+
class L_test_two_timing extends L implements I_test_two_timing {
53+
}
54+
type I_test_two_timing = I;
55+
class L_test_type extends L implements I_test_type {
56+
even = new L_test_type_even(`${this.__}.even`);
57+
odd = new L_test_type_odd(`${this.__}.odd`);
58+
}
59+
interface I_test_type extends I {
60+
even: I_test_type_even;
61+
odd: I_test_type_odd;
62+
}
63+
class L_test_type_even extends L implements I_test_type_even {
64+
no = new L_test_type_even_no(`${this.__}.no`);
65+
bad = this.no.good;
66+
}
67+
interface I_test_type_even extends I {
68+
no: I_test_type_even_no;
69+
}
70+
type L_test_type_even_bad = L_test_type_even_no_good
71+
class L_test_type_even_no extends L implements I_test_type_even_no {
72+
good = new L_test_type_even_no_good(`${this.__}.good`);
73+
}
74+
interface I_test_type_even_no extends I {
75+
good: I_test_type_even_no_good;
76+
}
77+
class L_test_type_even_no_good extends L implements I_test_type_even_no_good {
78+
}
79+
type I_test_type_even_no_good = I;
80+
class L_test_type_odd extends L implements I_test_type_odd {
81+
good = new L_test_type_odd_good(`${this.__}.good`);
82+
}
83+
interface I_test_type_odd extends I {
84+
good: I_test_type_odd_good;
85+
}
86+
class L_test_type_odd_good extends L implements I_test_type_odd_good {
87+
}
88+
type I_test_type_odd_good = I;
89+
const test = new L_test("test");
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@_exported import Hope
2+
@_exported import Combine
3+
@_exported import Lexicon
4+
@_exported import TypeScriptStandAlone
5+
6+
final class TypeScriptLexicon™: Hopes {
7+
8+
func test_generator() async throws {
9+
10+
var json = try await "test".taskpaper().lexicon().json()
11+
json.date = Date(timeIntervalSinceReferenceDate: 0)
12+
13+
let code = try Generator.generate(json).string()
14+
15+
try hope(code) == "test.ts".file().string()
16+
}
17+
18+
func test_code() throws {
19+
/**
20+
@Test
21+
fun generator(){
22+
assert(test.one.more.time.one.more.time.identifier == "test.one.more.time.one.more.time")
23+
assert(test.two.bad == test.two.no.good)
24+
assert(test.two.bad.identifier == "test.two.no.good")
25+
}
26+
*/
27+
}
28+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// github.com/screensailor 2022
3+
//
4+
5+
import Foundation
6+
7+
extension String {
8+
9+
func taskpaper() throws -> String {
10+
try "\(self).taskpaper".file().string()
11+
}
12+
13+
func file() throws -> Data {
14+
guard let url = Bundle.module.url(forResource: "Resources/\(self)", withExtension: nil) else {
15+
throw "Could not find '\(self)'"
16+
}
17+
return try Data(contentsOf: url)
18+
}
19+
20+
func lexicon() async throws -> Lexicon {
21+
try await Lexicon.from(TaskPaper(self).decode())
22+
}
23+
}
24+
25+
extension Data {
26+
27+
func string(encoding: String.Encoding = .utf8) throws -> String {
28+
try String(data: self, encoding: encoding).try()
29+
}
30+
}

0 commit comments

Comments
 (0)