Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Language changes
* The `hash(::AbstractString)` function is now a zero-copy / zero-cost function, based upon providing a correct implementation of the `codeunit` and `iterate` functions. Third-party string packages should migrate to the new algorithm by deleting their existing overrides of the `hash` function. ([#59691])

* Indexless `getindex` and `setindex!` (i.e. `A[]`) on `ReinterpretArray` now correctly throw a `BoundsError` when there is more than one element. ([#58814])
* Characters can be parsed to more number types; `tryparse` now accepts characters as well as strings ([#59603])

Compiler/Runtime improvements
-----------------------------
Expand Down
63 changes: 50 additions & 13 deletions base/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import Base.Checked: add_with_overflow, mul_with_overflow
## string to integer functions ##

"""
parse(type, str; base)
parse(type, str)
parse(<:Integer, str; base=10)

Parse a string as a number. For `Integer` types, a base can be specified
Parse a string (or character) 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
Expand All @@ -17,6 +18,9 @@ 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.

!!! compat "Julia 1.13"
`parse(type, AbstractChar)` requires at least Julia 1.13 for non-integer types.

# Examples
```jldoctest
julia> parse(Int, "1234")
Expand All @@ -38,16 +42,50 @@ julia> parse(Complex{Float64}, "3.2e-1 + 4.5im")
parse(T::Type, str; base = Int)
parse(::Type{Union{}}, slurp...; kwargs...) = error("cannot parse a value as Union{}")

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)
"""
tryparse(type, str)
tryparse(<:Integer, str; base=10)

Like [`parse`](@ref), but returns either a value of the requested type,
or [`nothing`](@ref) if the string does not contain a valid number.

!!! compat "Julia 1.13"
`tryparse(type, AbstractChar)` requires at least Julia 1.13.
"""
tryparse(T::Type, str; base = Int)

@noinline function _invalid_base(base)
throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base"))
end

@noinline _invalid_digit(base, char) = throw(ArgumentError("invalid base $base digit $(repr(char))"))

function parse_char(::Type{T}, c::AbstractChar, base::Integer, throw::Bool) where T
a::UInt8 = (base <= 36 ? 10 : 36)
(2 <= base <= 62) || _invalid_base(base)
base = base % UInt8
u = reinterpret(UInt32, Char(c)::Char)
cp = u > 0x7a000000 ? 0xff : (u >> 24) % UInt8
d = UInt8('0') ≤ cp ≤ UInt8('9') ? cp - UInt8('0') :
UInt8('A') ≤ cp ≤ UInt8('Z') ? cp - UInt8('A') + UInt8(10) :
UInt8('a') ≤ cp ≤ UInt8('z') ? cp - UInt8('a') + a :
0xff
d < base || (throw ? _invalid_digit(base, c) : return nothing)
convert(T, d)::T
end

function parse(::Type{T}, c::AbstractChar; base::Integer=10) where {T <: Integer}
@inline parse_char(T, c, base, true)
end

function tryparse(::Type{T}, c::AbstractChar; base::Integer=10) where {T <: Integer}
@inline parse_char(T, c, base, false)
end

# For consistency with parse(t, AbstractString), support a `base` argument only when T<:Integer
parse(::Type{T}, c::AbstractChar) where T = @inline parse_char(T, c, 10, true)
tryparse(::Type{T}, c::AbstractChar) where T = @inline parse_char(T, c, 10, false)

function parseint_iterate(s::AbstractString, startpos::Int, endpos::Int)
(0 < startpos <= endpos) || (return Char(0), 0, 0)
j = startpos
Expand Down Expand Up @@ -116,8 +154,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::
return nothing
end
if !(2 <= base <= 62)
raise && throw(ArgumentError(LazyString("invalid base: base must be 2 ≤ base ≤ 62, got ", base)))
return nothing
raise ? _invalid_base(base) : return nothing
end
if i == 0
raise && throw(ArgumentError("premature end of integer: $(repr(SubString(s,startpos,endpos)))"))
Expand Down Expand Up @@ -236,7 +273,7 @@ end
if 2 <= base <= 62
return base
end
throw(ArgumentError("invalid base: base must be 2 ≤ base ≤ 62, got $base"))
_invalid_base(base)
end

"""
Expand Down
25 changes: 25 additions & 0 deletions test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
@test parse(Int, 'a', base=16) == 10
@test_throws ArgumentError parse(Int, 'a')
@test_throws ArgumentError parse(Int,typemax(Char))

@test tryparse(Int, '8') === 8
@test tryparse(Int, 'a') === nothing
@test tryparse(Int, 'a'; base=11) === 10
@test tryparse(Int32, 'a'; base=11) === Int32(10)
@test tryparse(UInt8, 'f'; base=16) === 0x0f
@test tryparse(UInt8, 'f'; base=15) === nothing

@test_throws ArgumentError parse(Int, 'a'; base=63)
@test_throws ArgumentError tryparse(Int, 'a'; base=63)
@test_throws ArgumentError parse(Int, "a"; base=63)
@test_throws ArgumentError tryparse(Int, "a"; base=63)
end

# Issue 29451
Expand Down Expand Up @@ -265,6 +277,19 @@ end
@test tryparse(Float32, "1.23") === 1.23f0
@test tryparse(Float16, "1.23") === Float16(1.23)

@testset "parse Chars to non-integer types" begin
for T in (Float64, Float32, Float16, Complex{Int})
@test parse(T, '3') === T(3)
@test_throws ArgumentError parse(T, 'a')
@test tryparse(T, '3') === T(3)
@test tryparse(T, 'a') === nothing

# for consistency with parse(T, str), `base` is not a valid argument
@test_throws MethodError parse(T, '3'; base=11)
@test_throws MethodError tryparse(T, '3'; base=11)
end
end

# parsing complex numbers (#22250)
@testset "complex parsing" begin
for sign in ('-','+'), Im in ("i","j","im"), s1 in (""," "), s2 in (""," "), s3 in (""," "), s4 in (""," ")
Expand Down