https://github.com/JuliaLang/julia
Revision 06a49a392db85e1f55a1810669559389e8ecf37c authored by Simeon David Schaub on 25 April 2022, 12:30:31 UTC, committed by Simeon David Schaub on 25 April 2022, 20:15:55 UTC
Not sure whether putting hints into the lowering pass like this is a great solution. I thought about maybe using Julia-side error hints for this instead, but using some kind of pattern-matching doesn't seem like a great solution either. fixes #45031
1 parent 9d14cb1
Tip revision: 06a49a392db85e1f55a1810669559389e8ecf37c authored by Simeon David Schaub on 25 April 2022, 12:30:31 UTC
improve error message for invalid function args
improve error message for invalid function args
Tip revision: 06a49a3
parse.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
import Base.Checked: add_with_overflow, mul_with_overflow
## string to integer functions ##
"""
parse(type, str; base)
Parse a string as a number. For `Integer` types, a base can be specified
(the default is 10). For floating-point types, the string is parsed as a decimal
floating-point number. `Complex` types are parsed from decimal strings
of the form `"R±Iim"` as a `Complex(R,I)` of the requested type; `"i"` or `"j"` can also be
used instead of `"im"`, and `"R"` or `"Iim"` are also permitted.
If the string does not contain a valid number, an error is raised.
!!! compat "Julia 1.1"
`parse(Bool, str)` requires at least Julia 1.1.
# Examples
```jldoctest
julia> parse(Int, "1234")
1234
julia> parse(Int, "1234", base = 5)
194
julia> parse(Int, "afc", base = 16)
2812
julia> parse(Float64, "1.2e-3")
0.0012
julia> parse(Complex{Float64}, "3.2e-1 + 4.5im")
0.32 + 4.5im
```
"""
parse(T::Type, str; base = Int)
function parse(::Type{T}, c::AbstractChar; base::Integer = 10) where T<:Integer
a::Int = (base <= 36 ? 10 : 36)
2 <= base <= 62 || throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base"))
d = '0' <= c <= '9' ? c-'0' :
'A' <= c <= 'Z' ? c-'A'+10 :
'a' <= c <= 'z' ? c-'a'+a : throw(ArgumentError("invalid digit: $(repr(c))"))
d < base || throw(ArgumentError("invalid base $base digit $(repr(c))"))
convert(T, d)
end
function parseint_iterate(s::AbstractString, startpos::Int, endpos::Int)
(0 < startpos <= endpos) || (return Char(0), 0, 0)
j = startpos
c, startpos = iterate(s,startpos)::Tuple{Char, Int}
c, startpos, j
end
function parseint_preamble(signed::Bool, base::Int, s::AbstractString, startpos::Int, endpos::Int)
c, i, j = parseint_iterate(s, startpos, endpos)
while isspace(c)
c, i, j = parseint_iterate(s,i,endpos)
end
(j == 0) && (return 0, 0, 0)
sgn = 1
if signed
if c == '-' || c == '+'
(c == '-') && (sgn = -1)
c, i, j = parseint_iterate(s,i,endpos)
end
end
while isspace(c)
c, i, j = parseint_iterate(s,i,endpos)
end
(j == 0) && (return 0, 0, 0)
if base == 0
if c == '0' && i <= endpos
c, i = iterate(s,i)::Tuple{Char, Int}
base = c=='b' ? 2 : c=='o' ? 8 : c=='x' ? 16 : 10
if base != 10
c, i, j = parseint_iterate(s,i,endpos)
end
else
base = 10
end
end
return sgn, base, j
end
@inline function __convert_digit(_c::UInt32, base)
_0 = UInt32('0')
_9 = UInt32('9')
_A = UInt32('A')
_a = UInt32('a')
_Z = UInt32('Z')
_z = UInt32('z')
a::UInt32 = base <= 36 ? 10 : 36
d = _0 <= _c <= _9 ? _c-_0 :
_A <= _c <= _Z ? _c-_A+ UInt32(10) :
_a <= _c <= _z ? _c-_a+a : UInt32(base)
end
function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int, base_::Integer, raise::Bool) where T<:Integer
sgn, base, i = parseint_preamble(T<:Signed, Int(base_), s, startpos, endpos)
if sgn == 0 && base == 0 && i == 0
raise && throw(ArgumentError("input string is empty or only contains whitespace"))
return nothing
end
if !(2 <= base <= 62)
raise && throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base"))
return nothing
end
if i == 0
raise && throw(ArgumentError("premature end of integer: $(repr(SubString(s,startpos,endpos)))"))
return nothing
end
c, i = parseint_iterate(s,i,endpos)
if i == 0
raise && throw(ArgumentError("premature end of integer: $(repr(SubString(s,startpos,endpos)))"))
return nothing
end
base = convert(T, base)
# Special case the common cases of base being 10 or 16 to avoid expensive runtime div
m::T = base == 10 ? div(typemax(T) - T(9), T(10)) :
base == 16 ? div(typemax(T) - T(15), T(16)) :
div(typemax(T) - base + 1, base)
n::T = 0
while n <= m
# Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80
_c = reinterpret(UInt32, c) >> 24
d::T = __convert_digit(_c, base)
if d >= base
raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))"))
return nothing
end
n *= base
n += d
if i > endpos
n *= sgn
return n
end
c, i = iterate(s,i)::Tuple{Char, Int}
isspace(c) && break
end
(T <: Signed) && (n *= sgn)
while !isspace(c)
# Fast path from `UInt32(::Char)`; non-ascii will be >= 0x80
_c = reinterpret(UInt32, c) >> 24
d::T = __convert_digit(_c, base)
if d >= base
raise && throw(ArgumentError("invalid base $base digit $(repr(c)) in $(repr(SubString(s,startpos,endpos)))"))
return nothing
end
(T <: Signed) && (d *= sgn)
n, ov_mul = mul_with_overflow(n, base)
n, ov_add = add_with_overflow(n, d)
if ov_mul | ov_add
raise && throw(OverflowError("overflow parsing $(repr(SubString(s,startpos,endpos)))"))
return nothing
end
(i > endpos) && return n
c, i = iterate(s,i)::Tuple{Char, Int}
end
while i <= endpos
c, i = iterate(s,i)::Tuple{Char, Int}
if !isspace(c)
raise && throw(ArgumentError("extra characters after whitespace in $(repr(SubString(s,startpos,endpos)))"))
return nothing
end
end
return n
end
function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString{String}},
startpos::Int, endpos::Int, base::Integer, raise::Bool)
if isempty(sbuff)
raise && throw(ArgumentError("input string is empty"))
return nothing
end
if isnumeric(sbuff[1])
intres = tryparse_internal(UInt8, sbuff, startpos, endpos, base, false)
(intres == 1) && return true
(intres == 0) && return false
raise && throw(ArgumentError("invalid Bool representation: $(repr(sbuff))"))
end
orig_start = startpos
orig_end = endpos
# Ignore leading and trailing whitespace
while startpos <= endpos && isspace(sbuff[startpos])
startpos = nextind(sbuff, startpos)
end
while endpos >= startpos && isspace(sbuff[endpos])
endpos = prevind(sbuff, endpos)
end
len = endpos - startpos + 1
p = pointer(sbuff) + startpos - 1
GC.@preserve sbuff begin
(len == 4) && (0 == _memcmp(p, "true", 4)) && (return true)
(len == 5) && (0 == _memcmp(p, "false", 5)) && (return false)
end
if raise
substr = SubString(sbuff, orig_start, orig_end) # show input string in the error to avoid confusion
if all(isspace, substr)
throw(ArgumentError("input string only contains whitespace"))
else
throw(ArgumentError("invalid Bool representation: $(repr(substr))"))
end
end
return nothing
end
@inline function check_valid_base(base)
if 2 <= base <= 62
return base
end
throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base"))
end
"""
tryparse(type, str; base)
Like [`parse`](@ref), but returns either a value of the requested type,
or [`nothing`](@ref) if the string does not contain a valid number.
"""
function tryparse(::Type{T}, s::AbstractString; base::Union{Nothing,Integer} = nothing) where {T<:Integer}
# Zero base means, "figure it out"
tryparse_internal(T, s, firstindex(s), lastindex(s), base===nothing ? 0 : check_valid_base(base), false)
end
function parse(::Type{T}, s::AbstractString; base::Union{Nothing,Integer} = nothing) where {T<:Integer}
convert(T, tryparse_internal(T, s, firstindex(s), lastindex(s),
base===nothing ? 0 : check_valid_base(base), true))
end
## string to float functions ##
function tryparse(::Type{Float64}, s::String)
hasvalue, val = ccall(:jl_try_substrtod, Tuple{Bool, Float64},
(Ptr{UInt8},Csize_t,Csize_t), s, 0, sizeof(s))
hasvalue ? val : nothing
end
function tryparse(::Type{Float64}, s::SubString{String})
hasvalue, val = ccall(:jl_try_substrtod, Tuple{Bool, Float64},
(Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset, s.ncodeunits)
hasvalue ? val : nothing
end
function tryparse_internal(::Type{Float64}, s::String, startpos::Int, endpos::Int)
hasvalue, val = ccall(:jl_try_substrtod, Tuple{Bool, Float64},
(Ptr{UInt8},Csize_t,Csize_t), s, startpos-1, endpos-startpos+1)
hasvalue ? val : nothing
end
function tryparse_internal(::Type{Float64}, s::SubString{String}, startpos::Int, endpos::Int)
hasvalue, val = ccall(:jl_try_substrtod, Tuple{Bool, Float64},
(Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset+startpos-1, endpos-startpos+1)
hasvalue ? val : nothing
end
function tryparse(::Type{Float32}, s::String)
hasvalue, val = ccall(:jl_try_substrtof, Tuple{Bool, Float32},
(Ptr{UInt8},Csize_t,Csize_t), s, 0, sizeof(s))
hasvalue ? val : nothing
end
function tryparse(::Type{Float32}, s::SubString{String})
hasvalue, val = ccall(:jl_try_substrtof, Tuple{Bool, Float32},
(Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset, s.ncodeunits)
hasvalue ? val : nothing
end
function tryparse_internal(::Type{Float32}, s::String, startpos::Int, endpos::Int)
hasvalue, val = ccall(:jl_try_substrtof, Tuple{Bool, Float32},
(Ptr{UInt8},Csize_t,Csize_t), s, startpos-1, endpos-startpos+1)
hasvalue ? val : nothing
end
function tryparse_internal(::Type{Float32}, s::SubString{String}, startpos::Int, endpos::Int)
hasvalue, val = ccall(:jl_try_substrtof, Tuple{Bool, Float32},
(Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset+startpos-1, endpos-startpos+1)
hasvalue ? val : nothing
end
tryparse(::Type{T}, s::AbstractString) where {T<:Union{Float32,Float64}} = tryparse(T, String(s))
tryparse(::Type{Float16}, s::AbstractString) =
convert(Union{Float16, Nothing}, tryparse(Float32, s))
tryparse_internal(::Type{Float16}, s::AbstractString, startpos::Int, endpos::Int) =
convert(Union{Float16, Nothing}, tryparse_internal(Float32, s, startpos, endpos))
## string to complex functions ##
function tryparse_internal(::Type{Complex{T}}, s::Union{String,SubString{String}}, i::Int, e::Int, raise::Bool) where {T<:Real}
# skip initial whitespace
while i ≤ e && isspace(s[i])
i = nextind(s, i)
end
if i > e
raise && throw(ArgumentError("input string is empty or only contains whitespace"))
return nothing
end
# find index of ± separating real/imaginary parts (if any)
i₊ = something(findnext(in(('+','-')), s, i), 0)
if i₊ == i # leading ± sign
i₊ = something(findnext(in(('+','-')), s, i₊+1), 0)
end
if i₊ != 0 && s[i₊-1] in ('e','E') # exponent sign
i₊ = something(findnext(in(('+','-')), s, i₊+1), 0)
end
# find trailing im/i/j
iᵢ = something(findprev(in(('m','i','j')), s, e), 0)
if iᵢ > 0 && s[iᵢ] == 'm' # im
iᵢ -= 1
if s[iᵢ] != 'i'
raise && throw(ArgumentError("expected trailing \"im\", found only \"m\""))
return nothing
end
end
if i₊ == 0 # purely real or imaginary value
if iᵢ > i && !(iᵢ == i+1 && s[i] in ('+','-')) # purely imaginary (not "±inf")
x = tryparse_internal(T, s, i, iᵢ-1, raise)
x === nothing && return nothing
return Complex{T}(zero(x),x)
else # purely real
x = tryparse_internal(T, s, i, e, raise)
x === nothing && return nothing
return Complex{T}(x)
end
end
if iᵢ < i₊
raise && throw(ArgumentError("missing imaginary unit"))
return nothing # no imaginary part
end
# parse real part
re = tryparse_internal(T, s, i, i₊-1, raise)
re === nothing && return nothing
# parse imaginary part
im = tryparse_internal(T, s, i₊+1, iᵢ-1, raise)
im === nothing && return nothing
return Complex{T}(re, s[i₊]=='-' ? -im : im)
end
# the ±1 indexing above for ascii chars is specific to String, so convert:
tryparse_internal(T::Type{Complex{S}}, s::AbstractString, i::Int, e::Int, raise::Bool) where S<:Real =
tryparse_internal(T, String(s), i, e, raise)
# fallback methods for tryparse_internal
tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int) where T<:Real =
startpos == firstindex(s) && endpos == lastindex(s) ? tryparse(T, s) : tryparse(T, SubString(s, startpos, endpos))
function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int, raise::Bool) where T<:Real
result = tryparse_internal(T, s, startpos, endpos)
if raise && result === nothing
_parse_failure(T, s, startpos, endpos)
end
return result
end
function tryparse_internal(::Type{T}, s::AbstractString, raise::Bool; kwargs...) where T<:Real
result = tryparse(T, s; kwargs...)
if raise && result === nothing
_parse_failure(T, s)
end
return result
end
@noinline _parse_failure(T, s::AbstractString, startpos = firstindex(s), endpos = lastindex(s)) =
throw(ArgumentError("cannot parse $(repr(s[startpos:endpos])) as $T"))
tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int, raise::Bool) where T<:Integer =
tryparse_internal(T, s, startpos, endpos, 10, raise)
parse(::Type{T}, s::AbstractString; kwargs...) where T<:Real =
convert(T, tryparse_internal(T, s, true; kwargs...))
parse(::Type{T}, s::AbstractString) where T<:Complex =
convert(T, tryparse_internal(T, s, firstindex(s), lastindex(s), true))
tryparse(T::Type{Complex{S}}, s::AbstractString) where S<:Real =
tryparse_internal(T, s, firstindex(s), lastindex(s), false)
Computing file changes ...