Skip to content

Instantly share code, notes, and snippets.

@Gouvernathor
Last active August 31, 2022 23:39
Show Gist options
  • Save Gouvernathor/74a2dfa486d18bb67d6d4421ecde87b0 to your computer and use it in GitHub Desktop.
Save Gouvernathor/74a2dfa486d18bb67d6d4421ecde87b0 to your computer and use it in GitHub Desktop.
This is outdated. A lot of edge cases aren't covered.
$ UNREACHABLE
"mmh... yep"
init python in reach:
start_points = (renpy.ast.EarlyPython,
renpy.ast.Init,
renpy.ast.Label,
renpy.ast.TranslateBlock,
)
def check(return_nodes=False, check_common=False):
"""
Checks every node in the game and returns the locations of the unreachable ones.
If return_nodes, returns the nodes themselves.
If check_commmon, checks the renpy/common directory as well.
Return nodes are skipped, because a label followed by a return,
not followed by an init block, makes the implicit return at the
end of the file unreachable, even though it's perfectly valid code.
Test this by removing the Return filter, and the "continue" for Init nodes.
reach.check() should only return lines 1 and 3 of the current file.
"""
all_stmts = renpy.game.script.all_stmts
if not check_common:
all_stmts = [node for node in all_stmts if "common" not in node.filename]
# a better filter is included in lint, this is just a quick one
starters = {node for node in all_stmts if isinstance(node, start_points)}
# all reachable nodes whose children haven't yet been checked
starters |= {node for node in all_stmts if isinstance(node, renpy.ast.Translate) and node.language is not None}
unreachable = set(all_stmts)
# the auto-generated Return at the end of every file is hard to segregate from the other Return nodes
# so, we don't check Return nodes
# EndTranslate nodes are naturally unreachable
def add_block(block):
next = block[0]
if next in unreachable:
starters.add(next)
while starters:
node = starters.pop()
unreachable.discard(node)
if isinstance(node, (renpy.ast.Init, renpy.ast.While, renpy.ast.TranslateBlock)):
add_block(node.block)
if isinstance(node, (renpy.ast.Init, renpy.ast.TranslateBlock)):
# the .next of init and translateblock nodes are weird
# they can lead to nodes which are yet unreachable
continue
elif isinstance(node, renpy.ast.Menu):
for (_l, _c, block) in node.items:
add_block(block)
# if there's only returns or jumps in the menu choices,
# the next of the menu is unreachable
# if not, the blocks will lead us there eventually
continue
elif isinstance(node, renpy.ast.If):
for (_c, block) in node.entries:
add_block(block)
elif isinstance(node, renpy.ast.UserStatement):
if node.code_block is not None:
add_block(node.code_block)
for i in node.subparses:
add_block(i.block)
# we can't really know whether to continue or not
# in doubt, don't report an error which might not be there
next = node.next
if next in unreachable:
starters.add(next)
unreachable = {node for node in unreachable if not isinstance(node, (renpy.ast.Return, renpy.ast.EndTranslate))}
if return_nodes:
return unreachable
return sorted(set((node.filename, node.linenumber) for node in unreachable))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment