Skip to content

Commit

Permalink
Keep track of previous/next sibling
Browse files Browse the repository at this point in the history
Sometimes there is a need to check the kind (possibly more) of previous
and next siblings in order to decide on formatting. This patch keeps
track of this in the Context.

This is used for replacing trailing whitespace from consecutive
NewlineWs nodes since sometimes the whitespace is after the LF.
  • Loading branch information
fredrikekre committed May 25, 2024
1 parent 05b9d5b commit 2b1dfc3
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 8 deletions.
45 changes: 38 additions & 7 deletions src/Runic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,27 @@ mutable struct Context
# User settings
verbose::Bool
debug::Bool
# Current state
# node::Union{JuliaSyntax.GreenNode, Nothing}
prev_sibling::Union{JuliaSyntax.GreenNode, Nothing}
next_sibling::Union{JuliaSyntax.GreenNode, Nothing}
# parent::Union{JuliaSyntax.GreenNode, Nothing}
end

function Context(src_str; debug::Bool = false, verbose::Bool = debug)
src_io = IOBuffer(src_str)
src_tree = JuliaSyntax.parseall(JuliaSyntax.GreenNode, src_str; ignore_warnings=true)
fmt_io = IOBuffer()
fmt_tree = nothing
return Context(src_str, src_tree, src_io, fmt_io, fmt_tree, verbose, debug)
return Context(
src_str, src_tree, src_io, fmt_io, fmt_tree, verbose, debug,
nothing, nothing,
)
end

function next_sibling_kind(ctx::Context)::Union{JuliaSyntax.Kind, Nothing}
next = ctx.next_sibling
return next === nothing ? nothing : JuliaSyntax.kind(next)
end

# Read the bytes of the current node from the output io
Expand Down Expand Up @@ -66,16 +79,24 @@ function format_node_with_children!(ctx::Context, node::JuliaSyntax.GreenNode)
if !JuliaSyntax.haschildren(node)
return node
end
# Keep track of the siblings on this stack
prev_sibling = ctx.prev_sibling
next_sibling = ctx.next_sibling
ctx.prev_sibling = nothing
ctx.next_sibling = nothing
# @assert JuliaSyntax.haschildren(node)
span_sum = 0
original_bytes = node_bytes(ctx, node) # TODO: Read into reusable buffer
children = JuliaSyntax.children(node)
# The new node parts
head′ = JuliaSyntax.head(node)
children′ = ()
children′ = children # This aliases until the need to copy below
# Keep track of changes; if no child changes the original node can be returned
any_child_changed = false
for (i, child) in pairs(children)
# Set the siblings: previous from children′, next from children
ctx.prev_sibling = get(children′, i - 1, nothing)
ctx.next_sibling = get(children, i + 1, nothing)
child′ = child
span_sum += JuliaSyntax.span(child)
this_child_changed = false
Expand All @@ -86,6 +107,8 @@ function format_node_with_children!(ctx::Context, node::JuliaSyntax.GreenNode)
child′′ = format_node!(ctx, child′)
if child′′ === nullnode
this_child_changed = true
# TODO: When this is fixed the sibling setting above needs to handle this
# too
error("TODO: handle removed children")
elseif child′′ === child′
child′ = child′′
Expand Down Expand Up @@ -113,13 +136,17 @@ function format_node_with_children!(ctx::Context, node::JuliaSyntax.GreenNode)
end
any_child_changed |= this_child_changed
if any_child_changed
# Promote children from tuple to array and copy older siblings into it
if children′ === ()
# De-alias the children if needed
if children′ === children
children′ = eltype(children)[children[j] for j in 1:(i-1)]
end
push!(children′, child′)
end
end
# Reset the siblings
ctx.prev_sibling = prev_sibling
ctx.next_sibling = next_sibling
# Return a new node if any of the children changed
if any_child_changed
span′ = mapreduce(JuliaSyntax.span, +, children′; init=0)
return JuliaSyntax.GreenNode(head′, span′, children′)
Expand All @@ -137,6 +164,12 @@ function format_node!(ctx::Context, node::JuliaSyntax.GreenNode)
@assert !JuliaSyntax.haschildren(node)
str = String(node_bytes(ctx, node))
str′ = replace(str, r"\h*(\r\n|\r|\n)" => '\n')
# If the next sibling is also a NewlineWs we can trim trailing
# whitespace from this node too
next_kind = next_sibling_kind(ctx)
if next_kind === K"NewlineWs"
str′ = replace(str′, r"(\r\n|\r|\n)\h*" => '\n')
end
if str != str′
# Write new bytes and reset the stream
nb = write_and_reset(ctx, str′)
Expand Down Expand Up @@ -393,7 +426,7 @@ function format_tree!(ctx::Context)
# Reset IOs so that the offsets are correct
seek(ctx.src_io, src_pos)
seek(ctx.fmt_io, fmt_pos)
# Keep track of the depth to break out of infinite loops
# Set the root to the current node
root′ = root
itr = 0
while true
Expand All @@ -404,13 +437,11 @@ function format_tree!(ctx::Context)
# the root node so error instead
error("root node deleted")
elseif root′′ === root′
root′ = root′′
@assert position(ctx.fmt_io) == fmt_pos + JuliaSyntax.span(root′)
break
else
# The node was changed, reset the output stream and try again
seek(ctx.fmt_io, fmt_pos)
root′ = root′′
end
# The root node must only change once.
if (itr += 1) == 2
Expand Down
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ using Test:
println(io, " ") # Trailing space on consecutive lines
println(io, " ")
str = String(take!(io))
@test_broken format_string(str) == "a = 1\nb = 2\n\n\n"
@test format_string(str) == "a = 1\nb = 2\n\n\n"
end

@testset "Hex/oct/bin literal integers" begin
Expand Down

0 comments on commit 2b1dfc3

Please sign in to comment.