Skip to content

Commit

Permalink
add *(::Union{Regex, AbstractString, Char}...)
Browse files Browse the repository at this point in the history
  • Loading branch information
rfourquet committed Apr 2, 2019
1 parent 719c062 commit 87e9ca2
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 3 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Standard library changes
`maximum` ([#30323]).
* `hasmethod` can now check for matching keyword argument names ([#30712]).
* `startswith` and `endswith` now accept a `Regex` for the second argument ([#29790]).
* `Regex` can now be multiplied (`*`) and exponentiated (`^`), like strings ([#23422]).
* `retry` supports arbitrary callable objects ([#30382]).
* `filter` now supports `SkipMissing`-wrapped arrays ([#31235]).
* A no-argument construct to `Ptr{T}` has been added which constructs a null pointer ([#30919])
Expand Down
46 changes: 46 additions & 0 deletions base/regex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,49 @@ function hash(r::Regex, h::UInt)
h = hash(r.compile_options, h)
h = hash(r.match_options, h)
end

## String operations ##

unwrap_string(r::Regex) = r.pattern
unwrap_string(s::Union{AbstractString,Char}) = s

"""
*(s::Regex, t::Union{Regex,AbstractString,AbstractChar}) -> Regex
*(s::Union{Regex,AbstractString,AbstractChar}, t::Regex) -> Regex
Concatenate regexes, strings and/or characters, producing a [`Regex`](@ref).
!!! compat "Julia 1.2"
This method requires at least Julia 1.2.
# Examples
```jldoctest
julia> r"Hello " * "world"
r"Hello world"
julia> 'j' * r"ulia"
r"julia"
```
"""
function *(r1::Union{Regex,AbstractString,AbstractChar}, rs::Union{Regex,AbstractString,AbstractChar}...)
opts = unique((r.compile_options, r.match_options) for r in (r1, rs...) if r isa Regex)
length(opts) == 1 ||
throw(ArgumentError("cannot multiply regexes with incompatible options"))
Regex(string(unwrap_string(r1), unwrap_string.(rs)...), opts[1][1], opts[1][2])
end

"""
^(s::Regex, n::Integer)
Repeat a regex `n` times.
!!! compat "Julia 1.2"
This method requires at least Julia 1.2.
# Examples
```jldoctest
julia> r"Test "^3
r"Test Test Test "
```
"""
^(r::Regex, i::Integer) = Regex(r.pattern^i, r.compile_options, r.match_options)
6 changes: 3 additions & 3 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ function setup_interface(
oldpos = firstindex(input)
firstline = true
isprompt_paste = false
jl_prompt_len = 7 # "julia> "
while oldpos <= lastindex(input) # loop until all lines have been executed
if JL_PROMPT_PASTE[]
# Check if the next statement starts with "julia> ", in that case
Expand All @@ -934,7 +935,6 @@ function setup_interface(
oldpos >= sizeof(input) && return
end
# Check if input line starts with "julia> ", remove it if we are in prompt paste mode
jl_prompt_len = 7
if (firstline || isprompt_paste) && startswith(SubString(input, oldpos), JULIA_PROMPT)
isprompt_paste = true
oldpos += jl_prompt_len
Expand All @@ -959,7 +959,7 @@ function setup_interface(
tail = lstrip(tail)
end
if isprompt_paste # remove indentation spaces corresponding to the prompt
tail = replace(tail, r"^ {7}"m => "") # 7: jl_prompt_len
tail = replace(tail, r"^"m * ' '^jl_prompt_len => "")
end
LineEdit.replace_line(s, tail, true)
LineEdit.refresh_line(s)
Expand All @@ -969,7 +969,7 @@ function setup_interface(
line = strip(input[oldpos:prevind(input, pos)])
if !isempty(line)
if isprompt_paste # remove indentation spaces corresponding to the prompt
line = replace(line, r"^ {7}"m => "") # 7: jl_prompt_len
line = replace(line, r"^"m * ' '^jl_prompt_len => "")
end
# put the line on the screen and history
LineEdit.replace_line(s, line)
Expand Down
23 changes: 23 additions & 0 deletions test/regex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@
@test !endswith("abc", r"C")
@test endswith("abc", r"C"i)

@testset "multiplication & exponentiation" begin
@test r"a" * r"b" == r"ab"
@test r"a" * "b" == r"ab"
@test r"a" * 'b' == r"ab"
@test "a" * r"b" == r"ab"
@test 'a' * r"b" == r"ab"
for a = (r"a", "a", 'a'),
b = (r"b", "b", 'b'),
c = (r"c", "c", 'c')
a isa Regex || b isa Regex || c isa Regex || continue
@test a * b * c == r"abc"
end
@test r"a"i * r"b"i == r"ab"i
@test r"a"i * "b" == r"ab"i
@test r"a"i * 'b' == r"ab"i
@test "a" * r"b"i == r"ab"i
@test 'a' * r"b"i == r"ab"i
@test_throws ArgumentError r"a"i * r"b"
@test_throws ArgumentError r"a" * r"b"i

@test r"abc"^ 2 == r"abcabc"
end

# Test that PCRE throws the correct kind of error
# TODO: Uncomment this once the corresponding change has propagated to CI
#@test_throws ErrorException Base.PCRE.info(C_NULL, Base.PCRE.INFO_NAMECOUNT, UInt32)
Expand Down

0 comments on commit 87e9ca2

Please sign in to comment.