Skip to content

Commit 5d5fcf3

Browse files
committed
fix(floats): preserve parsedigits extension API
Keep the historical parsedigits signature and decimal behavior for custom AbstractConf implementations. Route built-in parsers through a separate context-aware core so matching decimal and delimiter bytes remain unambiguous without breaking downstream packages.
1 parent a55c30c commit 5d5fcf3

3 files changed

Lines changed: 26 additions & 6 deletions

File tree

src/floats.jl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ end
299299
end
300300

301301
# start parsing digits or decimal point; we start digits as UInt64(0) and can _widen type if needed
302-
x, code, pos = parsedigits(conf, source, pos, len, b, code, options, decimal, UInt64(0), neg, startpos)
302+
x, code, pos = parsedigits_context(conf, source, pos, len, b, code, options, decimal, UInt64(0), neg, startpos)
303303
if !isfinite(x)
304304
code |= SPECIAL_VALUE
305305
end
@@ -334,10 +334,18 @@ getx(x, f) = f === nothing ? x : nothing
334334
# statically compiled (`juliac --trim`) binary the widened instance doesn't exist, so
335335
# parsing a wide-digit float would fail at runtime. The `@noinline` wrappers keep each
336336
# ladder step compiling separately, which is what bounds base-case compilation.
337-
@noinline _parsedigits(conf::AbstractConf{T}, source, pos, len, b, code, options, decimal::UInt8, digits::IntType, neg::Bool, startpos, overflow_invalid::Bool, ndigits::Int, f::F) where {T, IntType, F} =
338-
parsedigits(conf, source, pos, len, b, code, options, decimal, digits, neg, startpos, overflow_invalid, ndigits, f)::Tuple{rettype(T), ReturnCode, Int}
337+
@noinline _parsedigits(conf::AbstractConf{T}, source, pos, len, b, code, options, digits::IntType, neg::Bool, startpos, overflow_invalid::Bool, ndigits::Int, f::F) where {T, IntType, F} =
338+
parsedigits(conf, source, pos, len, b, code, options, digits, neg, startpos, overflow_invalid, ndigits, f)::Tuple{rettype(T), ReturnCode, Int}
339339

340-
@inline function parsedigits(conf::AbstractConf{T}, source, pos, len, b, code, options, decimal::UInt8, digits::IntType, neg::Bool, startpos, overflow_invalid::Bool=false, ndigits::Int=0, f::F=nothing) where {T, IntType, F}
340+
@noinline _parsedigits_context(conf::AbstractConf{T}, source, pos, len, b, code, options, decimal::UInt8, digits::IntType, neg::Bool, startpos, overflow_invalid::Bool, ndigits::Int, f::F) where {T, IntType, F} =
341+
parsedigits_context(conf, source, pos, len, b, code, options, decimal, digits, neg, startpos, overflow_invalid, ndigits, f)::Tuple{rettype(T), ReturnCode, Int}
342+
343+
# Custom AbstractConf implementations use this entry point. Keep its historical
344+
# signature and decimal behavior while built-in parsers pass explicit context.
345+
@inline parsedigits(conf::AbstractConf{T}, source, pos, len, b, code, options, digits::IntType, neg::Bool, startpos, overflow_invalid::Bool=false, ndigits::Int=0, f::F=nothing) where {T, IntType, F} =
346+
parsedigits_context(conf, source, pos, len, b, code, options, options.decimal, digits, neg, startpos, overflow_invalid, ndigits, f)
347+
348+
@inline function parsedigits_context(conf::AbstractConf{T}, source, pos, len, b, code, options, decimal::UInt8, digits::IntType, neg::Bool, startpos, overflow_invalid::Bool=false, ndigits::Int=0, f::F=nothing) where {T, IntType, F}
341349
x = zero(T)
342350
anydigits = false
343351
has_groupmark = _has_groupmark(options, code)
@@ -351,7 +359,7 @@ getx(x, f) = f === nothing ? x : nothing
351359
while true
352360
if b <= 0x09
353361
if overflows(IntType) && digits > overflowval(IntType)
354-
return _parsedigits(conf, source, pos, len, b + UInt8('0'), code, options, decimal, _widen(digits), neg, startpos, overflow_invalid, ndigits, f)::Tuple{rettype(T), ReturnCode, Int}
362+
return _parsedigits_context(conf, source, pos, len, b + UInt8('0'), code, options, decimal, _widen(digits), neg, startpos, overflow_invalid, ndigits, f)::Tuple{rettype(T), ReturnCode, Int}
355363
elseif ndigits > maxdigits(T)
356364
# if input is way too big, just bail
357365
fastseek!(source, startpos - 1)

src/ints.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function parsenumber(source, pos, len, b, code, f::F, opts, checkdelim::Bool) wh
128128
b = peekbyte(source, pos)
129129
# parse rest of number
130130
decimal = _effective_decimal(opts, code, checkdelim)
131-
_, code, pos = parsedigits(DefaultConf{Number}(), source, pos, len, b, code, opts, decimal, Int64(0), neg, startpos, true, 0, f)
131+
_, code, pos = parsedigits_context(DefaultConf{Number}(), source, pos, len, b, code, opts, decimal, Int64(0), neg, startpos, true, 0, f)
132132
if invalid(code)
133133
# by default, parsedigits only has up to Float64 precision; if we overflow
134134
# let's try BigFloat

test/floats.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,18 @@ end
528528
Parsers.xparse(BigFloat, bytes; decimal=',', delim=';')
529529
@test bytes == codeunits("1,2;3")
530530
@test Parsers.parse(BigFloat, collect(codeunits("1.")), Parsers.Options(delim='.')) == BigFloat(1)
531+
532+
# Custom AbstractConf implementations use this historical parsedigits entry point.
533+
source = codeunits("1,2")
534+
options = Parsers.Options(decimal=',', delim=',')
535+
x, code, pos = Parsers.parsedigits(
536+
Parsers.DefaultConf{Float64}(), source, 1, length(source), source[1],
537+
Parsers.SUCCESS, options, UInt64(0), false, 1, false, 0, nothing,
538+
)
539+
@test x == 1.2
540+
@test Parsers.ok(code)
541+
@test Parsers.eof(code)
542+
@test pos == 4
531543
end
532544

533545
@testset "BigFloats" begin

0 commit comments

Comments
 (0)