Skip to content

Commit

Permalink
also rename labels during minify (+ lint unused/dup labels)
Browse files Browse the repository at this point in the history
  • Loading branch information
thisismypassport committed Jun 26, 2023
1 parent e90ddd1 commit 2c61f8b
Show file tree
Hide file tree
Showing 16 changed files with 239 additions and 143 deletions.
19 changes: 19 additions & 0 deletions pico_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def add_error(msg, node):
# find global assignment, and check for uses

used_locals = set()
used_labels = set()
assigned_locals = set()

def is_assign_target(node):
Expand Down Expand Up @@ -55,6 +56,9 @@ def preprocess_vars(node):
assigned_locals.add(node.var)
else:
used_locals.add(node.var)

if node.kind == VarKind.label and not node.new:
used_labels.add(node.var)

elif node.type == NodeType.sublang:
for glob in node.lang.get_defined_globals():
Expand Down Expand Up @@ -90,6 +94,21 @@ def lint_pre(node):
(node != node.parent.params[-1] or node not in node.parent.children)): # don't warn for non-last or implicit params
add_error("Local '%s' isn't used" % node.name, node)

elif node.kind == VarKind.label and node.new:
if lint_duplicate and node.name != '_':
prev_var = node.scope.parent.find(node.name, crossfunc=True)
if prev_var != None:
if prev_var.scope.funcdepth < node.var.scope.funcdepth:
if prev_var.scope.funcdepth == 0:
add_error("Label '%s' has the same name as a label declared at the top level" % node.name, node)
else:
add_error("Label '%s' has the same name as a label declared in a parent function" % node.name, node)
else:
add_error("Label '%s' has the same name as a label declared in a parent scope" % node.name, node)

if lint_unused and node.var not in used_labels and not node.name.startswith("_"):
add_error("Label '%s' isn't used" % node.name, node)

elif node.kind == VarKind.global_:
if lint_undefined and node.name not in custom_globals:
if node.name in builtin_globals:
Expand Down
119 changes: 88 additions & 31 deletions pico_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ def __init__(m, name, scope, implicit=False):
class Global(VarBase):
pass

class Label(VarBase):
def __init__(m, name, scope):
super().__init__(name)
m.scope = scope

class Scope:
def __init__(m, parent=None, depth=0, funcdepth=0):
m.parent = parent
Expand Down Expand Up @@ -60,36 +65,45 @@ def used_members(m): # note - only for members used as if they were globals,
def has_used_members(m):
return lazy_property.is_set(m, "used_members")

"""class LabelScope:
class LabelScope:
def __init__(m, parent=None, funcdepth=0):
m.parent = parent
m.funcdepth = funcdepth
m.labels = None
m.gotos = None

def add(m, var):
if m.labels is None:
m.labels = {}
m.labels[var.name] = var

def find(m, name):
def find(m, name, crossfunc=False):
var = m.labels.get(name) if m.labels else None
if var:
return var
elif m.parent and m.parent.funcdepth == m.funcdepth:
elif m.parent and (crossfunc or m.parent.funcdepth == m.funcdepth):
return m.parent.find(name)

def add_goto(m, node):
if m.gotos is None:
m.gotos = []
m.gotos.append(node)
def chain(m, crossfunc=False):
curr = m
while curr:
yield curr
curr = curr.parent if (crossfunc or curr.parent.funcdepth == curr.funcdepth) else None

# for implementation reuse with locals, we stored used labels in used_locals

@lazy_property
def used_labels(m):
def used_locals(m):
return set()
@property
def has_used_labels(m):
return lazy_property.is_set(m, "used_labels")"""
def has_used_locals(m):
return lazy_property.is_set(m, "used_locals")

@property
def has_used_globals(m):
return False
@property
def has_used_members(m):
return False

