Failure to correctly differentiate between Int and Double parser
rex-remind101 opened this issue · 5 comments
When using OneOf
there is failure to differentiate between an Int and Double.
Examples:
let parser = OneOf {
Double.parser(of: Substring.self).map(.case(Value.float))
Int.parser().map(.case(Value.int))
}
let input = "1.0"
let argument = try parser.parse(input)
XCTAssertEqual(argument, .float(1.0))
passes
let parser = OneOf {
Int.parser().map(.case(Value.int))
Double.parser(of: Substring.self).map(.case(Value.float))
}
let input = "1.0"
let argument = try parser.parse(input)
XCTAssertEqual(argument, .float(1.0))
fails:
caught error: "error: unexpected input
--> input:1:2
1 | 1.0
| ^ expected end of input"
let parser = OneOf {
Int.parser().map(.case(Value.int))
Double.parser(of: Substring.self).map(.case(Value.float))
}
let input = "1.0"
let argument = try parser.parse(input)
XCTAssertEqual(argument, .int(1))
passes
let parser = OneOf {
Double.parser(of: Substring.self).map(.case(Value.float))
Int.parser().map(.case(Value.int))
}
let input = "1.0"
let argument = try parser.parse(input)
XCTAssertEqual(argument, .int(1))
fails:
XCTAssertEqual failed: ("float(1.0)") is not equal to ("int(1)")
@rex-remind101 OneOf
attempts each parser until one succeeds.
When the input is "1.0"
:
Int.parser()
successfully parses1
and leaves a remaining input of".0"
Double.parser()
successfully parses1.0
and leaves a remaining input of""
(no remaining input)
What might be causing some confusions is that try parser.parse(input)
will fail if the parser does not consume the entirety of the input.
You can use a mutable value, eg: try parser.parse(&input)
, to parse a value incrementally. The documentation for Parser/parse(_:) goes into this in more detail.
So for your failing examples:
let parser = OneOf {
Int.parser().map(.case(Value.int))
Double.parser(of: Substring.self).map(.case(Value.float))
}
let input = "1.0"
let argument = try parser.parse(input)
XCTAssertEqual(argument, .float(1.0))
Int.parser()
succeeds and parses.int(1)
- The parser fails because there is a remaining input of
".0"
let parser = OneOf {
Double.parser(of: Substring.self).map(.case(Value.float))
Int.parser().map(.case(Value.int))
}
let input = "1.0"
let argument = try parser.parse(input)
XCTAssertEqual(argument, .int(1))
Double.parser()
succeeds and parses.float(1.0)
- The parser succeeds because all input was consumed
- The assertion fails because
.float(1.0)
does not equal.int(1)
I guess that's just not the semantics I want, a decimal point implies a float number for my use case and I believe that is typical. I ended up doing a copy-paste and slight refactor of IntParser
so that it fails on look-ahead if it finds ","
, then by organizing MyIntParser before Double.parser it appears to succeed in all my tested cases.
Fwiw, I don't see how the current semantics could allow clear disambiguation between Int
and Double
at all in a OneOf
, unless I'm missing something, and this seems to me like something people may want in general.
@rex-remind101 You can probably implement the lookahead with a Not { "." }
parser:
let parser = OneOf {
Parse(.case(Value.int)) {
Int.parser()
Not { "." }
}
Double.parser()
.map(.case(Value.float))
}
Amazing, I'll give that a shot later but makes sense.
May be useful to add this pattern to the Backtracking section https://pointfreeco.github.io/swift-parsing/0.9.0/documentation/parsing/backtracking