Add a Swift backend for LibertyXDR
This commit is contained in:
parent
8466d0d850
commit
53197b51e5
@ -72,7 +72,7 @@ add_test (test-cmake-parser
|
|||||||
# Test protocol code generation
|
# Test protocol code generation
|
||||||
set (lxdrgen_outputs)
|
set (lxdrgen_outputs)
|
||||||
set (lxdrgen_base "${PROJECT_BINARY_DIR}/lxdrgen.lxdr")
|
set (lxdrgen_base "${PROJECT_BINARY_DIR}/lxdrgen.lxdr")
|
||||||
foreach (backend c go mjs)
|
foreach (backend c go mjs swift)
|
||||||
list (APPEND lxdrgen_outputs ${lxdrgen_base}.${backend})
|
list (APPEND lxdrgen_outputs ${lxdrgen_base}.${backend})
|
||||||
add_custom_command (OUTPUT ${lxdrgen_base}.${backend}
|
add_custom_command (OUTPUT ${lxdrgen_base}.${backend}
|
||||||
COMMAND env LC_ALL=C awk
|
COMMAND env LC_ALL=C awk
|
||||||
@ -108,3 +108,11 @@ if (NODE_EXECUTABLE)
|
|||||||
else ()
|
else ()
|
||||||
message (WARNING "Cannot test generated protocol code for Javascript")
|
message (WARNING "Cannot test generated protocol code for Javascript")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
find_program (SWIFTC_EXECUTABLE swiftc)
|
||||||
|
if (SWIFTC_EXECUTABLE)
|
||||||
|
add_test (test-lxdrgen-swift
|
||||||
|
${SWIFTC_EXECUTABLE} -typecheck ${lxdrgen_base}.swift)
|
||||||
|
else ()
|
||||||
|
message (WARNING "Cannot test generated protocol code for Swift")
|
||||||
|
endif ()
|
||||||
|
@ -54,6 +54,9 @@ lxdrgen-mjs.awk::
|
|||||||
LibertyXDR backend for Javascript, currently for decoding only.
|
LibertyXDR backend for Javascript, currently for decoding only.
|
||||||
It cuts a corner by not using BigInts, on par with `JSON.parse()`.
|
It cuts a corner by not using BigInts, on par with `JSON.parse()`.
|
||||||
|
|
||||||
|
lxdrgen-swift.awk::
|
||||||
|
LibertyXDR backend for the Swift programming language.
|
||||||
|
|
||||||
Contributing and Support
|
Contributing and Support
|
||||||
------------------------
|
------------------------
|
||||||
Use https://git.janouch.name/p/liberty to report any bugs, request features,
|
Use https://git.janouch.name/p/liberty to report any bugs, request features,
|
||||||
|
@ -441,7 +441,7 @@ function codegen_struct(name, cg, gotype) {
|
|||||||
|
|
||||||
function codegen_union_tag(d, cg) {
|
function codegen_union_tag(d, cg) {
|
||||||
cg["tagtype"] = d["type"]
|
cg["tagtype"] = d["type"]
|
||||||
cg["tagname"] = d["name"]
|
cg["tagname"] = snaketocamel(d["name"])
|
||||||
# The tag is implied from the type of struct stored in the interface.
|
# The tag is implied from the type of struct stored in the interface.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,8 +450,8 @@ function codegen_union_struct(name, casename, cg, scg, structname, init) {
|
|||||||
structname = name snaketocamel(casename)
|
structname = name snaketocamel(casename)
|
||||||
codegen_struct(structname, scg)
|
codegen_struct(structname, scg)
|
||||||
|
|
||||||
init = CodegenGoType[structname] "{" snaketocamel(cg["tagname"]) \
|
init = CodegenGoType[structname] "{" cg["tagname"] \
|
||||||
": " decapitalize(snaketocamel(cg["tagname"])) "}"
|
": " decapitalize(cg["tagname"]) "}"
|
||||||
append(cg, "unmarshal",
|
append(cg, "unmarshal",
|
||||||
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
|
"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \
|
||||||
"\t\ts := " init "\n" \
|
"\t\ts := " init "\n" \
|
||||||
@ -467,7 +467,7 @@ function codegen_union_struct(name, casename, cg, scg, structname, init) {
|
|||||||
"\t\tu.Interface = &s\n")
|
"\t\tu.Interface = &s\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
function codegen_union(name, cg, gotype, tagfield, tagvar) {
|
function codegen_union(name, cg, gotype, tagvar) {
|
||||||
gotype = PrefixCamel name
|
gotype = PrefixCamel name
|
||||||
print "type " gotype " struct {"
|
print "type " gotype " struct {"
|
||||||
print "\tInterface any"
|
print "\tInterface any"
|
||||||
@ -481,18 +481,17 @@ function codegen_union(name, cg, gotype, tagfield, tagvar) {
|
|||||||
print "}"
|
print "}"
|
||||||
print ""
|
print ""
|
||||||
|
|
||||||
tagfield = snaketocamel(cg["tagname"])
|
tagvar = decapitalize(cg["tagname"])
|
||||||
tagvar = decapitalize(tagfield)
|
|
||||||
print "func (u *" gotype ") UnmarshalJSON(data []byte) (err error) {"
|
print "func (u *" gotype ") UnmarshalJSON(data []byte) (err error) {"
|
||||||
print "\tvar t struct {"
|
print "\tvar t struct {"
|
||||||
print "\t\t" tagfield " " CodegenGoType[cg["tagtype"]] \
|
print "\t\t" cg["tagname"] " " CodegenGoType[cg["tagtype"]] \
|
||||||
" `json:\"" tagvar "\"`"
|
" `json:\"" tagvar "\"`"
|
||||||
print "\t}"
|
print "\t}"
|
||||||
print "\tif err := json.Unmarshal(data, &t); err != nil {"
|
print "\tif err := json.Unmarshal(data, &t); err != nil {"
|
||||||
print "\t\treturn err"
|
print "\t\treturn err"
|
||||||
print "\t}"
|
print "\t}"
|
||||||
print ""
|
print ""
|
||||||
print "\tswitch " tagvar " := t." tagfield "; " tagvar " {"
|
print "\tswitch " tagvar " := t." cg["tagname"] "; " tagvar " {"
|
||||||
print cg["unmarshal"] "\tdefault:"
|
print cg["unmarshal"] "\tdefault:"
|
||||||
print "\t\terr = errors.New(`unsupported value: ` + " tagvar ".String())"
|
print "\t\terr = errors.New(`unsupported value: ` + " tagvar ".String())"
|
||||||
print "\t}"
|
print "\t}"
|
||||||
|
@ -185,14 +185,14 @@ function codegen_struct(name, cg) {
|
|||||||
|
|
||||||
function codegen_union_tag(d, cg) {
|
function codegen_union_tag(d, cg) {
|
||||||
cg["tagtype"] = d["type"]
|
cg["tagtype"] = d["type"]
|
||||||
cg["tagname"] = d["name"]
|
cg["tagname"] = decapitalize(snaketocamel(d["name"]))
|
||||||
}
|
}
|
||||||
|
|
||||||
function codegen_union_struct(name, casename, cg, scg, structname) {
|
function codegen_union_struct(name, casename, cg, scg, structname) {
|
||||||
append(scg, "methods",
|
append(scg, "methods",
|
||||||
"\n" \
|
"\n" \
|
||||||
"\tconstructor() {\n" \
|
"\tconstructor() {\n" \
|
||||||
"\t\tthis." decapitalize(snaketocamel(cg["tagname"])) \
|
"\t\tthis." cg["tagname"] \
|
||||||
" = " cg["tagtype"] "." snaketocamel(casename) "\n" \
|
" = " cg["tagtype"] "." snaketocamel(casename) "\n" \
|
||||||
"\t}\n")
|
"\t}\n")
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ function codegen_union_struct(name, casename, cg, scg, structname) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function codegen_union(name, cg, tagvar) {
|
function codegen_union(name, cg, tagvar) {
|
||||||
tagvar = decapitalize(snaketocamel(cg["tagname"]))
|
tagvar = cg["tagname"]
|
||||||
|
|
||||||
print ""
|
print ""
|
||||||
print "export function deserialize" name "(r) {"
|
print "export function deserialize" name "(r) {"
|
||||||
|
276
tools/lxdrgen-swift.awk
Normal file
276
tools/lxdrgen-swift.awk
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
# lxdrgen-swift.awk: Swift backend for lxdrgen.awk.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
# SPDX-License-Identifier: 0BSD
|
||||||
|
|
||||||
|
function define_internal(name, swifttype) {
|
||||||
|
Types[name] = "internal"
|
||||||
|
CodegenSwiftType[name] = swifttype
|
||||||
|
CodegenDeserialize[name] = "%s.read()"
|
||||||
|
}
|
||||||
|
|
||||||
|
function define_sint(size, shortname, swifttype) {
|
||||||
|
shortname = "i" size
|
||||||
|
swifttype = "Int" size
|
||||||
|
define_internal(shortname, swifttype)
|
||||||
|
}
|
||||||
|
|
||||||
|
function define_uint(size, shortname, swifttype) {
|
||||||
|
shortname = "u" size
|
||||||
|
swifttype = "UInt" size
|
||||||
|
define_internal(shortname, swifttype)
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_begin() {
|
||||||
|
define_sint("8")
|
||||||
|
define_sint("16")
|
||||||
|
define_sint("32")
|
||||||
|
define_sint("64")
|
||||||
|
define_uint("8")
|
||||||
|
define_uint("16")
|
||||||
|
define_uint("32")
|
||||||
|
define_uint("64")
|
||||||
|
define_internal("bool", "Bool")
|
||||||
|
define_internal("string", "String")
|
||||||
|
|
||||||
|
print "// Code generated from " FILENAME ". DO NOT EDIT."
|
||||||
|
print "import Foundation"
|
||||||
|
print ""
|
||||||
|
print "public struct RelayReader {"
|
||||||
|
print "\tpublic var data: Data"
|
||||||
|
print ""
|
||||||
|
print "\tpublic enum ReadError: Error {"
|
||||||
|
print "\t\tcase unexpectedEOF"
|
||||||
|
print "\t\tcase invalidEncoding"
|
||||||
|
print "\t\tcase overflow"
|
||||||
|
print "\t\tcase unexpectedValue"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func read<T: FixedWidthInteger>() throws -> T {"
|
||||||
|
print "\t\tlet size = MemoryLayout<T>.size"
|
||||||
|
print "\t\tguard data.count >= size else {"
|
||||||
|
print "\t\t\tthrow ReadError.unexpectedEOF"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\tvar acc: T = 0"
|
||||||
|
print "\t\tdata.prefix(size).forEach { acc = acc << 8 | T($0) }"
|
||||||
|
print "\t\tdata = data.dropFirst(size)"
|
||||||
|
print "\t\treturn acc"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func read() throws -> Bool {"
|
||||||
|
print "\t\ttry read() != UInt8(0)"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func read() throws -> String {"
|
||||||
|
print "\t\tlet size: UInt32 = try self.read()"
|
||||||
|
print "\t\tguard let count = Int(exactly: size) else {"
|
||||||
|
print "\t\t\tthrow ReadError.overflow"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\tguard data.count >= count else {"
|
||||||
|
print "\t\t\tthrow ReadError.unexpectedEOF"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\tdefer {"
|
||||||
|
print "\t\t\tdata = data.dropFirst(count)"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\tif let s = String(data: data.prefix(count), encoding: .utf8) {"
|
||||||
|
print "\t\t\treturn s"
|
||||||
|
print "\t\t} else {"
|
||||||
|
print "\t\t\tthrow ReadError.invalidEncoding"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func read<" \
|
||||||
|
"T: RawRepresentable<Int8>>() throws -> T {"
|
||||||
|
print "\t\tguard let value = T(rawValue: try read()) else {"
|
||||||
|
print "\t\t\tthrow ReadError.unexpectedValue"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\treturn value"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func read<T>("
|
||||||
|
print "\t\t\t_ read: (inout Self) throws -> T) throws -> [T] {"
|
||||||
|
print "\t\tlet size: UInt32 = try self.read()"
|
||||||
|
print "\t\tguard let count = Int(exactly: size) else {"
|
||||||
|
print "\t\t\tthrow ReadError.overflow"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\tvar array = [T]()"
|
||||||
|
print "\t\tarray.reserveCapacity(count)"
|
||||||
|
print "\t\tfor _ in 0..<count {"
|
||||||
|
print "\t\t\tarray.append(try read(&self))"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t\treturn array"
|
||||||
|
print "\t}"
|
||||||
|
print "}"
|
||||||
|
print ""
|
||||||
|
print "public struct RelayWriter {"
|
||||||
|
print "\tpublic var data = Data()"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func append<T: FixedWidthInteger>(_ number: T) {"
|
||||||
|
print "\t\tvar n = number.byteSwapped"
|
||||||
|
print "\t\tfor _ in 0..<MemoryLayout<T>.size {"
|
||||||
|
print "\t\t\tdata.append(UInt8(truncatingIfNeeded: n))"
|
||||||
|
print "\t\t\tn >>= 8"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func append(_ bool: Bool) {"
|
||||||
|
print "\t\tappend(UInt8(bool ? 1 : 0))"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func append(_ string: String) {"
|
||||||
|
print "\t\tlet bytes = string.data(using: .utf8)!"
|
||||||
|
print "\t\tappend(UInt32(bytes.count))"
|
||||||
|
print "\t\tdata.append(bytes)"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func append<" \
|
||||||
|
"T: RawRepresentable<Int8>>(_ value: T) {"
|
||||||
|
print "\t\tappend(value.rawValue)"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func append<T>("
|
||||||
|
print "\t\t\t_ array: Array<T>, _ write: (inout Self, T) -> ()) {"
|
||||||
|
print "\t\tappend(UInt32(array.count))"
|
||||||
|
print "\t\tfor i in 0..<array.count {"
|
||||||
|
print "\t\t\twrite(&self, array[i])"
|
||||||
|
print "\t\t}"
|
||||||
|
print "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic mutating func append<T: RelayEncodable>(_ value: T) {"
|
||||||
|
print "\t\tvalue.encode(to: &self)"
|
||||||
|
print "\t}"
|
||||||
|
print "}"
|
||||||
|
print ""
|
||||||
|
print "public protocol RelayEncodable { " \
|
||||||
|
"func encode(to: inout RelayWriter) }"
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_constant(name, value) {
|
||||||
|
print ""
|
||||||
|
print "public let " decapitalize(PrefixCamel snaketocamel(name)) " = " value
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_enum_value(name, subname, value, cg) {
|
||||||
|
append(cg, "fields",
|
||||||
|
"\tcase " decapitalize(snaketocamel(subname)) " = " value "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_enum(name, cg, swifttype) {
|
||||||
|
swifttype = PrefixCamel name
|
||||||
|
print ""
|
||||||
|
print "public enum " swifttype ": Int8 {"
|
||||||
|
print cg["fields"] "}"
|
||||||
|
|
||||||
|
CodegenSwiftType[name] = swifttype
|
||||||
|
CodegenDeserialize[name] = "%s.read()"
|
||||||
|
for (i in cg)
|
||||||
|
delete cg[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_struct_field(d, cg, camel) {
|
||||||
|
camel = decapitalize(snaketocamel(d["name"]))
|
||||||
|
if (!d["isarray"]) {
|
||||||
|
append(cg, "fields",
|
||||||
|
"\tpublic var " camel ": " CodegenSwiftType[d["type"]] "\n")
|
||||||
|
append(cg, "deserialize",
|
||||||
|
"\t\tself." camel " = try " \
|
||||||
|
sprintf(CodegenDeserialize[d["type"]], "from") "\n")
|
||||||
|
append(cg, "serialize",
|
||||||
|
"\t\tto.append(self." camel ")\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
append(cg, "fields",
|
||||||
|
"\tpublic var " camel ": [" CodegenSwiftType[d["type"]] "]\n")
|
||||||
|
append(cg, "deserialize",
|
||||||
|
"\t\tself." camel " = try from.read() { r in try " \
|
||||||
|
sprintf(CodegenDeserialize[d["type"]], "r") " }\n")
|
||||||
|
append(cg, "serialize",
|
||||||
|
"\t\tto.append(self." camel ") { (w, value) in w.append(value) }\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_struct_tag(d, cg, camel) {
|
||||||
|
camel = decapitalize(snaketocamel(d["name"]))
|
||||||
|
append(cg, "serialize",
|
||||||
|
"\t\tto.append(self." camel ")\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_struct(name, cg, swifttype) {
|
||||||
|
swifttype = PrefixCamel name
|
||||||
|
print ""
|
||||||
|
print "public struct " swifttype " {\n" cg["fields"] "}"
|
||||||
|
print ""
|
||||||
|
print "extension " swifttype ": " PrefixCamel "Encodable {"
|
||||||
|
print "\tpublic init(from: inout RelayReader) throws {"
|
||||||
|
print cg["deserialize"] "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic func encode(to: inout RelayWriter) {"
|
||||||
|
print cg["serialize"] "\t}"
|
||||||
|
print "}"
|
||||||
|
|
||||||
|
CodegenSwiftType[name] = swifttype
|
||||||
|
CodegenDeserialize[name] = "%s.read()"
|
||||||
|
for (i in cg)
|
||||||
|
delete cg[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_union_tag(d, cg) {
|
||||||
|
cg["tagtype"] = d["type"]
|
||||||
|
cg["tagname"] = decapitalize(snaketocamel(d["name"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_union_struct(name, casename, cg, scg, swifttype) {
|
||||||
|
# And thus not all generated structs are present in Types.
|
||||||
|
swifttype = PrefixCamel name snaketocamel(casename)
|
||||||
|
casename = decapitalize(snaketocamel(casename))
|
||||||
|
print ""
|
||||||
|
print "public struct " swifttype ": " PrefixCamel name " {"
|
||||||
|
print "\tpublic var " cg["tagname"] \
|
||||||
|
": " CodegenSwiftType[cg["tagtype"]] " { ." casename " }"
|
||||||
|
print scg["fields"] "}"
|
||||||
|
print ""
|
||||||
|
print "extension " swifttype ": " PrefixCamel "Encodable {"
|
||||||
|
print "\tfileprivate init(from: inout RelayReader) throws {"
|
||||||
|
print scg["deserialize"] "\t}"
|
||||||
|
print ""
|
||||||
|
print "\tpublic func encode(to: inout RelayWriter) {"
|
||||||
|
print scg["serialize"] "\t}"
|
||||||
|
print "}"
|
||||||
|
|
||||||
|
append(cg, "cases", "\tcase ." casename ":\n" \
|
||||||
|
"\t\treturn try " swifttype "(from: &from)\n")
|
||||||
|
|
||||||
|
CodegenSwiftType[name] = swifttype
|
||||||
|
CodegenDeserialize[name] = "%s.read()"
|
||||||
|
for (i in scg)
|
||||||
|
delete scg[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
function codegen_union(name, cg, swifttype, init) {
|
||||||
|
# Classes don't have automatic member-wise initializers,
|
||||||
|
# thus using structs and protocols.
|
||||||
|
swifttype = PrefixCamel name
|
||||||
|
print ""
|
||||||
|
print "public protocol " swifttype ": " PrefixCamel "Encodable {"
|
||||||
|
print "\tvar " cg["tagname"] ": " CodegenSwiftType[cg["tagtype"]] " { get }"
|
||||||
|
print "}"
|
||||||
|
|
||||||
|
init = decapitalize(swifttype)
|
||||||
|
print ""
|
||||||
|
print "public func " init \
|
||||||
|
"(from: inout RelayReader) throws -> " swifttype " {"
|
||||||
|
print "\tlet " cg["tagname"] ": " CodegenSwiftType[cg["tagtype"]] \
|
||||||
|
" = try from.read()"
|
||||||
|
print "\tswitch " cg["tagname"] " {"
|
||||||
|
# TODO: Only generate the default if there are remaining values,
|
||||||
|
# so that swiftc doesn't produce warnings.
|
||||||
|
print cg["cases"] "\tdefault:"
|
||||||
|
print "\t\tthrow RelayReader.ReadError.unexpectedValue"
|
||||||
|
print"\t}"
|
||||||
|
print "}"
|
||||||
|
|
||||||
|
CodegenSwiftType[name] = swifttype
|
||||||
|
CodegenDeserialize[name] = init "(from: &%s)"
|
||||||
|
for (i in cg)
|
||||||
|
delete cg[i]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user