Last active
August 31, 2022 23:39
-
-
Save Gouvernathor/74a2dfa486d18bb67d6d4421ecde87b0 to your computer and use it in GitHub Desktop.
This is outdated. A lot of edge cases aren't covered.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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