Skip to content

Commit

Permalink
Improve inference with a function over a closure
Browse files Browse the repository at this point in the history
The previous code pattern using `takewhile` relied on Julia's type
inference to produce correct results. In particular, it relied on
`takewhile(f, x) |> collect` to correctly infer to `Vector{Pair{<:Any,
Char}}`. However, type inference in Julia is an optimization, and should
not be relied on to produce the correct answer. Instead, use a normal
function to replace the functional style of programming.
  • Loading branch information
jakobnissen authored and tecosaur committed May 26, 2024
1 parent 6901610 commit d3aa7e1
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/src/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ StyledStrings.StyledMarkup.escaped!
StyledStrings.StyledMarkup.interpolated!
StyledStrings.StyledMarkup.readexpr!
StyledStrings.StyledMarkup.skipwhitespace!
StyledStrings.StyledMarkup.read_while!
StyledStrings.StyledMarkup.begin_style!
StyledStrings.StyledMarkup.end_style!
StyledStrings.StyledMarkup.read_annotation!
Expand Down
69 changes: 50 additions & 19 deletions src/styledmarkup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,50 @@ function skipwhitespace!(state::State)
end
end

"""
read_while!(f::Function, state::Base.Stateful, lastchar::Char)
Read `state` until `f(::Char)` is `false`.
Given a `Stateful` that iterates `(_, char::Char)` pairs, and a predicate
`f(::Char)::Bool`, return `(str, lastchar)`, where `str::String` contains all the
`char` for which `f(char) == true`, and `lastchar` the last `char` element seen,
or the input `lastchar` there are no elements of `state`.
# Examples
```julia-repl
julia> s = Base.Stateful(pairs("abc"));
julia> read_while!(isnumeric, s, 'w')
("", 'a')
julia> first(s) # s is mutated
2 => 'b'
julia> read_while!(isascii, Base.Stateful(pairs("123Σω")), 'k')
("123", 'Σ')
julia> read_while!(isascii, Base.Stateful(pairs("abcde")), 'α')
("abcde", 'e')
julia> read_while!(isascii , Base.Stateful(pairs("")), 'k')
("", 'k')
```
"""
function read_while!(f::Function, state::Base.Stateful, lastchar::Char)
v = Char[]
for (_, char::Char) in state
lastchar = char
if f(char)
push!(v, char)
else
break
end
end
if isempty(v) "" else String(v) end, lastchar
end

"""
begin_style!(state::State, i::Int, char::Char)
Expand Down Expand Up @@ -438,16 +482,8 @@ it to `newstyles`.
"""
function read_inlineface!(state::State, i::Int, char::Char, newstyles)
# Substructure parsing helper functions
function readalph!(state, lastchar)
Iterators.takewhile(
c -> 'a' <= (lastchar = last(c)) <= 'z', state.s) |>
collect .|> last |> String, lastchar
end
function readsymbol!(state, lastchar)
Iterators.takewhile(
c -> (lastchar = last(c)) (' ', '\t', '\n', '\r', ',', ')'), state.s) |>
collect .|> last |> String, lastchar
end
readalph!(state, lastchar) = read_while!(c -> 'a' <= c <= 'z', state.s, lastchar)
readsymbol!(state, lastchar) = read_while!(((' ', '\t', '\n', '\r', ',', ')')), state.s, lastchar)
function parsecolor(color::String)
if color == "nothing"
elseif startswith(color, '#') && length(color) == 7
Expand Down Expand Up @@ -554,18 +590,14 @@ function read_inlineface!(state::State, i::Int, char::Char, newstyles)
lastchar = last(popfirst!(state.s))
break
end
facename = Iterators.takewhile(
c -> (lastchar = last(c)) (',', ']', ')'), state.s) |>
collect .|> last |> String
facename, lastchar = read_while!(((',', ']', ')')), state.s, lastchar)
push!(inherit, Symbol(rstrip(facename)))
if lastchar != ','
break
end
end
else
facename = Iterators.takewhile(
c -> (lastchar = last(c)) (',', ']', ')'), state.s) |>
collect .|> last |> String
facename, lastchar = read_while!(((',', ']', ')')), state.s, lastchar)
push!(inherit, Symbol(rstrip(facename)))
end
inherit, lastchar
Expand Down Expand Up @@ -614,9 +646,8 @@ function read_inlineface!(state::State, i::Int, char::Char, newstyles)
if isnextchar(state, '"')
readexpr!(state, first(peek(state.s))) |> first
else
Iterators.takewhile(
c -> (lastchar = last(c)) (',', ')'), state.s) |>
collect .|> last |> String
str, lastchar = read_while!(((',', ')')), state.s, lastchar)
str
end
elseif key == :height
if isnextchar(state, ('.', '0':'9'...))
Expand Down

0 comments on commit d3aa7e1

Please sign in to comment.