Conditional parsing based on previous output #288
-
swift-parsing v0.9.2 I'm trying to parse input consisting of a single character op code followed by an int. E.g. "A12", "B50", "C4". This is simple enough to parse using an enum for the opcodes and a struct to pair the op with the int. Method 1 uses Are there any better ways to achieve this? enum Op: String, CaseIterable {
case opA: "A"
case opB: "B"
case opC: "C"
// And many more...
var valueRequired: Bool {
switch self {
case .opC:
return false
default:
return true
}
}
}
struct OpWithValue {
let op: Op
let value: Int?
}
// Method 1
let OpParser1 = OneOf {
Parse(OpWithValue.init) {
Op.parser()
Int.parser()
}
Op.opC.rawValue.map {
OpWithValue(op: .opC, value: nil)
}
}
// Method 2
let OpParser2 = Parse {
Op.parser(of: Substring.self)
}.flatMap { op in
if op.valueRequired {
Int.parser().map {
OpWithValue(op: op, value: $0)
}
} else {
Optionally {
Int.parser()
}.map {
OpWithValue(op: op, value: $0)
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
Hey @andrews05. There are indeed several ways to implement a solution to your problem, but the simplest is probably to attack it at its root: But I feel that it would be preferable to model your domain so it prevents incoherent states. One way to do it is to make the code in the enum: enum Op {
case opA(Int)
case opB(Int)
case opC(Int?)
} You can then create a few helpers: extension Op {
static func withValue(
_ label: Substring,
as case: @escaping (Int) -> Op
) -> some Parser<Substring, Op> {
Parse(`case`) {
StartsWith(label)
Digits()
}
}
static func withOptionalValue(
_ label: Substring,
as case: @escaping (Int?) -> Op
) -> some Parser<Substring, Op> {
Parse(`case`) {
StartsWith(label)
Optionally {
Digits()
}
}
}
}
extension Op {
static var parser: some Parser<Substring, Self> {
OneOf {
withValue("A", as: opA)
withValue("B", as: opB)
withOptionalValue("C", as: opC)
}
}
} I feel that this thing scales relatively well: if you add one case, you add one line to the Note that I nested everything in This is my personal take on the problem, and your solution has its merits too, so you should really use what you're most confortable with, especially depending on how this values are used once they're parsed. |
Beta Was this translation helpful? Give feedback.
-
Another option I explored: OneOf {
for op in Op.allCases {
Parse(OpWithValue.init) {
op.rawValue.map { op }
if op.valueRequired {
Digits().map { $0 as Int? }
} else {
Optionally { Digits() }
}
}
}
} This one has the most helpful/accurate error diagnostic on failure. |
Beta Was this translation helpful? Give feedback.
Hey @andrews05. There are indeed several ways to implement a solution to your problem, but the simplest is probably to attack it at its root:
OpWithValue
is a type that allows incoherent states (that isopA
with anil
value for example). It means that you need to take of this at the parser level and prevent it to create such values by failing if the input is not coherent. You can for example usefilter
to discard invalid configurations.But I feel that it would be preferable to model your domain so it prevents incoherent states. One way to do it is to make the code in the enum:
You can then create a few helpers: