Skip to content

Commit

Permalink
Implement YAML schemas for property reading and writing
Browse files Browse the repository at this point in the history
  • Loading branch information
kdubb committed Jun 10, 2024
1 parent 7752668 commit faf86a4
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 106 deletions.
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
"version": "1.1.1"
}
},
{
"package": "Regex",
"repositoryURL": "https://github.com/sharplet/Regex.git",
"state": {
"branch": null,
"revision": "76c2b73d4281d77fc3118391877efd1bf972f515",
"version": "2.1.1"
}
},
{
"package": "swift-collections",
"repositoryURL": "https://github.com/apple/swift-collections.git",
Expand Down
10 changes: 7 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import PackageDescription
let pcDeps: [Target.Dependency] = [
"BigInt",
.product(name: "Collections", package: "swift-collections"),
.byName(name: "Float16", condition: .when(platforms: [.macOS, .macCatalyst, .linux]))
.byName(name: "Float16", condition: .when(platforms: [.macOS, .macCatalyst, .linux])),
.product(name: "Regex", package: "regex"),
]
#else
let pcDeps: [Target.Dependency] = [
"BigInt",
.product(name: "Collections", package: "swift-collections"),
"Float16",
.product(name: "Regex", package: "regex"),
]
#endif

Expand All @@ -43,7 +45,8 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/attaswift/BigInt.git", .upToNextMinor(from: "5.3.0")),
.package(url: "https://github.com/SusanDoggie/Float16.git", .upToNextMinor(from: "1.1.1")),
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.4"))
.package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.4")),
.package(url: "https://github.com/sharplet/Regex.git", .upToNextMinor(from: "2.1.1"))
],
targets: [
.target(
Expand Down Expand Up @@ -100,7 +103,8 @@ let package = Package(
"PotentCodables",
"BigInt",
"Cfyaml",
.product(name: "Collections", package: "swift-collections")
.product(name: "Collections", package: "swift-collections"),
.product(name: "Regex", package: "regex")
]
),
.testTarget(
Expand Down
34 changes: 34 additions & 0 deletions Sources/PotentCodables/Regexes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Pattern.swift
// PotentCodables
//
// Copyright © 2019 Outfox, inc.
//
//
// Distributed under the MIT License, See LICENSE for details.
//

import Foundation
import Regex


extension Regex: ExpressibleByStringLiteral {

public init(stringLiteral value: StaticString) {
self.init(value)
}

}

extension Regex {

public static func anyOf(_ regexes: Regex...) -> Regex {
do {
return try Regex(string: regexes.map { "(\($0.description))" }.joined(separator: "|"))
}
catch {
preconditionFailure("unexpected error creating regex from joined set: \(error)")
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Cfyaml.swift
// Libfyaml.swift
// PotentCodables
//
// Copyright © 2021 Outfox, inc.
Expand All @@ -11,7 +11,7 @@
import Cfyaml


enum Libfyaml {
internal enum Libfyaml {

internal static func createParser() -> OpaquePointer? {

Expand Down
8 changes: 6 additions & 2 deletions Sources/PotentYAML/YAML.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ public enum YAML {
self.isNegative = isNegative
}

public init(_ value: String) {
self.init(value, isInteger: value.allSatisfy { $0.isNumber || $0 == "-" }, isNegative: value.hasPrefix("-"))
public init(_ value: String, schema: YAMLSchema = .core) {
self.init(value, isInteger: schema.isInt(value), isNegative: value.hasPrefix("-"))
}

public init<T: FloatingPoint>(_ value: T) {
Expand Down Expand Up @@ -193,6 +193,10 @@ public enum YAML {
case doubleQuoted = 2
case literal = 3
case folded = 4

public var isQuoted: Bool {
self == .singleQuoted || self == .doubleQuoted
}
}

public enum CollectionStyle: Int32 {
Expand Down
131 changes: 38 additions & 93 deletions Sources/PotentYAML/YAMLReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import Cfyaml
import Foundation


internal enum YAMLReader {
internal struct YAMLReader {

typealias Error = YAMLSerialization.Error

static func read(data: Data) throws -> YAML.Sequence {
static func read(data: Data, schema: YAMLSchema = .core) throws -> YAML.Sequence {

guard let parser = Libfyaml.createParser().map(Parser.init) else {
throw Error.unableToCreateParser
Expand All @@ -29,11 +29,19 @@ internal enum YAMLReader {

try parser.expect(eventType: FYET_STREAM_START)

return try stream(parser: parser)
return try YAMLReader(parser: parser, schema: schema).stream()
}
}

static func stream(parser: Parser) throws -> YAML.Sequence {
let parser: Parser
let schema: YAMLSchema

init(parser: Parser, schema: YAMLSchema = .core) {
self.parser = parser
self.schema = schema
}

private func stream() throws -> YAML.Sequence {

var documents: YAML.Sequence = []

Expand All @@ -47,7 +55,7 @@ internal enum YAMLReader {
}

if event.type == FYET_DOCUMENT_START {
documents.append(try document(parser: parser))
documents.append(try document())
}
else {
throw parser.error(fallback: .unexpectedEvent)
Expand All @@ -58,20 +66,20 @@ internal enum YAMLReader {
}


static func document(parser: Parser) throws -> YAML {
private func document() throws -> YAML {

let root = try parser.next()
defer { parser.free(event: root) }

let document = try value(event: root, parser: parser)
let document = try value(event: root)

try parser.expect(eventType: FYET_DOCUMENT_END)

return document
}


static func mapping(parser: Parser) throws -> YAML.Mapping {
private func mapping() throws -> YAML.Mapping {

var result: YAML.Mapping = []

Expand All @@ -84,12 +92,12 @@ internal enum YAMLReader {
break
}

let key = try value(event: event, parser: parser)
let key = try value(event: event)

let valEvent = try parser.next()
defer { parser.free(event: valEvent) }

let val = try value(event: valEvent, parser: parser)
let val = try value(event: valEvent)

result.append(YAML.Mapping.Element(key: key, value: val))
}
Expand All @@ -98,7 +106,7 @@ internal enum YAMLReader {
}


static func sequence(parser: Parser) throws -> YAML.Sequence {
private func sequence() throws -> YAML.Sequence {

var result: YAML.Sequence = []

Expand All @@ -111,24 +119,18 @@ internal enum YAMLReader {
break
}

let val = try value(event: event, parser: parser)
let val = try value(event: event)

result.append(val)
}

return result
}

private static let nullRegex = RegEx(pattern: #"^(null|Null|NULL|~)$"#)
private static let trueRegex = RegEx(pattern: #"^(true|True|TRUE)$"#)
private static let falseRegex = RegEx(pattern: #"^(false|False|FALSE)$"#)
private static let integerRegex = RegEx(pattern: #"^(([-+]?[0-9]+)|(0o[0-7]+)|(0x[0-9a-fA-F]+))$"#)
private static let floatRegex = RegEx(pattern: #"^([-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?)$"#)
private static let infinityRegex = RegEx(pattern: #"^([-+]?(\.inf|\.Inf|\.INF))$"#)
private static let nanRegex = RegEx(pattern: #"^(\.nan|\.NaN|\.NAN)$"#)

static func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) throws -> YAML {
let stringStyle = YAML.StringStyle(rawValue: style.rawValue) ?? .any
private func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) throws -> YAML {
guard let stringStyle = YAML.StringStyle(rawValue: style.rawValue) else {
preconditionFailure("String style not recognized")
}

switch tag {
case .none:
Expand All @@ -152,7 +154,7 @@ internal enum YAMLReader {

}

static func untaggedScalar(
private func untaggedScalar(
value: String,
stringStyle: YAML.StringStyle,
style: fy_scalar_style,
Expand All @@ -161,40 +163,34 @@ internal enum YAMLReader {

if style == FYSS_PLAIN {

if nullRegex.matches(string: value) {
if schema.isNull(value) {
return .null(anchor: anchor)
}

if trueRegex.matches(string: value) {
if schema.isTrue(value) {
return .bool(true, anchor: anchor)
}

if falseRegex.matches(string: value) {
if schema.isFalse(value) {
return .bool(false, anchor: anchor)
}

if integerRegex.matches(string: value) {
return .integer(YAML.Number(value), anchor: anchor)
}

if floatRegex.matches(string: value) {
return .float(YAML.Number(value), anchor: anchor)
}

if infinityRegex.matches(string: value) {
return .float(YAML.Number(value.lowercased()), anchor: anchor)
if schema.isInt(value) {
return .integer(YAML.Number(value.lowercased(), isInteger: true, isNegative: value.hasPrefix("-")),
anchor: anchor)
}

if nanRegex.matches(string: value) {
return .float(YAML.Number(value.lowercased()), anchor: anchor)
if schema.isFloat(value) {
return .float(YAML.Number(value.lowercased(), isInteger: false, isNegative: value.hasPrefix("-")),
anchor: anchor)
}

}

return .string(value, style: stringStyle, tag: nil, anchor: nil)
}

static func boolTaggedScalar(value: String, anchor: String?) throws -> YAML {
private func boolTaggedScalar(value: String, anchor: String?) throws -> YAML {

if let bool = Bool(value.lowercased()) {
return .bool(bool, anchor: anchor)
Expand All @@ -209,14 +205,14 @@ internal enum YAMLReader {
throw Error.invalidTaggedBool
}

static func value(event: Parser.Event, parser: Parser) throws -> YAML {
private func value(event: Parser.Event) throws -> YAML {
switch event.type {
case FYET_MAPPING_START:
let value = try mapping(parser: parser)
let value = try mapping()
return .mapping(value, style: event.style.collectionStyle, tag: YAML.Tag(event.tag), anchor: event.anchor)

case FYET_SEQUENCE_START:
let value = try sequence(parser: parser)
let value = try sequence()
return .sequence(value, style: event.style.collectionStyle, tag: YAML.Tag(event.tag), anchor: event.anchor)

case FYET_SCALAR:
Expand Down Expand Up @@ -338,57 +334,6 @@ extension String {

}


private class RegEx {

struct Options: OptionSet {
let rawValue: Int32

init(rawValue: Int32) {
self.rawValue = rawValue
}

static let basic = Options([])
static let extended = Options(rawValue: 1 << 0)
static let caseInsensitive = Options(rawValue: 1 << 1)
static let resultOnly = Options(rawValue: 1 << 2)
static let newLineSensitive = Options(rawValue: 1 << 3)
}

struct MatchOptions: OptionSet {
let rawValue: Int32

init(rawValue: Int32) {
self.rawValue = rawValue
}

static let firstCharacterNotAtBeginningOfLine = MatchOptions(rawValue: REG_NOTBOL)
static let lastCharacterNotAtEndOfLine = MatchOptions(rawValue: REG_NOTEOL)
}

private var posixRegex = regex_t()

init(pattern: String, options: Options = [.extended]) {
let res = pattern.withCString { patternPtr in
regcomp(&posixRegex, patternPtr, options.rawValue)
}
guard res == 0 else {
fatalError("invalid pattern")
}
}

deinit {
regfree(&posixRegex)
}

func matches(string: String, options: MatchOptions = []) -> Bool {
return string.withCString { stringPtr in
regexec(&posixRegex, stringPtr, 0, nil, options.rawValue) == 0
}
}

}

extension fy_node_style {

var collectionStyle: YAML.CollectionStyle {
Expand Down
Loading

0 comments on commit faf86a4

Please sign in to comment.