Skip to content

Commit b933013

Browse files
authored
Fix line numbers with source fragments (#310)
When parsing source code fragments incrementally with * `Meta.parse(str, index)` or * `parsestmt(str, index)` we must avoid scanning the rest of `str` for line numbers for efficiency. In this mode, the user is expected to provide `first_line` to "manually" specify which line number we're counting from. Admittedly this is a bit clunky and should be integrated better with SourceFile (which should also be renamed - see issue #190) but for now seems to be the most consistent way to approach things here. As part of the refactoring here, switch over to using `Vector{UInt8}` for literal parsing which makes parsing to `ParseStream` and `GreenNode` around 10% faster.
1 parent b0a2837 commit b933013

18 files changed

+230
-192
lines changed

docs/src/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ JuliaSyntax.highlight
3838
JuliaSyntax.sourcetext
3939
JuliaSyntax.source_line
4040
JuliaSyntax.source_location
41+
JuliaSyntax.source_line_range
4142
```
4243

4344
## Expression heads/kinds

prototypes/simple_parser.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ end
140140
function parse_and_show(production::Function, code)
141141
st = ParseStream(code)
142142
production(st)
143-
t = JuliaSyntax.build_tree(GreenNode, st, wrap_toplevel_as_kind=K"error")
143+
t = JuliaSyntax.build_tree(GreenNode, st)
144144
show(stdout, MIME"text/plain"(), t, code, show_trivia=true)
145145
if !isempty(st.diagnostics)
146146
println()

src/expr.jl

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function _strip_parens(ex)
6868
end
6969
end
7070

71-
function _leaf_to_Expr(source, head, srcrange, node)
71+
function _leaf_to_Expr(source, txtbuf, head, srcrange, node)
7272
k = kind(head)
7373
if k == K"core_@cmd"
7474
return GlobalRef(Core, Symbol("@cmd"))
@@ -79,7 +79,7 @@ function _leaf_to_Expr(source, head, srcrange, node)
7979
Expr(:error) :
8080
Expr(:error, "$(_token_error_descriptions[k]): `$(source[srcrange])`")
8181
else
82-
val = isnothing(node) ? parse_julia_literal(source, head, srcrange) : node.val
82+
val = isnothing(node) ? parse_julia_literal(txtbuf, head, srcrange) : node.val
8383
if val isa Union{Int128,UInt128,BigInt}
8484
# Ignore the values of large integers and convert them back to
8585
# symbolic/textural form for compatibility with the Expr
@@ -457,7 +457,9 @@ end
457457

458458
function build_tree(::Type{Expr}, stream::ParseStream;
459459
filename=nothing, first_line=1, kws...)
460-
source = SourceFile(sourcetext(stream), filename=filename, first_line=first_line)
460+
source = SourceFile(sourcetext(stream), first_index=first_byte(stream),
461+
filename=filename, first_line=first_line)
462+
txtbuf = textbuf(stream)
461463
args = Any[]
462464
childranges = UnitRange{Int}[]
463465
childheads = SyntaxHead[]
@@ -467,7 +469,7 @@ function build_tree(::Type{Expr}, stream::ParseStream;
467469
end
468470
k = kind(head)
469471
if isnothing(nodechildren)
470-
ex = _leaf_to_Expr(source, head, srcrange, nothing)
472+
ex = _leaf_to_Expr(source, txtbuf, head, srcrange, nothing)
471473
else
472474
resize!(childranges, length(nodechildren))
473475
resize!(childheads, length(nodechildren))
@@ -487,7 +489,8 @@ end
487489

488490
function _to_expr(node::SyntaxNode)
489491
if !haschildren(node)
490-
return _leaf_to_Expr(node.source, head(node), range(node), node)
492+
offset, txtbuf = _unsafe_wrap_substring(sourcetext(node.source))
493+
return _leaf_to_Expr(node.source, txtbuf, head(node), range(node) .+ offset, node)
491494
end
492495
cs = children(node)
493496
args = Any[_to_expr(c) for c in cs]

src/hooks.jl

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,7 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
157157
write(_debug_log[], code)
158158
end
159159

160-
io = IOBuffer(code)
161-
seek(io, offset)
162-
163-
stream = ParseStream(io)
160+
stream = ParseStream(code, offset+1)
164161
if options === :statement || options === :atom
165162
# To copy the flisp parser driver:
166163
# * Parsing atoms consumes leading trivia
@@ -179,9 +176,7 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
179176
end
180177

181178
if any_error(stream)
182-
tree = build_tree(SyntaxNode, stream,
183-
wrap_toplevel_as_kind=K"None", first_line=lineno,
184-
filename=filename)
179+
tree = build_tree(SyntaxNode, stream, first_line=lineno, filename=filename)
185180
tag = _incomplete_tag(tree, lastindex(code))
186181
if _has_v1_10_hooks
187182
exc = ParseError(stream, filename=filename, first_line=lineno,
@@ -233,13 +228,7 @@ function core_parser_hook(code, filename::String, lineno::Int, offset::Int, opti
233228
#
234229
# show_diagnostics(stdout, stream.diagnostics, code)
235230
#
236-
ex = build_tree(Expr, stream; filename=filename,
237-
wrap_toplevel_as_kind=K"None", first_line=lineno)
238-
if @isexpr(ex, :None)
239-
# The None wrapping is only to give somewhere for trivia to be
240-
# attached; unwrap!
241-
ex = only(ex.args)
242-
end
231+
ex = build_tree(Expr, stream; filename=filename, first_line=lineno)
243232
end
244233

245234
# Note the next byte in 1-based indexing is `last_byte(stream) + 1` but
@@ -291,15 +280,13 @@ else
291280
Base.Meta.ParseError(e::JuliaSyntax.ParseError) = e
292281
end
293282

294-
const _default_parser = _has_v1_6_hooks ? Core._parse : nothing
295-
296283
"""
297284
enable_in_core!([enable=true; freeze_world_age=true, debug_filename=nothing])
298285
299286
Connect the JuliaSyntax parser to the Julia runtime so that it replaces the
300287
flisp parser for all parsing work. That is, JuliaSyntax will be used for
301-
`include()` `Meta.parse()`, the REPL, etc. To disable, set use
302-
`enable_in_core!(false)`.
288+
`include()` `Meta.parse()`, the REPL, etc. To reset to the reference parser,
289+
use `enable_in_core!(false)`.
303290
304291
Keyword arguments:
305292
* `freeze_world_age` - Use a fixed world age for the parser to prevent
@@ -322,7 +309,7 @@ function enable_in_core!(enable=true; freeze_world_age = true,
322309
world_age = freeze_world_age ? Base.get_world_counter() : typemax(UInt)
323310
_set_core_parse_hook(fix_world_age(core_parser_hook, world_age))
324311
else
325-
_set_core_parse_hook(_default_parser)
312+
_set_core_parse_hook(Core.Compiler.fl_parse)
326313
end
327314
nothing
328315
end

src/kinds.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,8 @@ const _kind_names =
914914
"cartesian_iterator"
915915
"comprehension"
916916
"typed_comprehension"
917+
# Container for a single statement/atom plus any trivia and errors
918+
"wrapper"
917919
"END_SYNTAX_KINDS"
918920
]
919921

0 commit comments

Comments
 (0)