Skip to content
Merged
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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ SWON uses the amazingly written [cJSON][credits-cjson] parser (C) under the hood

### Things to do

- Enums with associated types.
- Enums with optional or default associated values.
- Do not suppress errors inside an optional field (silently made nil).

## License
Expand Down
86 changes: 69 additions & 17 deletions Sources/SWONMacros/Decoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,74 @@ struct SWONDecodeMacro: MemberMacro {

var assignments: [String] = []

// Parse enum from raw value
if let enumDecl = declaration.as(EnumDeclSyntax.self) {
guard let inheritance = enumDecl.inheritanceClause,
let inheritedType = inheritance.inheritedTypes.first else {
fatalError("Enumeration must inherit a raw type")
}
let enumType = enumDecl.name.text
let rawType = inheritedType.type.description
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Raw type of enum \(enumType) is \(rawType)")

// Enum with associated values
if enumDecl.hasAssociatedValues {
let cases = enumDecl.cases
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Associated cases of enum \(enumType): \(cases.map(\.description))")
// )
// )
// )
switch rawType {
case "String":
// FIXME: ###, Ensure root is an actual object
assignments.append("""
let child = swon_get_map_first(root)
guard let key = swon_get_map_key(child) else {
throw SWONError.invalid("Unable to find enum dictionary")
}
""")

assignments.append("switch String(cString: key) {")
cases.forEach {
$0.elements.forEach { el in
assignments.append("case \"\(el.name)\":")
guard let parms = el.parameterClause?.parameters, !parms.isEmpty else {
assignments.append("self = .\(el.name)")
return
}
var parmAssignments: [String] = []
parms.enumerated().forEach { i, p in
let name: String
let type = p.type.decodedType(context: context)
// FIXME: ###, Handle optional (replace with nil)
if let firstName = p.firstName {
name = firstName.description
parmAssignments.append("\(name): \(name)")
} else {
name = "_\(i)"
parmAssignments.append(name)
}
assignments.append("""
let \(name) = try {
var dict = swon_t()
let dictResult = swon_get_object(&dict, child, "\(name)")
try dictResult.check("\(name) in \(el.name)")
\(mapItem(to: type, fieldName: "\(name) in \(el.name)", varName: "dict", nesting: 0, isOptional: false))
}()
""")
}
let parmAssignmentList = parmAssignments.joined(separator: ",")
assignments.append("self = .\(el.name)(\(parmAssignmentList))")
}
}
assignments.append("default: throw SWONError.invalid(\"Unknown enum case '\\(key)'\")")
assignments.append("}")
}
// Enum with raw value
else if let inheritedType = enumDecl.inheritanceClause?.inheritedTypes.first {
let rawType = inheritedType.type.description
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Raw type of enum \(enumType) is \(rawType)")
// )
// )
switch rawType {
case "String":
assignments.append("""
var str: UnsafePointer<CChar>!
try swon_get_string(&str, root).check("\(enumType)")
let rawValue = String(cString: str)
Expand All @@ -53,17 +104,18 @@ struct SWONDecodeMacro: MemberMacro {
}
self = value
""")
case "Int":
assignments.append("""
case "Int":
assignments.append("""
var num: Int32 = 0
try swon_get_integer(&num, root).check("\(enumType)")
guard let value = \(enumType)(rawValue: Int(num)) else {
throw SWONError.invalid("\(enumType).\\(num)")
}
self = value
""")
default:
fatalError("Unsupported raw type '\(rawType)'")
default:
fatalError("Unsupported raw type '\(rawType)'")
}
}
} else {
// Parse struct fields
Expand Down
142 changes: 114 additions & 28 deletions Sources/SWONMacros/Encoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,121 @@ struct SWONEncodeMacro: MemberMacro {

var assignments: [String] = []

// Parse enum from raw value
if let enumDecl = declaration.as(EnumDeclSyntax.self) {
guard let inheritance = enumDecl.inheritanceClause,
let inheritedType = inheritance.inheritedTypes.first else {
fatalError("Enumeration must inherit a raw type")
}
let enumType = enumDecl.name.text
let rawType = inheritedType.type.description
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Raw type of enum \(enumType) is \(rawType)")

// Enum with associated values
if enumDecl.hasAssociatedValues {
let cases = enumDecl.cases
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Associated cases of enum \(enumType): \(cases.map(\.description))")
// )
// )
// )
switch rawType {
case "String":
assignments.append("""
var item = swon_t()
guard rawValue.withCString({ swon_create_string(&item, $0) }) else {
assignments.append("switch self {")
cases.forEach {
for el in $0.elements {
// Parameters of the case
guard let parms = el.parameterClause?.parameters, !parms.isEmpty else {
assignments.append("case .\(el.name):")
// Empty dictionary with el name
assignments.append("""
var empty = swon_t()
guard swon_create_object(&empty) else {
throw SWONError.invalid("Unable to create empty dictionary (\(el.name))")
}
guard swon_object_add_item(&root, "\(el.name)", empty) else {
swon_free(&empty)
throw SWONError.invalid("Unable to set root[\(el.name)]")
}
""")
continue
}
// Iterate through parameters
var parmVars: [String] = []
var parmDeclarations: [String] = []
var parmAssignments: [String] = []
parms.enumerated().forEach { i, p in
let name = p.firstName?.description ?? "_\(i)"
let type = p.type.decodedType(context: context)
parmVars.append(name)
parmDeclarations.append("let \(name)")
parmAssignments.append(
contentsOf: type.statements(
forName: "\(name)JSON",
element: name,
pair: false,
withDeclaration: false
)
)
}
let parmDeclarationList = parmDeclarations.joined(separator: ",")
assignments.append("case .\(el.name)(\(parmDeclarationList)):")
assignments.append("var item = swon_t()")
parmVars.forEach {
assignments.append("var \($0)JSON = swon_t()")
}
assignments.append("do {")
assignments.append("""
guard swon_create_object(&item) else {
throw SWONError.invalid("Unable to create root[\(el.name)]")
}
""")
assignments.append(contentsOf: parmAssignments)
parmVars.forEach {
assignments.append("""
guard swon_object_add_item(&item, \"\($0)\", \($0)JSON) else {
throw SWONError.invalid("Unable to set root[\(el.name)][\($0)]")
}
""")
}
assignments.append("""
guard swon_object_add_item(&root, \"\(el.name)\", item) else {
throw SWONError.invalid("Unable to set root[\(el.name)]")
}
""")
assignments.append("} catch {")
assignments.append("swon_free(&item)")
parmVars.forEach {
assignments.append("swon_free(&\($0)JSON)")
}
assignments.append("throw error")
assignments.append("}")
}
}
assignments.append("}")
}
// Enum with raw value
else if let inheritedType = enumDecl.inheritanceClause?.inheritedTypes.first {
let rawType = inheritedType.type.description
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
// message: SWONMessage(message: "Raw type of enum \(enumType) is \(rawType)")
// )
// )
switch rawType {
case "String":
assignments.append("""
var root = swon_t()
guard swon_create_string(&root, rawValue) else {
throw SWONError.invalid("\(enumType)(String)")
}
return item
""")
case "Int":
assignments.append("""
var item = swon_t()
guard swon_create_number(&item, Double(rawValue)) else {
case "Int":
assignments.append("""
var root = swon_t()
guard swon_create_number(&root, Double(rawValue)) else {
throw SWONError.invalid("\(enumType)(Int)")
}
return item
""")
default:
fatalError("Unsupported raw type '\(rawType)'")
default:
fatalError("Unsupported raw type '\(rawType)'")
}
}
} else {
// Parse struct fields
// Struct fields
for prop in properties {
guard let field = prop.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text else {
continue
Expand All @@ -87,8 +166,8 @@ struct SWONEncodeMacro: MemberMacro {
)
assignments.append(contentsOf: stmts)
}
assignments.append("return root")
}
assignments.append("return root")
// context.diagnose(
// Diagnostic(
// node: Syntax(node),
Expand Down Expand Up @@ -283,11 +362,18 @@ private extension SWONEncodeMacro {
}

private extension DecodedType {
func statements(forName name: String, element: String, pair: Bool) -> [String] {
func statements(
forName name: String,
element: String,
pair: Bool,
withDeclaration: Bool = true
) -> [String] {
var stmts: [String] = []
let suffix = pair ? ".value" : ""
var isScalar = true
stmts.append("var \(name) = swon_t()")
if withDeclaration {
stmts.append("var \(name) = swon_t()")
}
switch description {
case "Bool":
stmts.append("guard swon_create_bool(&\(name), \(element)\(suffix)) else {")
Expand Down
17 changes: 17 additions & 0 deletions Sources/SWONMacros/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ extension TypeSyntax {
}
}

extension EnumDeclSyntax {
var cases: [EnumCaseDeclSyntax] {
memberBlock.members
.compactMap { member in
member.decl.as(EnumCaseDeclSyntax.self)
}
}

var hasAssociatedValues: Bool {
cases.contains { caseDecl in
caseDecl.elements.contains { element in
element.parameterClause != nil
}
}
}
}

struct SWONMessage: DiagnosticMessage {
let severity: DiagnosticSeverity = .note
let message: String
Expand Down
1 change: 1 addition & 0 deletions Sources/SWON_C/swon.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ void swon_free(swon_t *dst) {
swon_result swon_get_object(swon_t *dst, swon_t src, const char *field) {
cJSON *item = cJSON_GetObjectItemCaseSensitive(src.impl, field);
SWON_RETURN_IF_NULL(item)
SWON_RETURN_INVALID_IF(!cJSON_IsObject(src.impl))
dst->impl = item;
return SWONResultValid;
}
Expand Down
11 changes: 11 additions & 0 deletions Tests/SWONTests/ComplexStruct.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,15 @@ struct SubStruct: Equatable, Codable {
let statusHistory: [Status]
let colorToStatus: [String: Status]
let optionalColorArray: [Color]?
let assEnum: [AssociatedEnum]?
}

@SWON
enum AssociatedEnum: Equatable, Codable {
case single
case singleFlat(String)
case singleKeyed(i: Int)
case multipleFlat(Int, Bool)
case multipleKeyed(d: Double, b: Bool)
case multipleMixed(d: Double, Bool, foo: SubStruct)
}
42 changes: 41 additions & 1 deletion Tests/SWONTests/Resources/populated.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,47 @@
"optionalSize": null,
"statusHistory": ["active", "pending"],
"colorToStatus": {"blue": "pending"},
"optionalColorArray": null
"optionalColorArray": null,
"assEnum": [
{
"single": {}
},
{
"singleFlat": {
"_0": "Value"
}
},
{
"singleKeyed": {
"i": 42
}
},
{
"multipleFlat": {
"_0": 100,
"_1": true
}
},
{
"multipleKeyed": {
"d": 3.14,
"b": false
}
},
{
"multipleMixed": {
"d": 1.5,
"_1": true,
"foo": {
"favoriteColor": "blue",
"optionalSize": null,
"statusHistory": ["active", "pending"],
"colorToStatus": {"blue": "pending"},
"optionalColorArray": null
}
}
}
]
}]
}
}
Loading