fix(parsing): a zero followed by a sign is text, not an octal integer
Int(_, radix:) accepts a sign, so 0-3 (a CPU range, a port range) was read as the integer -3, and 0x-1, 0b-1 and 0o-1 as negative numbers. After a radix prefix only digits may follow. Octal, hex, binary and signed integers read as before.
This commit is contained in:
parent
ae85ec97c9
commit
d2421aa0ce
@ -401,6 +401,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A plain scalar that starts with `0` and continues with a sign is text, not an
|
||||
octal integer. `Int(_, radix:)` accepts a leading sign, so `0-3` (a CPU set, a
|
||||
port range) read as the integer -3, and `0x-1`, `0b-1` and `0o-1` as negative
|
||||
numbers. After a radix prefix only digits may follow. Octal (`0755`),
|
||||
hexadecimal, binary and signed integers read as before.
|
||||
|
||||
- A string the emitter wrote without quotes could be read back as something
|
||||
else, so the string `yes` (and `no`, `on`, `off` in every case), `0x1F`,
|
||||
`0b1`, `0o17`, `1_000`, `.inf` and `.nan` did not survive a dump and parse.
|
||||
|
||||
@ -69,7 +69,8 @@ extension PureYAML.Parsing {
|
||||
|
||||
for radixPrefix in radixPrefixes where unsigned.hasPrefix(radixPrefix.prefix) {
|
||||
let digits = String(unsigned.dropFirst(radixPrefix.prefix.count))
|
||||
guard !digits.isEmpty, let magnitude = Int(digits, radix: radixPrefix.radix) else {
|
||||
// `Int(_, radix:)` accepts a sign, so `0-3` would read as minus three: after the prefix only digits may follow
|
||||
guard let first = digits.first, first != "-", first != "+", let magnitude = Int(digits, radix: radixPrefix.radix) else {
|
||||
return nil
|
||||
}
|
||||
return negative ? -magnitude : magnitude
|
||||
|
||||
25
Tests/SignedOctalLookalikeTests.swift
Normal file
25
Tests/SignedOctalLookalikeTests.swift
Normal file
@ -0,0 +1,25 @@
|
||||
@testable import PureYAML
|
||||
import Testing
|
||||
|
||||
/// A plain scalar that starts with `0` is an octal integer only when everything after the `0` is octal digits. A sign after the leading zero makes it text:
|
||||
/// `0-3` is the range "0 to 3" (a CPU set, a port range), not minus three.
|
||||
@Suite("Plain scalars that begin with a zero")
|
||||
struct SignedOctalLookalikeTests {
|
||||
@Test("A zero followed by a sign is text", arguments: ["0-3", "0-15", "0+1", "00-1", "0x-1", "0b-1", "0o-1"])
|
||||
func test_signAfterTheLeadingZeroIsText(_ scalar: String) throws {
|
||||
#expect(try PureYAML.parse("key: \(scalar)\n") == .mapping(.init([.init(key: "key", value: .string(scalar))])))
|
||||
}
|
||||
|
||||
@Test("Octal, hexadecimal, binary and signed integers stay integers", arguments: [
|
||||
("0755", 493), ("0", 0), ("-0", 0), ("0x1F", 31), ("0b101", 5), ("0o17", 15), ("-12", -12), ("+7", 7), ("-0755", -493),
|
||||
])
|
||||
func test_integersAreStillIntegers(_ scalar: String, _ expected: Int) throws {
|
||||
#expect(try PureYAML.parse("key: \(scalar)\n") == .mapping(.init([.init(key: "key", value: .int(expected))])))
|
||||
}
|
||||
|
||||
@Test("A range written with a leading zero survives a round trip", arguments: ["0-3", "0-15"])
|
||||
func test_rangeRoundTrips(_ scalar: String) throws {
|
||||
let value = PureYAML.Model.Value.mapping(.init([.init(key: "cpuset", value: .string(scalar))]))
|
||||
#expect(try PureYAML.parse(PureYAML.dump(value)) == value)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user