Skip to content

Commit 67845c1

Browse files
author
yzhou
committed
initial dump
1 parent 60ce144 commit 67845c1

20 files changed

+1103
-0
lines changed

Package.swift

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// swift-tools-version:4.2
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "ImmutableSwift",
8+
dependencies: [
9+
// Dependencies declare other packages that this package depends on.
10+
// .package(url: /* package url */, from: "1.0.0"),
11+
.package(url: "https://github.com/apple/swift-package-manager.git", from: "0.1.0"),
12+
],
13+
targets: [
14+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
15+
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
16+
.target(
17+
name: "ImmutableSwift",
18+
dependencies: ["Utility"]),
19+
.testTarget(
20+
name: "ImmutableSwiftTests",
21+
dependencies: ["ImmutableSwift"]),
22+
]
23+
)

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# ImmutableSwift
2+
3+
A description of this package.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
class Generator {
2+
static func Generate(_ datamodel: DataModel) -> String {
3+
var output = ""
4+
let pluginList = datamodel.plugins
5+
6+
// optional imports
7+
if datamodel.imports != nil {
8+
output = output + Generator.ImportsToString(datamodel.imports!) + "\n"
9+
}
10+
11+
// optional accesscontrol
12+
if datamodel.accessControlLevel != nil {
13+
output = output + Generator.AccessControlLevelToString(datamodel.accessControlLevel!) + " "
14+
}
15+
16+
// struct or class
17+
let shouldUseClass: Bool = Generator.shouldUseClass(pluginList)
18+
if shouldUseClass {
19+
output = output + Keyword.CLASS + " "
20+
} else {
21+
output = output + Keyword.STRUCT + " "
22+
}
23+
// name
24+
output = output + datamodel.name + " "
25+
26+
// optional super classes
27+
if pluginList != nil {
28+
output = output + Generator.PrintSuperClasses(pluginList!)
29+
}
30+
31+
// variables
32+
var datamodelBody = Generator.SchemaToString(datamodel.schema)
33+
34+
// post variable body from plugins
35+
if pluginList != nil {
36+
let additionalBody = Generator.GeneratePostVariableDefinitionObjectBodyWithPluginList(pluginList!, datamodel)
37+
if !additionalBody.isEmpty {
38+
datamodelBody = datamodelBody + "\n" + additionalBody
39+
}
40+
}
41+
42+
// constructor
43+
if shouldUseClass {
44+
let constructor = Generator.PrintConstructorForClass(datamodel.schema)
45+
datamodelBody = datamodelBody + "\n" + constructor + "\n"
46+
}
47+
48+
// post constructor body from plugins
49+
if pluginList != nil {
50+
let additionalBody = Generator.GeneratePostConstructorObjectBodyWithPluginList(pluginList!, datamodel)
51+
if !additionalBody.isEmpty {
52+
datamodelBody = datamodelBody + "\n" + additionalBody + "\n"
53+
}
54+
}
55+
56+
datamodelBody = StringUtils.formatStringWithIndentLevel(str: datamodelBody, indentLevel: 1)
57+
output = output + Generator.WrappedModelBody(datamodelBody)
58+
return output
59+
}
60+
61+
static func ImportsToString(_ imports: [Import]) -> String {
62+
var output = ""
63+
for importStatment in imports {
64+
if importStatment is SingleModuleImport {
65+
let singleModuleImport = importStatment as! SingleModuleImport
66+
output = output + [Keyword.IMPORT, singleModuleImport.module].joined(separator: " ")
67+
} else if importStatment is ModuleWithSubmoduleImport {
68+
let moduleWithSubmoduleImport = importStatment as! ModuleWithSubmoduleImport
69+
output = output + [Keyword.IMPORT, moduleWithSubmoduleImport.module + "." + moduleWithSubmoduleImport.submodule].joined(separator: " ")
70+
} else if importStatment is ModuleWithSymbolImport {
71+
let moduleWithSymbolImport = importStatment as! ModuleWithSymbolImport
72+
output = output + [Keyword.IMPORT, moduleWithSymbolImport.kind, moduleWithSymbolImport.module + "." + moduleWithSymbolImport.symbol].joined(separator: " ")
73+
}
74+
output = output + "\n"
75+
}
76+
return output
77+
}
78+
79+
static func AccessControlLevelToString(_ accessControl: AccessControl) -> String {
80+
switch accessControl {
81+
case AccessControl.levelPublic:
82+
return Keyword.AccessControlModifier.PUBLIC_LEVEL
83+
case AccessControl.levelInternal:
84+
return Keyword.AccessControlModifier.INTERNAL_LEVEL
85+
}
86+
}
87+
88+
static func shouldUseClass(_ pluginList: PluginList?) -> Bool {
89+
if pluginList == nil {
90+
return false
91+
}
92+
for pluginName in pluginList!.plugins {
93+
if Plugins.PLUGIN_MAP[pluginName] != nil, Plugins.PLUGIN_MAP[pluginName]!.shouldUseClass() {
94+
return true
95+
}
96+
}
97+
return false
98+
}
99+
100+
static func PrintSuperClasses(_ pluginList: PluginList) -> String {
101+
var superClasses: [String] = []
102+
for pluginName in pluginList.plugins {
103+
if Plugins.PLUGIN_MAP[pluginName] != nil {
104+
superClasses = superClasses + Plugins.PLUGIN_MAP[pluginName]!.superClasses()
105+
}
106+
}
107+
if !superClasses.isEmpty {
108+
return ": " + superClasses.joined(separator: ", ")
109+
} else {
110+
return ""
111+
}
112+
}
113+
114+
static func PrintConstructorForClass(_ schema: Schema) -> String {
115+
let constructorFormat = "init(%@) {\n%@\n}"
116+
var parameters: [String] = []
117+
var assignments: [String] = []
118+
for statement in schema.statements {
119+
if statement is StateDef {
120+
parameters.append((statement as! StateDef).name + ":" + (statement as! StateDef).type)
121+
assignments.append("self." + (statement as! StateDef).name + " = " + (statement as! StateDef).name)
122+
}
123+
}
124+
let parametersString = parameters.joined(separator: ", ")
125+
let assignmentString = StringUtils.formatStringWithIndentLevel(str: assignments.joined(separator: "\n"), indentLevel: 1)
126+
return String(format: constructorFormat, parametersString, assignmentString)
127+
}
128+
129+
static func SchemaToString(_ schema: Schema) -> String {
130+
var result: [String] = []
131+
for i in 0 ..< schema.statements.count {
132+
let statement = schema.statements[i]
133+
if statement is Comment {
134+
result.append("//" + (statement as! Comment).source)
135+
} else if statement is StateDef {
136+
let stateDef = statement as! StateDef
137+
result.append("let " + stateDef.name + " : " + stateDef.type)
138+
}
139+
}
140+
return result.joined(separator: "\n") + "\n"
141+
}
142+
143+
static func GeneratePostVariableDefinitionObjectBodyWithPluginList(_ pluginList: PluginList, _ datamodel: DataModel) -> String {
144+
var additionalBody: [String] = []
145+
for pluginName in pluginList.plugins {
146+
if Plugins.PLUGIN_MAP[pluginName] != nil {
147+
let bodyFromPlugin = Plugins.PLUGIN_MAP[pluginName]!.postVariableDefinition(datamodel)
148+
if !bodyFromPlugin.isEmpty {
149+
additionalBody.append(bodyFromPlugin)
150+
}
151+
}
152+
}
153+
return additionalBody.joined(separator: "\n")
154+
}
155+
156+
static func GeneratePostConstructorObjectBodyWithPluginList(_ pluginList: PluginList, _ datamodel: DataModel) -> String {
157+
var additionalMethods: [String] = []
158+
for pluginName in pluginList.plugins {
159+
if Plugins.PLUGIN_MAP[pluginName] != nil {
160+
let methodFromPlugin = Plugins.PLUGIN_MAP[pluginName]!.postConstructor(datamodel)
161+
if !methodFromPlugin.isEmpty {
162+
additionalMethods.append(methodFromPlugin)
163+
}
164+
}
165+
}
166+
return additionalMethods.joined(separator: "\n")
167+
}
168+
169+
static func WrappedModelBody(_ body: String) -> String {
170+
return "{\n" + body + "}"
171+
}
172+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class ISCodable: ImmutableSwiftGeneratorPlugin {
2+
static let Name: String = "ISCodable"
3+
4+
func shouldUseClass() -> Bool {
5+
return false
6+
}
7+
8+
func superClasses() -> [String] {
9+
return ["Codable"]
10+
}
11+
12+
func postVariableDefinition(_: DataModel) -> String {
13+
return ""
14+
}
15+
16+
func postConstructor(_: DataModel) -> String {
17+
return ""
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
class ISCoding: ImmutableSwiftGeneratorPlugin {
2+
static let Name: String = "ISCoding"
3+
4+
// type X in this array should use NSCoder.decodeX()
5+
static let typesWithDedicatedDecodeMethodWithSameName: [String] = [
6+
"Bool",
7+
"Data",
8+
"Float",
9+
"Int32",
10+
"Int64",
11+
"CGPoint",
12+
"CGRect",
13+
"CGSize",
14+
"CGAffineTransform",
15+
"UIEdgeInsets",
16+
"UIOffset",
17+
"CGVector",
18+
]
19+
20+
// type X in this array should use NSCoder.decodeCUSTOMNAME()
21+
static let typesWithDedicatedDecodeMethodWithCustomName: [String: String] = [
22+
"Int32": "CInt",
23+
"Int": "Integer",
24+
"NSPoint": "Point",
25+
"NSRect": "Rect",
26+
"NSSize": "Size",
27+
"CMTime": "Time",
28+
"CMTimeRange": "TimeRange",
29+
"CMTimeMapping": "TimeMapping",
30+
]
31+
32+
static let decodeMethodWithOptionalReturnValue: [String] = [
33+
"decodeObject",
34+
"decodeData",
35+
]
36+
37+
func shouldUseClass() -> Bool {
38+
return true
39+
}
40+
41+
func superClasses() -> [String] {
42+
return ["NSObject", "NSCoding"]
43+
}
44+
45+
func postVariableDefinition(_ datamodel: DataModel) -> String {
46+
return ISCoding.printKeyEnum(datamodel.schema) + "\n"
47+
}
48+
49+
func postConstructor(_ datamodel: DataModel) -> String {
50+
var out = ""
51+
out = out + ISCoding.printEncodeFunc(datamodel.schema) + "\n"
52+
out = out + "\n" + ISCoding.printDecodeFunc(datamodel.schema) + "\n"
53+
return out
54+
}
55+
56+
static func printKeyEnum(_ schema: Schema) -> String {
57+
let keyEnumFormat = "enum Key:String {\n%@\n}"
58+
var keys: [String] = []
59+
for statement in schema.statements {
60+
if statement is StateDef {
61+
keys.append(String(format: "case %@ = \"%@\"", (statement as! StateDef).name, (statement as! StateDef).name))
62+
}
63+
}
64+
var keyString = keys.joined(separator: "\n")
65+
keyString = StringUtils.formatStringWithIndentLevel(str: keyString, indentLevel: 1)
66+
return String(format: keyEnumFormat, keyString)
67+
}
68+
69+
static func printEncodeFunc(_ schema: Schema) -> String {
70+
let encodefuncFormat = "func encode(with aCoder: NSCoder) {\n%@\n}"
71+
var encodeStatement: [String] = []
72+
for statement in schema.statements {
73+
if statement is StateDef {
74+
let stateDef = statement as! StateDef
75+
if ISCoding.isOptionalType(stateDef.type) {
76+
encodeStatement.append(String(format: "if %@ != nil {\naCoder.encode(%@, forKey: Key.%@.rawValue)\n}", (statement as! StateDef).name, (statement as! StateDef).name, (statement as! StateDef).name))
77+
} else {
78+
encodeStatement.append(String(format: "aCoder.encode(%@, forKey: Key.%@.rawValue)", (statement as! StateDef).name, (statement as! StateDef).name))
79+
}
80+
}
81+
}
82+
var encodeString = encodeStatement.joined(separator: "\n")
83+
encodeString = StringUtils.formatStringWithIndentLevel(str: encodeString, indentLevel: 1)
84+
return String(format: encodefuncFormat, encodeString)
85+
}
86+
87+
static func printDecodeFunc(_ schema: Schema) -> String {
88+
let decodeFuncFormat = "convenience required init?(coder aDecoder: NSCoder) {\n%@\n}"
89+
let initMethodCallFormat = "self.init(%@)"
90+
var decodeStatement: [String] = []
91+
var initParameters: [String] = []
92+
for statement in schema.statements {
93+
if statement is StateDef {
94+
var decodeMethod = "decodeObject"
95+
let stateType = (statement as! StateDef).type
96+
let stateName = (statement as! StateDef).name
97+
if ISCoding.typesWithDedicatedDecodeMethodWithSameName.contains(stateType) {
98+
decodeMethod = "decode" + stateType
99+
} else if ISCoding.typesWithDedicatedDecodeMethodWithCustomName.keys.contains(stateType) {
100+
decodeMethod = "decode" + ISCoding.typesWithDedicatedDecodeMethodWithCustomName[stateType]!
101+
}
102+
103+
if ISCoding.decodeMethodWithOptionalReturnValue.contains(decodeMethod), !ISCoding.isOptionalType(stateType) {
104+
decodeStatement.append(String(format: "guard let %@ = aDecoder.%@(forKey: Key.%@.rawValue) as? %@ else { return nil }", stateName, decodeMethod, stateName, stateType))
105+
} else {
106+
decodeStatement.append(String(format: "let %@ = aDecoder.%@(forKey: Key.%@.rawValue)", stateName, decodeMethod, stateName))
107+
}
108+
109+
initParameters.append(stateName + ":" + stateName)
110+
}
111+
}
112+
let decodeString = decodeStatement.joined(separator: "\n")
113+
let initParametersString = initParameters.joined(separator: ",")
114+
var decodeMethodBody = decodeString + "\n" + String(format: initMethodCallFormat, initParametersString)
115+
decodeMethodBody = StringUtils.formatStringWithIndentLevel(str: decodeMethodBody, indentLevel: 1)
116+
117+
return String(format: decodeFuncFormat, decodeMethodBody)
118+
}
119+
120+
static func isOptionalType(_ type: String) -> Bool {
121+
if type.count <= 0 {
122+
return false
123+
}
124+
return type.last! == "?"
125+
}
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
class ISCopying: ImmutableSwiftGeneratorPlugin {
2+
static let Name: String = "ISCopying"
3+
4+
func shouldUseClass() -> Bool {
5+
return true
6+
}
7+
8+
func superClasses() -> [String] {
9+
return ["NSCopying"]
10+
}
11+
12+
func postVariableDefinition(_: DataModel) -> String {
13+
return ""
14+
}
15+
16+
func postConstructor(_ datamodel: DataModel) -> String {
17+
var results = ""
18+
results = results + ISCopying.GenerateCopyingFunc(datamodel)
19+
return results
20+
}
21+
22+
private static func GenerateCopyingFunc(_ datamodel: DataModel) -> String {
23+
let copyFuncTemplate = "func copy(with zone: NSZone? = nil) -> Any {\n%@\n}"
24+
let copyFuncBodyTemplate = "let copy = %@(%@)\n return copy"
25+
var initMethodArguments: [String] = []
26+
for statement in datamodel.schema.statements {
27+
if statement is StateDef {
28+
initMethodArguments.append(String(format: "%@:%@", (statement as! StateDef).name, (statement as! StateDef).name))
29+
}
30+
}
31+
let initMethodArgument = initMethodArguments.joined(separator: ",")
32+
var copyFuncBody = String(format: copyFuncBodyTemplate, datamodel.name, initMethodArgument)
33+
copyFuncBody = StringUtils.formatStringWithIndentLevel(str: copyFuncBody, indentLevel: 1)
34+
return String(format: copyFuncTemplate, copyFuncBody)
35+
}
36+
}

0 commit comments

Comments
 (0)