• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / 1404

11 Jan 2026 12:09AM UTC coverage: 76.724% (+0.04%) from 76.683%
1404

push

buildkite

web-flow
Add `@stm` "SyntaxTree match" macro (#60475)

62704 of 81727 relevant lines covered (76.72%)

23123244.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.58
/stdlib/Markdown/src/parse/util.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
const whitespace = " \t\r"
4

5
"""
6
Skip any leading whitespace. Returns io.
7
"""
8
function skipwhitespace(io::IO; newlines = true)
939,674✔
9
    while !eof(io) && (peek(io, Char) in whitespace || (newlines && peek(io) == UInt8('\n')))
939,536✔
10
        read(io, Char)
207,490✔
11
    end
207,490✔
12
    return io
469,837✔
13
end
14

15
"""
16
Skip any leading blank lines. Returns the number skipped.
17
"""
18
function skipblank(io::IO)
328,138✔
19
    start = position(io)
328,138✔
20
    i = 0
328,138✔
21
    for c in readeach(io, Char)
604,241✔
22
        c == '\n' && (start = position(io); i+=1; continue)
389,966✔
23
        c == '\r' && (start = position(io); i+=1; continue)
372,788✔
24
        c in whitespace || break
372,785✔
25
    end
236,171✔
26
    seek(io, start)
656,276✔
27
    return i
328,138✔
28
end
29

30
"""
31
Return true if the line contains only (and, unless allowempty,
32
at least one of) the characters given.
33
"""
34
function linecontains(io::IO, chars; allow_whitespace = true,
519,204✔
35
                                     eat = true,
36
                                     allowempty = false)
37
    start = position(io)
259,602✔
38
    l = readline(io)
259,602✔
39
    length(l) == 0 && return allowempty
259,602✔
40

41
    result = allowempty
165,638✔
42
    for c in l
331,198✔
43
        c in whitespace && (allow_whitespace ? continue : (result = false; break))
175,794✔
44
        c in chars && (result = true; continue)
165,542✔
45
        result = false; break
165,542✔
46
    end
20,384✔
47
    !(result && eat) && seek(io, start)
331,180✔
48
    return result
165,638✔
49
end
50

51
blankline(io::IO; eat = true) =
519,204✔
52
    linecontains(io, "",
53
                 allow_whitespace = true,
54
                 allowempty = true,
55
                 eat = eat)
56

57
"""
58
Test if the stream starts with the given string.
59
`eat` specifies whether to advance on success (true by default).
60
`padding` specifies whether leading whitespace should be ignored.
61
"""
62
function startswith(stream::IO, s::AbstractString; eat = true, padding = false, newlines = true)
5,199,688✔
63
    start = position(stream)
2,599,844✔
64
    padding && skipwhitespace(stream, newlines = newlines)
2,599,844✔
65
    result = true
2,599,844✔
66
    for char in s
5,199,688✔
67
        !eof(stream) && read(stream, Char) == char ||
2,862,848✔
68
            (result = false; break)
2,356,879✔
69
    end
768,973✔
70
    !(result && eat) && seek(stream, start)
4,959,498✔
71
    return result
2,599,844✔
72
end
73

74
function startswith(stream::IO, c::AbstractChar; eat = true)
2,398,910✔
75
    if !eof(stream) && peek(stream) == UInt8(c)
2,386,466✔
76
        eat && read(stream, Char)
542,318✔
77
        return true
542,318✔
78
    else
79
        return false
1,844,148✔
80
    end
81
end
82

83
function startswith(stream::IO, ss::Vector{<:AbstractString}; kws...)
×
84
    any(s->startswith(stream, s; kws...), ss)
×
85
end
86

87
function matchstart(stream::IO, r::Regex; eat = true, padding = false)
1,052,916✔
88
    @assert Base.startswith(r.pattern, "^")
526,458✔
89
    start = position(stream)
526,458✔
90
    padding && skipwhitespace(stream)
526,458✔
91
    line = readline(stream)
526,458✔
92
    seek(stream, start)
1,052,916✔
93
    m = match(r, line)
526,458✔
94
    if eat && m !== nothing
526,458✔
95
        for i in 1:length(m.match)
147,334✔
96
            read(stream, Char)
181,376✔
97
        end
215,418✔
98
    end
99
    return m
526,458✔
100
end
101

102
function startswith(stream::IO, r::Regex; kws...)
84,451✔
103
    return matchstart(stream, r; kws...) !== nothing
84,451✔
104
end
105

106
"""
107
Executes the block of code, and if the return value is `nothing` or `false`,
108
returns the stream to its initial position.
109
"""
110
function withstream(f, stream)
208,354✔
111
    pos = position(stream)
3,336,516✔
112
    result = f()
3,336,795✔
113
    (result ≡ nothing || result ≡ false) && seek(stream, pos)
5,430,844✔
114
    return result
3,336,516✔
115
end
116

117
"""
118
Consume the standard allowed markdown indent of
119
three spaces. Returns false if there are more than
120
three present.
121
"""
122
function eatindent(io::IO, n = 3)
123
    withstream(io) do
2,025,884✔
124
        m = 0
921,016✔
125
        while startswith(io, ' ') m += 1 end
1,328,925✔
126
        return m <= n
921,016✔
127
    end
128
end
129

130
"""
131
Read the stream until startswith(stream, delim)
132
The delimiter is consumed but not included.
133
Returns nothing and resets the stream if delim is
134
not found.
135
"""
136
function readuntil(stream::IO, delimiter; newlines = false, match = nothing)
196,101✔
137
    withstream(stream) do
196,101✔
138
        buffer = IOBuffer()
192,896✔
139
        count = 0
192,896✔
140
        while !eof(stream)
1,775,126✔
141
            if startswith(stream, delimiter)
1,772,423✔
142
                if count == 0
191,777✔
143
                    return takestring!(buffer)
189,691✔
144
                else
145
                    count -= 1
2,086✔
146
                    write(buffer, delimiter)
2,086✔
147
                    continue
2,086✔
148
                end
149
            end
150
            char = read(stream, Char)
1,580,646✔
151
            char == match && (count += 1)
1,580,646✔
152
            !newlines && char == '\n' && break
1,580,646✔
153
            write(buffer, char)
1,580,144✔
154
        end
1,582,230✔
155
    end
156
end
157

158
# TODO: refactor this. If we're going to assume
159
# the delimiter is a single character + a minimum
160
# repeat we may as well just pass that into the
161
# function.
162

163
"""
164
Parse a symmetrical delimiter which wraps words.
165
i.e. `*word word*` but not `*word * word`.
166
`repeat` specifies whether the delimiter can be repeated.
167
Escaped delimiters are not yet supported.
168
"""
169
function parse_inline_wrapper(stream::IO, delimiter::AbstractString; rep = false)
231,904✔
170
    delimiter, nmin = string(delimiter[1]), length(delimiter)
231,904✔
171
    withstream(stream) do
115,952✔
172
        if position(stream) >= 1
115,952✔
173
            # check the previous byte isn't a delimiter
174
            skip(stream, -1)
128,232✔
175
            (read(stream, Char) in delimiter) && return nothing
64,116✔
176
        end
177
        n = nmin
106,439✔
178
        startswith(stream, delimiter^n) || return nothing
200,389✔
179
        while startswith(stream, delimiter); n += 1; end
19,440✔
180
        !rep && n > nmin && return nothing
12,489✔
181
        !eof(stream) && peek(stream, Char) in whitespace && return nothing
8,985✔
182

183
        buffer = IOBuffer()
8,454✔
184
        for char in readeach(stream, Char)
15,933✔
185
            write(buffer, char)
71,443✔
186
            if !(char in whitespace || char == '\n' || char in delimiter) && startswith(stream, delimiter^n)
137,317✔
187
                trailing = 0
6,425✔
188
                while startswith(stream, delimiter); trailing += 1; end
7,517✔
189
                trailing == 0 && return takestring!(buffer)
6,425✔
190
                write(buffer, delimiter ^ (n + trailing))
849✔
191
            end
192
        end
65,867✔
193
    end
194
end
195

196
function showrest(io::IO)
×
197
    start = position(io)
×
198
    show(read(io, String))
×
199
    println()
×
200
    seek(io, start)
×
201
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc