diff --git a/src/core/parse_stream.jl b/src/core/parse_stream.jl index 393e23c8..7bcd5745 100644 --- a/src/core/parse_stream.jl +++ b/src/core/parse_stream.jl @@ -635,8 +635,14 @@ function peek_behind_pos(stream::ParseStream; skip_trivia::Bool=true, while node_idx > 0 node = stream.output[node_idx] if kind(node) == K"TOMBSTONE" || (skip_trivia && is_trivia(node)) - node_idx -= 1 byte_idx -= node.byte_span + # If this is a non-terminal node, skip its children without + # subtracting their byte_spans, as they're already included in the parent + if is_non_terminal(node) + node_idx -= (1 + node.node_span) + else + node_idx -= 1 + end else break end diff --git a/test/parse_stream.jl b/test/parse_stream.jl index 0eca59b7..43b16ab5 100644 --- a/test/parse_stream.jl +++ b/test/parse_stream.jl @@ -156,3 +156,13 @@ end @test ParseStream(y) isa ParseStream @test parsestmt(Expr, y) == parsestmt(Expr, "1") end + +@testset "peek_behind_pos with negative byte index" begin + # Test that peek_behind_pos doesn't cause InexactError when byte_idx goes negative + # This can happen when parsing certain incomplete keywords like "do" + # where trivia skipping walks back past the beginning of the stream + @test_throws JuliaSyntax.ParseError parseall(GreenNode, "do") + @test_throws JuliaSyntax.ParseError parseall(GreenNode, "do ") + @test_throws JuliaSyntax.ParseError parseall(GreenNode, " do") + @test_throws JuliaSyntax.ParseError parseall(GreenNode, "do\n") +end