class NodeType(Enum):
values = ("var", "index", "member", "const", "group", "unary_op", "binary_op", "call",
Expand Down Expand Up @@ -154,6 +168,8 @@ def parse(source, tokens):
depth = -1
funcdepth = 0
scope = Scope(None, depth, funcdepth)
labelscope = LabelScope(None, funcdepth)
unresolved_labels = None
errors = []
globals = LazyDict(lambda key: Global(key))

Expand All @@ -178,12 +194,15 @@ def accept(value, tokens=None):
return True
return False

def add_error(msg, off=0, fail=False):
err = Error(msg, peek(off))
def add_error_at(msg, token, fail=False):
err = Error(msg, token)
errors.append(err)
if fail:
raise ParseError()

def add_error(msg, off=0, fail=False):
add_error_at(msg, peek(off), fail)

def require(value, tokens=None):
if not accept(value, tokens):
add_error("expected '%s'" % value, fail=True)
Expand All @@ -198,18 +217,32 @@ def require_ident(tokens=None):
return peek(-1)
add_error("identifier expected", fail=True)

def parse_var(token=None, newscope=None, member=False, implicit=False):
def parse_var(token=None, new=None, member=False, label=False, implicit=False):
token = token or require_ident()
name = token.value

var = None
kind = VarKind.local
if newscope:
var = Local(name, newscope)
elif member:
search_scope = None

if member:
kind = VarKind.member

elif label:
kind = VarKind.label
search_scope = labelscope

if new:
var = Label(name, labelscope)
# else, can't resolve until func ends

else:
if e(scope):
kind = VarKind.local
search_scope = new or scope

if new:
assert isinstance(new, Scope)
var = Local(name, new)
else:
var = scope.find(name)

if not var:
Expand All @@ -223,10 +256,18 @@ def parse_var(token=None, newscope=None, member=False, implicit=False):
if var and hasattr(token, "keys_kind"):
var.keys_kind = token.keys_kind

return Node(NodeType.var, [token], name=name, kind=kind, var_kind=var_kind, var=var, new=bool(newscope), scope=newscope or scope)
node = Node(NodeType.var, [token], name=name, kind=kind, var_kind=var_kind, var=var, new=bool(new), scope=search_scope)

if label and not new:
nonlocal unresolved_labels
if unresolved_labels is None:
unresolved_labels = []
unresolved_labels.append(node)

return node

def parse_function(stmt=False, local=False):
nonlocal scope, funcdepth
nonlocal scope, funcdepth, unresolved_labels
tokens = [peek(-1)]
self_param = None
func_kind = getattr(tokens[0], "func_kind", None)
Expand All @@ -237,7 +278,7 @@ def parse_function(stmt=False, local=False):
params = []
if stmt:
if local:
target = parse_var(newscope=scope)
target = parse_var(new=scope)
scope.add(target.var)
name = target.name
else:
Expand All @@ -257,7 +298,7 @@ def parse_function(stmt=False, local=False):
name += ":" + key.name

self_token = Token.synthetic(TokenType.ident, "self", target, append=True)
self_param = parse_var(token=self_token, newscope=funcscope, implicit=True)
self_param = parse_var(token=self_token, new=funcscope, implicit=True)
params.append(self_param)

tokens.append(target)
Expand All @@ -268,7 +309,7 @@ def parse_function(stmt=False, local=False):
if accept("..."):
params.append(Node(NodeType.varargs, [peek(-1)]))
else:
params.append(parse_var(newscope=funcscope))
params.append(parse_var(new=funcscope))
tokens.append(params[-1])

if accept(")", tokens):
Expand All @@ -278,6 +319,9 @@ def parse_function(stmt=False, local=False):
for param in params:
if param.type == NodeType.var:
funcscope.add(param.var)

old_unresolved = unresolved_labels
unresolved_labels = None

funcdepth += 1
scope = funcscope
Expand All @@ -286,6 +330,9 @@ def parse_function(stmt=False, local=False):
require("end", tokens)
scope = scope.parent
funcdepth -= 1

late_resolve_labels()
unresolved_labels = old_unresolved

funcnode = Node(NodeType.function, tokens, target=target, params=params, body=body, name=name, kind=func_kind)
if self_param:
Expand Down Expand Up @@ -518,7 +565,7 @@ def parse_for():

if peek(1).value == "=":
newscope = Scope(scope, depth + 1, funcdepth)
target = parse_var(newscope=newscope)
target = parse_var(new=newscope)
tokens.append(target)
require("=", tokens)
min = parse_expr()
Expand All @@ -544,7 +591,7 @@ def parse_for():

else:
newscope = Scope(scope, depth + 1, funcdepth)
targets = parse_list(tokens, lambda: parse_var(newscope=newscope))
targets = parse_list(tokens, lambda: parse_var(new=newscope))
require("in", tokens)
sources = parse_list(tokens, parse_expr)
require("do", tokens)
Expand Down Expand Up @@ -590,7 +637,7 @@ def parse_local():
return Node(NodeType.local, tokens, targets=[func.name], sources=[func], func_local=True)

else:
targets = parse_list(tokens, lambda: parse_var(newscope=newscope))
targets = parse_list(tokens, lambda: parse_var(new=newscope))
sources = []
if accept("=", tokens):
sources = parse_list(tokens, parse_expr)
Expand Down Expand Up @@ -654,10 +701,11 @@ def parse_stmt(vline):
elif value == "local":
return parse_local()
elif value == "goto":
label = require_ident()
label = parse_var(label=True)
return Node(NodeType.goto, [token, label], label=label)
elif value == "::":
label = require_ident()
label = parse_var(label=True, new=True)
labelscope.add(label.var)
end = require("::")
return Node(NodeType.label, [token, label, end], label=label)
elif value == "function":
Expand All @@ -668,9 +716,9 @@ def parse_stmt(vline):
return parse_misc_stmt()

def parse_block(vline=None, with_until=False):
nonlocal scope, depth
nonlocal scope, labelscope, depth
oldscope = scope
start = peek()
labelscope = LabelScope(labelscope, funcdepth)
depth += 1

stmts = []
Expand All @@ -693,6 +741,7 @@ def parse_block(vline=None, with_until=False):
until = parse_until()

depth -= 1
labelscope = labelscope.parent
while scope != oldscope:
scope = scope.parent

Expand All @@ -701,10 +750,18 @@ def parse_block(vline=None, with_until=False):
return node, until
else:
return node

def late_resolve_labels():
if unresolved_labels:
for node in unresolved_labels:
node.var = node.scope.find(node.name)
if not node.var:
add_error_at("Unknown label %s" % node.name, node.children[0])

def parse_root():
root = parse_block()
root.globals = globals
late_resolve_labels()
if peek().type != None:
add_error("Expected end of input")
if idx < len(tokens):
Expand Down

0 comments on commit 2c61f8b

Please sign in to comment.