diff --git a/README.md b/README.md index 626f44a..d3a4ac4 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Reading/Writing PNGs additionally requires the Pillow module (`python -m pip ins [Download the latest version of the source here.](https://github.com/thisismypassport/shrinko8/archive/refs/heads/main.zip) -The supported tools are: +The major supported features are: * [Minification](#minification) - Reduce the token count, character count and compression ratio of your cart. +* [Constants](#constants) - Replace constant expressions with their value. Also removes 'if' branches with a constant condition. * [Linting](#linting) - Check for common code errors such as forgetting to declare a local. * [Getting Cart Size](#getting-cart-size) - Count the amount of tokens, characters, and compressed bytes your cart uses. * [Format Conversion](#format-conversion) - Convert between p8 files, pngs, and more. Achieves better code compression than Pico-8 when creating pngs. @@ -72,6 +73,7 @@ You can specify what the minification should focus on reducing via additional co You can disable parts of the minification process via additional command-line options: * `--no-minify-rename` : Disable all renaming of identifiers +* `--no-minify-consts` : Disable replacements of constant expressions with their value (see [constants](#constants)) * `--no-minify-spaces` : Disable removal of spaces (and line breaks) * `--no-minify-lines` : Disable removal of line breaks * `--no-minify-comments` : Disable removal of comments (requires `--no-minify-spaces`) @@ -311,6 +313,113 @@ You can keep specific comments in the output via: -- But this comment is gone after minify ``` +# Constants + +During [minification](#minification), Shrinko8 will automatically replace most constant expressions with their value: + +```lua +func(60*60) +-- becomes: +func(3600) + +func('the answer is: '..1+3*2) +-- becomes: +func('the answer is: 7') +``` + +In addition, variables that are declared with the `--[[const]]` hint are treated as constants: + +```lua +--[[const]] k_hero_spr = 4 +spr(k_hero_spr, x, y) +-- becomes: +spr(4, x, y) + +--[[const]] version = 'v1.2' +?'version: '..version +-- becomes: +?'version: v1.2' + +-- the --[[const]] hint can apply to either individual variables or entire local statements +--[[const]] local k_rock,k_box,k_wall = 4,5,6 +objs={k_rock,k_wall,k_wall,k_box} +-- becomes: +objs={4,6,6,5} + +-- some builtin functions can be used inside const declarations +--[[const]] k_value = 2.5 +--[[const]] derived = flr(mid(k_value, 1, 5)) +?derived +-- becomes: +?2 +``` + +Keep in mind that *Local* variables that aren't declared as `--[[const]]` may still be treated as constants in cases where it's safe & advantageous to do so. + +Furthermore, constant `if` and `elseif` branches are removed appropriately, allowing you to easily keep debug code in your source files, enabling it by simply changing the value of a variable: + +```lua +--[[const]] TRACE = false +--[[const]] DEBUG = true + +if (TRACE) ?"something happened!" +if DEBUG then + spr(debug_spr, 10, 10) +end + +-- becomes: +spr(debug_spr,10,10) +``` + +## Passing constants via command line + +You can even declare constants in the command line, if you prefer: + +`python shrinko8.py path-to-input.p8 path-to-output.p8 --minify-safe-only --const DEBUG true --const SPEED 2.5 --str-const VERSION v1.2` + +```lua +--[[CONST]] SPEED = 0.5 -- default value +if DEBUG then + ?'debug version ' .. (VERSION or '???') +end +hero = 0 +function _update() + hero += SPEED +end +``` + +Becomes: (disregarding other minifications) + +```lua +?"debug version v1.2" +hero = 0 +function _update() + hero += 2.5 +end +``` + +## Limitations + +Keep in mind that in some cases, Shrinko8 will play it safe and avoid a computation whose result is questionable or has a high potential to change between pico8 versions. If this prevents a `--[[const]]` variable from being assigned a constant, Shrinko8 will warn about this: + +```lua +--[[const]] x = abs(0x7fff+1)-1 +?x + +-- warning: +--tmp.lua:1:13: Local 'x' is marked as const but its value cannot be determined due to 'abs(0x7fff+1)' + +-- Becomes only: +x=abs(32768)-1 +?x +``` + +If you find such limitations that you'd like to see lifted, feel free to open an issue. + +Finally, note that: +* You can turn off all constant replacement via `--no-minify-consts`. +* You can prevent treating specific variables as constants by declaring them with a `--[[non-const]]` hint. (though normally, there is no reason to do this) + # Linting Linting finds common code issues in your cart, like forgetting to use a 'local' statement diff --git a/pico_constfold.py b/pico_constfold.py new file mode 100644 index 0000000..7762e29 --- /dev/null +++ b/pico_constfold.py @@ -0,0 +1,603 @@ +from utils import * +from pico_defs import fixnum_is_negative, k_fixnum_mask +from pico_tokenize import Token, TokenType, ConstToken, k_skip_children, tokenize +from pico_parse import Node, NodeType, VarKind, is_global_or_builtin_local, is_vararg_expr +from pico_output import format_fixnum + +class LuaType(Enum): + nil = boolean = number = string = ... + +class LuaValue: + def __init__(m, type, value): + m.type, m.value = type, value + + def is_number(m): + return m.type == LuaType.number + def is_string(m): + return m.type == LuaType.string + def is_truthy(m): + return not (m.type == LuaType.nil or (m.type == LuaType.boolean and not m.value)) + +class LuaNil(LuaValue): + def __init__(m): + super().__init__(LuaType.nil, None) + +class LuaBoolean(LuaValue): + def __init__(m, value): + super().__init__(LuaType.boolean, bool(value)) + +class LuaNumber(LuaValue): + # (value must be a fixnum) + def __init__(m, value): + super().__init__(LuaType.number, value & k_fixnum_mask) + +class LuaString(LuaValue): + # (value must be a string) + def __init__(m, value): + super().__init__(LuaType.string, value) + +k_lua_nil = LuaNil() +k_lua_true = LuaBoolean(True) +k_lua_false = LuaBoolean(False) +k_lua_maxint = 0x7fff + +def fixnum_is_whole(value): + return (value & 0xffff) == 0 + +def fixnum_to_whole(value): + return value >> 16 + +def fixnum_to_signed(value): + if value & 0x80000000: + value -= 0x100000000 + return value + +# no need for fixnum_from_signed - LuaNumber's masking does the job + +def is_signed_fixnum_in_range(value): + if value >= 0: + return value < 0x80000000 + else: + return value >= -0x80000000 + +# ops + +def lua_neg(a): + if a.is_number(): + return LuaNumber(-a.value) + +def lua_abs(a): + if a.is_number() and a.value != 0x80000000: # avoid relying on clamping overflow behavior + return LuaNumber(abs(a.value)) + +def lua_floor(a): + if a.is_number(): + return LuaNumber(a.value & 0xffff0000) + +def lua_ceil(a): + if a.is_number(): + if a.value & 0xffff: + a.value = (a.value & 0xffff0000) + 0x10000 + if a.value == 0x80000000: # avoid relying on wrapping overflow behavior + return + return LuaNumber(a.value) + +def lua_add(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(a.value + b.value) + +def lua_sub(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(a.value - b.value) + +def lua_mul(a, b): + if a.is_number() and b.is_number(): + result = (fixnum_to_signed(a.value) * fixnum_to_signed(b.value)) >> 16 + if is_signed_fixnum_in_range(result): # avoid relying on wrapping overflow behavior (or should we?) + return LuaNumber(result) + +def quot(a, b): + if (a < 0) != (b < 0): + return -(a // -b) + else: + return a // b + +def lua_div(a, b): + if a.is_number() and b.is_number() and b.value != 0: # avoid relying on div-by-0 behavior + result = quot(fixnum_to_signed(a.value) << 16, fixnum_to_signed(b.value)) + if is_signed_fixnum_in_range(result): # avoid relying on clamping overflow behavior + return LuaNumber(result) + +def lua_idiv(a, b): + c = lua_div(a, b) + if c: + return lua_floor(c) + +def lua_mod(a, b): + if a.is_number() and b.is_number() and b.value != 0 and not fixnum_is_negative(b.value): # avoid relying on mod-by-0 behavior, or on non-standard mod-by-neg behavior + result = fixnum_to_signed(a.value) % fixnum_to_signed(b.value) + if is_signed_fixnum_in_range(result): # probably always true, but just in case + return LuaNumber(result) + +def lua_eq(a, b): + return LuaBoolean(a.type == b.type and a.value == b.value) + +def lua_not_eq(a, b): + return LuaBoolean(a.type != b.type or a.value != b.value) + +def lua_less(a, b): + if a.is_number() and b.is_number(): + return LuaBoolean(fixnum_to_signed(a.value) < fixnum_to_signed(b.value)) + elif a.is_string() and b.is_string(): + return LuaBoolean(a.value < b.value) + +def lua_less_eq(a, b): + if a.is_number() and b.is_number(): + return LuaBoolean(fixnum_to_signed(a.value) <= fixnum_to_signed(b.value)) + elif a.is_string() and b.is_string(): + return LuaBoolean(a.value <= b.value) + +def lua_greater(a, b): + if a.is_number() and b.is_number(): + return LuaBoolean(fixnum_to_signed(a.value) > fixnum_to_signed(b.value)) + elif a.is_string() and b.is_string(): + return LuaBoolean(a.value > b.value) + +def lua_greater_eq(a, b): + if a.is_number() and b.is_number(): + return LuaBoolean(fixnum_to_signed(a.value) >= fixnum_to_signed(b.value)) + elif a.is_string() and b.is_string(): + return LuaBoolean(a.value >= b.value) + +def lua_max(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(max(fixnum_to_signed(a.value), fixnum_to_signed(b.value))) + +def lua_min(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(min(fixnum_to_signed(a.value), fixnum_to_signed(b.value))) + +def _mid(n1, n2, n3): + b12, b23, b13 = n1 <= n2, n2 <= n3, n1 <= n3 + return n2 if b12 == b23 else n1 if b12 != b13 else n3 + +def lua_mid(a, b, c): + if a.is_number() and b.is_number() and c.is_number(): + return LuaNumber(_mid(fixnum_to_signed(a.value), fixnum_to_signed(b.value), fixnum_to_signed(c.value))) + +def lua_bin_not(a): + if a.is_number(): + return LuaNumber(~a.value) + +def lua_bin_and(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(a.value & b.value) + +def lua_bin_or(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(a.value | b.value) + +def lua_bin_xor(a, b): + if a.is_number() and b.is_number(): + return LuaNumber(a.value ^ b.value) + +def lua_shl(a, b): + if a.is_number() and b.is_number(): + if fixnum_is_negative(b.value): + if not fixnum_is_negative(a.value): # avoid relying on questionable shift-left-neg-by-neg behaviour + return lua_lshr(a, lua_neg(b)) + elif fixnum_is_whole(b.value): # avoid relying on shift-by-fract behaviour + return LuaNumber(a.value << fixnum_to_whole(b.value)) + +def lua_shr(a, b): + if a.is_number() and b.is_number(): + if fixnum_is_negative(b.value): + return lua_shl(a, lua_neg(b)) + elif fixnum_is_whole(b.value): + return LuaNumber(fixnum_to_signed(a.value) >> fixnum_to_whole(b.value)) + +def lua_lshr(a, b): + if a.is_number() and b.is_number(): + if fixnum_is_negative(b.value): + return lua_shl(a, lua_neg(b)) + elif fixnum_is_whole(b.value): + return LuaNumber(a.value >> fixnum_to_whole(b.value)) + +def lua_rotl(a, b): + if a.is_number() and b.is_number(): + if fixnum_is_negative(b.value): + return lua_rotr(a, lua_neg(b)) + elif fixnum_is_whole(b.value): + return LuaNumber(rotate_left(a.value, fixnum_to_whole(b.value), 32)) + +def lua_rotr(a, b): + if a.is_number() and b.is_number(): + if fixnum_is_negative(b.value): + return lua_rotl(a, lua_neg(b)) + elif fixnum_is_whole(b.value): + return LuaNumber(rotate_right(a.value, fixnum_to_whole(b.value), 32)) + +def lua_not(a): + return LuaBoolean(not a.is_truthy()) + +def lua_and(a, b): + return b if a.is_truthy() else a + +def lua_or(a, b): + return a if a.is_truthy() else b + +def lua_len(a): + if a.is_string() and len(a.value) <= k_lua_maxint: # avoid relying on long string behavior + return LuaNumber(len(a.value) << 16) + +def _lua_tostr(a): + if a.is_string(): + return a + elif a.is_number() and fixnum_is_whole(a.value): # avoid relying on fract. tostr + return LuaString(format_fixnum(a.value, base=10, sign='-' if fixnum_is_negative(a.value) else '')) + +def lua_cat(a, b): + a, b = _lua_tostr(a), _lua_tostr(b) + if a and b: + return LuaString(a.value + b.value) + +k_const_unary_ops = { + "-": lua_neg, + "~": lua_bin_not, + "not": lua_not, + "#": lua_len, +} + +k_const_binary_ops = { + "or": lua_or, + "and": lua_and, + "!=": lua_not_eq, + "~=": lua_not_eq, + "==": lua_eq, + "<": lua_less, + "<=": lua_less_eq, + ">": lua_greater, + ">=": lua_greater_eq, + "|": lua_bin_or, + "^^": lua_bin_xor, + "~": lua_bin_xor, + "&": lua_bin_and, + "<<": lua_shl, + ">>": lua_shr, + ">>>": lua_lshr, + "<<>": lua_rotl, + ">><": lua_rotr, + "..": lua_cat, + "+": lua_add, + "-": lua_sub, + "*": lua_mul, + "/": lua_div, + "\\": lua_idiv, + "%": lua_mod, +} + +k_const_globals = { + "abs": (lua_abs, 1), + "band": (lua_bin_and, 2), + "bnot": (lua_bin_not, 1), + "bor": (lua_bin_or, 2), + "bxor": (lua_bin_xor, 2), + "ceil": (lua_ceil, 1), + "flr": (lua_floor, 1), + "lshr": (lua_lshr, 2), + "max": (lua_max, 2), + "mid": (lua_mid, 3), + "min": (lua_min, 2), + "rotl": (lua_rotl, 2), + "rotr": (lua_rotr, 2), + "shl": (lua_shl, 2), + "shr": (lua_shr, 2), +} + +def const_from_token(token): + if token.type == TokenType.number: + return LuaNumber(token.fixnum_value) + elif token.type == TokenType.string: + return LuaString(token.string_value) + elif token.value == "nil": + return k_lua_nil + elif token.value == "true": + return k_lua_true + elif token.value == "false": + return k_lua_false + else: + return None + +def get_const(node): + if node.type == NodeType.const: + # compute & cache const value + token = node.token + constval = const_from_token(token) + assert constval, "unexpected const token: %s" % token + return constval + elif node.type == NodeType.group: + return get_const(node.child) + else: + return None + +def set_const(node, value): + if value.type == LuaType.number: + token = ConstToken(TokenType.number, node, fixnum_value=value.value) + elif value.type == LuaType.string: + token = ConstToken(TokenType.string, node, string_value=value.value) + elif value.type == LuaType.boolean: + token = ConstToken(TokenType.keyword, node, value="true" if value.value else "false") + elif value.type == LuaType.nil: + token = ConstToken(TokenType.keyword, node, value="nil") + else: + assert False, "unexpected const value: %s" % value + + node.replace_with(create_const_node(token)) + +def fixup_syntax_between(parent, prev, next): + nextt = next.first_token() + if nextt and nextt.value == "(": + prevt = prev.last_token() + if prevt.value in (")", "]") or prevt.type == TokenType.ident: + i = parent.children.index(prev) + parent.insert_token(i+1, TokenType.punct, ";", near_next=True) + +def fixup_syntax_after_remove(stmt): + # after removing stmt (or even just part of stmt), extra semicolons may need to be inserted + parent = stmt.parent + if parent.type == NodeType.block: + stmt_n = len(parent.stmts) + stmt_i = parent.stmts.index(stmt) + if stmt.type is None: + if 0 < stmt_i < stmt_n - 1: + fixup_syntax_between(parent, parent.stmts[stmt_i-1], parent.stmts[stmt_i+1]) + else: + if stmt_i > 0: + fixup_syntax_between(parent, parent.stmts[stmt_i-1], stmt) + if stmt_i < stmt_n - 1: + fixup_syntax_between(parent, stmt, parent.stmts[stmt_i+1]) + +def erase_assign_targets(node, indices): + if len(node.targets) < len(node.sources) and len(indices) == len(node.targets): + indices.pop() # can't deal with this easily otherwise + + def delete_array_item(node, array, i): + child_i = node.children.index(array[i]) + node.remove_child(child_i) + + # need to delete comma too + if i > 0: + node.remove_token(child_i - 1, ",") + elif len(array) > 1: + node.remove_token(child_i, ",") + elif node.type == NodeType.local and array is node.sources: + node.remove_token(child_i - 1, "=") + + del array[i] + + for i in reversed(indices): + delete_array_item(node, node.targets, i) + if i < len(node.sources): + delete_array_item(node, node.sources, i) + + if not node.targets and not node.sources: + node.erase() + elif not node.sources and node.type != NodeType.local: + nil_token = Token.synthetic(TokenType.keyword, "nil", node, append=True) + node.sources.append(create_const_node(nil_token)) + node.append_existing(node.sources[-1]) + else: + assert node.targets # due to the pop above + + fixup_syntax_after_remove(node) + +def create_const_node(token): + return Node(NodeType.const, [token], token=token) + +def create_do_node_if_needed(node): + needed = False + + def check_do_needed(node): + nonlocal needed + if node.type == NodeType.var and node.new: + needed = True + elif node.type in (NodeType.return_, NodeType.break_, NodeType.goto): + needed = True + elif node.type in (NodeType.if_, NodeType.elseif, NodeType.while_, NodeType.repeat, NodeType.for_, NodeType.for_in, NodeType.do): + return k_skip_children + elif node.type == NodeType.function: + if node.target: + node.target.traverse_nodes(pre=check_do_needed) + return k_skip_children + + node.traverse_nodes(pre=check_do_needed) + if needed: + do_token = Token.synthetic(TokenType.keyword, "do", node, prepend=True) + end_token = Token.synthetic(TokenType.keyword, "end", node, append=True) + node = Node(NodeType.do, [do_token, node, end_token], body=node) + return node + +def create_else_node(node, short=False): + else_token = Token.synthetic(TokenType.keyword, "else", node, prepend=True) + end_token = Token.synthetic(TokenType.keyword, "end", node, append=True) + return Node(NodeType.else_, [else_token, node, end_token], body=node, short=short) + +def remove_else_node(node): + assert node.children[-1] is node.else_ + node.remove_child(-1) + node.else_ = None + end_token = Token.synthetic(TokenType.keyword, "end", node, append=True) + node.append_existing(end_token) + +def fold_consts(ctxt, root, errors): + + def add_error(msg, node): + errors.append(Error(msg, node)) + + in_const_ctxt = 0 + const_ctxt_fail = None + + def skip_special(node): + if node.type in (NodeType.local, NodeType.assign): + # we traverse these specially in update_node_constval + return k_skip_children + + def update_node_constval(node): + nonlocal in_const_ctxt, const_ctxt_fail + # we don't check NodeType.const/group until needed (in get_const) + + if node.type == NodeType.unary_op: + func = k_const_unary_ops.get(node.op, None) + if func: + child_const = get_const(node.child) + if child_const: + constval = func(child_const) + if constval: + set_const(node, constval) + + elif node.type == NodeType.binary_op: + func = k_const_binary_ops.get(node.op, None) + if func: + left_const = get_const(node.left) + if left_const: # (optimization) + right_const = get_const(node.right) + if right_const: + constval = func(left_const, right_const) + elif func in (lua_and, lua_or): # short-circuit + constval = func(left_const, node.right) + else: + constval = None + + if constval: + if constval is node.right: + node.replace_with(node.right) + else: + set_const(node, constval) + + elif node.type == NodeType.var and node.var and node.var.constval and not node.new and not node.assignment: + set_const(node, node.var.constval) + + elif node.type in (NodeType.local, NodeType.assign): + visit_assign(node) + + # we assume that in a const context, calls are always to builtins + # TODO: can we be smarter in the non-safe-only case? + elif node.type == NodeType.call and node.func.type == NodeType.var and is_global_or_builtin_local(node.func) \ + and not node.func.var.reassigned and (not root.has_env or in_const_ctxt): + func, num_args = k_const_globals.get(node.func.name, (None, None)) + if func and (num_args is None or num_args == len(node.args)): + arg_consts = [] + for arg in node.args: + arg_const = get_const(arg) + if arg_const: + arg_consts.append(arg_const) + else: + break # (optimization) + else: + constval = func(*arg_consts) + if constval: + set_const(node, constval) + + elif node.type == NodeType.if_: + constval = get_const(node.cond) + if constval: + if constval.is_truthy(): + node.replace_with(create_do_node_if_needed(node.then)) + elif not node.else_: + node.erase() + elif node.else_.type == NodeType.else_: + node.replace_with(create_do_node_if_needed(node.else_.body)) + else: # elseif + node.replace_with(node.else_) + node.modify_token(0, "if", expected="elseif") + node.type = NodeType.if_ + + elif node.type == NodeType.elseif: + constval = get_const(node.cond) + if constval: + if constval.is_truthy(): + node.replace_with(create_else_node(node.then, node.short)) + elif not node.else_: + remove_else_node(node.parent) + else: + node.replace_with(node.else_) + + # store which node was expected to be const but isn't + if in_const_ctxt and node.type != NodeType.const and const_ctxt_fail is None: + if node.parent.type == NodeType.call and node == node.parent.func: + pass + else: + const_ctxt_fail = node + + def visit_assign(node): + nonlocal in_const_ctxt, const_ctxt_fail + erasable = [] + + for i, target in enumerate(node.targets): + target.traverse_nodes(pre=skip_special, post=update_node_constval) + + if target.type == NodeType.var: + is_const_ctxt = target.var.is_const + # (even if a global is assigned once, it's hard to tell if all its uses are after the assignment, so we close globals under --[[const]]) + is_const = (target.kind == VarKind.local and not target.var.reassigned) or \ + (is_const_ctxt and target.kind == VarKind.global_ and target.var.reassigned <= 1 and target.name not in ctxt.builtins) + else: + is_const = is_const_ctxt = False + + if is_const_ctxt: + in_const_ctxt += 1 + const_ctxt_fail = None + + constval = None + if is_const and is_const_ctxt != False: + if i < len(node.sources): + source = node.sources[i] + source.traverse_nodes(pre=skip_special, post=update_node_constval) + constval = get_const(source) + elif not (node.sources and is_vararg_expr(node.sources[-1])): + constval = k_lua_nil + else: + if i < len(node.sources): + node.sources[i].traverse_nodes(pre=skip_special, post=update_node_constval) + + if constval: + if is_const_ctxt or not isinstance(constval, LuaString): # string const folding may balloon total char count + erasable.append(i) + if not target.var.constval: # may've been set to something else through ctxt.consts + target.var.constval = constval + else: + if is_const_ctxt: + reason = "" + if const_ctxt_fail: + reason = f" due to '{const_ctxt_fail.source_text}'" + add_error(f"Local '{target.name}' is marked as const but its value cannot be determined" + reason, target) + + if is_const_ctxt: + in_const_ctxt -= 1 + const_ctxt_fail = None + + for i, source in enumerate(node.sources): + if i >= len(node.targets): # else, traversed above + source.traverse_nodes(pre=skip_special, post=update_node_constval) + + if erasable: + erase_assign_targets(node, erasable) + + root.traverse_nodes(pre=skip_special, post=update_node_constval) + +def parse_constant(value): + tokens, errors = tokenize(Source(None, value)) + if not errors: + token = None + if len(tokens) == 1: + token = tokens[0] + elif len(tokens) == 2 and tokens[0].value in ("-", "~") and tokens[1].type == TokenType.number: + token = tokens[1] + token.value = tokens[0].value + token.value + + if token: + constval = const_from_token(token) + if constval: + return constval + +from pico_process import Error, Source diff --git a/pico_defs.py b/pico_defs.py index befaa75..f3fe4d1 100644 --- a/pico_defs.py +++ b/pico_defs.py @@ -219,6 +219,7 @@ def decode_p8str(bytes): from_pico_chars = from_p8str # legacy name # fixnum - an integer representing a 16.16 pico8 fixed-point number +# e.g. # This may become a class in the future, but is a convention now. k_fixnum_mask = 0xffffffff @@ -242,6 +243,8 @@ def fixnum_to_num(value): value >>= 16 # preserve int-ness return -value if neg else value +# pico-8 versions + k_version_tuples = { 29: (0,2,1,0), 30: (0,2,2,0), diff --git a/pico_lint.py b/pico_lint.py index 4a0f45c..9606719 100644 --- a/pico_lint.py +++ b/pico_lint.py @@ -13,8 +13,7 @@ def lint_code(ctxt, root, lint_opts): custom_globals = set(lint_opts.get("globals", ())) def add_error(msg, node): - err = Error(msg, node) - errors.append(err) + errors.append(Error(msg, node)) # find global assignment, and check for uses diff --git a/pico_minify.py b/pico_minify.py index 0c9289a..8d57284 100644 --- a/pico_minify.py +++ b/pico_minify.py @@ -1,111 +1,19 @@ from utils import * -from pico_tokenize import TokenType, tokenize, Token, k_char_escapes, CommentHint -from pico_tokenize import parse_string_literal, parse_fixnum, k_keep_prefix +from pico_tokenize import TokenType from pico_tokenize import StopTraverse, k_skip_children -from pico_parse import Node, NodeType, VarKind, k_nested -from pico_parse import k_unary_ops_prec, k_binary_op_precs, k_right_binary_ops +from pico_parse import Node, NodeType, VarKind +from pico_parse import k_unary_ops_prec, get_precedence, is_right_assoc from pico_parse import is_vararg_expr, is_short_block_stmt, is_global_or_builtin_local +from pico_output import format_fixnum, format_string_literal +from pico_output import output_min_wspace, output_original_wspace class Focus(Bitmask): chars = compressed = tokens = ... none = 0 -# essentially only returns decvalue right now, given mostly non-fract. inputs -# TODO: test with fract-ish inputs to see what's best to do. -def format_fixnum(value, allow_minus=False): - """format a fixnum to a pico8 string""" - intvalue = value >> 16 - dotvalue = value & 0xffff - - hexvalue = "0x%x" % intvalue - if dotvalue: - hexvalue = "0x" if hexvalue == "0x0" else hexvalue - hexvalue += (".%04x" % dotvalue).rstrip('0') - - def str_add_1(str): - if not str: - return "1" - elif str[-1] == ".": - return str_add_1(str[:-1]) + "." - elif str[-1] == "9": - return str_add_1(str[:-1]) + "0" - else: - return str[:-1] + chr(ord(str[-1]) + 1) - - numvalue = value / (1 << 16) - decvalue = "%.10f" % numvalue - while "." in decvalue: - nextvalue = decvalue[:-1] - nextupvalue = str_add_1(nextvalue) - if parse_fixnum(nextvalue) == value: - decvalue = nextvalue - elif parse_fixnum(nextupvalue) == value: - decvalue = nextupvalue - else: - break - if decvalue.startswith("0."): - decvalue = decvalue[1:] - - minvalue = hexvalue if len(hexvalue) < len(decvalue) else decvalue - - if allow_minus and value & 0x80000000 and value != 0x80000000: - negvalue = "-" + format_fixnum(-value & 0xffffffff) - if len(negvalue) < len(minvalue): - minvalue = negvalue - - return minvalue - -k_char_escapes_rev = {v: k for k, v in k_char_escapes.items() if k != '\n'} -k_char_escapes_rev.update({"\0": "0", "\x0e": "14", "\x0f": "15"}) - -k_char_escapes_rev_min = {k: v for k, v in k_char_escapes_rev.items() if k in "\0\n\r\"'\\"} - -def format_string_literal(value, use_ctrl_chars=True, use_complex_long=True, long=None, quote=None): - """format a pico8 string to a pico8 string literal""" - - if long != False: - if "\0" not in value and "\r" not in value and (use_complex_long or "]]" not in value): - newline = "\n" if value.startswith("\n") else "" - - for i in itertools.count(): - eqs = "=" * i - if f"]{eqs}]" not in value: - break - - strlong = f"[{eqs}[{newline}{value}]{eqs}]" - if long == True: - return strlong - else: - strlong = None - long = False - - if long != True: - if quote is None: - quote = '"' if value.count('"') <= value.count("'") else "'" - - exclude_esc = "'" if quote == '"' else '"' - - char_escapes_rev = k_char_escapes_rev_min if use_ctrl_chars else k_char_escapes_rev - - litparts = [] - for i, ch in enumerate(value): - if ch in char_escapes_rev and ch != exclude_esc: - esc = char_escapes_rev[ch] - if esc.isdigit() and i + 1 < len(value) and value[i + 1].isdigit(): - esc = esc.rjust(3, '0') - litparts.append("\\" + esc) - else: - litparts.append(ch) - - strlit = '%s%s%s' % (quote, "".join(litparts), quote) - if long == False: - return strlit - - return strlong if len(strlong) < len(strlit) else strlit - def minify_string_literal(ctxt, token, focus, value=None): if value is None: - value = parse_string_literal(token.value) + value = token.string_value if focus.chars: return format_string_literal(value, use_complex_long=ctxt.version >= 40) @@ -113,18 +21,6 @@ def minify_string_literal(ctxt, token, focus, value=None): # haven't found a good balanced heuristic for 'long' yet return format_string_literal(value, long=token.value.startswith('[')) -def get_precedence(node): - if node.type == NodeType.binary_op: - return k_binary_op_precs[node.op] - elif node.type == NodeType.unary_op: - return k_unary_ops_prec - -def is_right_assoc(node): - if node.type == NodeType.binary_op: - return node.op in k_right_binary_ops - else: - return False - def minify_needs_comments(minify): # returns whether minify_code makes use of the tokens' comments return not minify.get("wspace", True) @@ -157,7 +53,7 @@ def analyze_node_post(node): # can the node be converted to shorthand? if not is_short and not has_elseif: - has_shorthand, has_empties = False, False + has_shorthand, has_empties, starts_with_do = False, False, False def check_shorthand(node): nonlocal has_shorthand @@ -169,13 +65,17 @@ def check_shorthand(node): node.traverse_parents(check_shorthand) # now check the children - for body in get_node_bodies(node): + for i, body in enumerate(get_node_bodies(node)): body.traverse_nodes(post=check_shorthand) if not body.children: has_empties = True + + if i == 0: + # beware of do block ambiguity + starts_with_do = body.first_token().value == "do" # empty bodies require extra ';'s to shorten, which worsens compression - is_short = not has_shorthand and not (has_empties and not focus.chars) + is_short = not has_shorthand and not (has_empties and not focus.chars) and not starts_with_do if is_short: shortenables.add(node) @@ -202,16 +102,16 @@ def check_shorthand(node): def minify_change_shorthand(node, new_short): if new_short: node.short = True - node.erase_token(2, ("then", "do")) + node.remove_token(2, ("then", "do")) if node.type == NodeType.if_ and node.else_: node.else_.short = True - node.else_.erase_token(-1, "end") + node.else_.remove_token(-1, "end") else: - node.erase_token(-1, "end") + node.remove_token(-1, "end") # we can assume node.cond is not wrapped in parens, since we're in a post-visit # wrap it in parens ourselves (TODO: eww...) - node.cond.replace_with(Node(NodeType.group, [], child=copy(node.cond))) + node.cond.replace_with(Node(NodeType.group, [], child=node.cond.move())) node.cond.children.append(node.cond.child) node.cond.insert_token(0, TokenType.punct, "(", near_next=True) node.cond.append_token(TokenType.punct, ")") @@ -478,12 +378,12 @@ def fixup_tokens(token): if token.type == TokenType.number: outer_prec = get_precedence(token.parent.parent) if token.parent.type == NodeType.const else None - allow_minus = outer_prec is None or outer_prec < k_unary_ops_prec - token.modify(format_fixnum(parse_fixnum(token.value), allow_minus=allow_minus)) - if token.value.startswith("-"): - # insert synthetic minus token, so that output_tokens's tokenize won't get confused + allow_unary = outer_prec is None or outer_prec < k_unary_ops_prec + token.modify(format_fixnum(token.fixnum_value, sign=None if allow_unary else '')) + if token.value.startswith("-") or token.value.startswith("~"): + # insert synthetic unary token, so that output_tokens's tokenize won't get confused + token.parent.insert_token(0, TokenType.punct, token.value[0], near_next=True) token.modify(token.value[1:]) - token.parent.insert_token(0, TokenType.punct, "-", near_next=True) root.traverse_nodes(fixup_nodes_pre, fixup_nodes_post, tokens=fixup_tokens) @@ -491,128 +391,3 @@ def fixup_tokens(token): return output_min_wspace(root, minify_lines) else: return output_original_wspace(root, minify_comments) - -def need_whitespace_between(prev_token, token): - combined = prev_token.value + token.value - ct, ce = tokenize(PicoSource(None, combined)) - return ce or len(ct) != 2 or (ct[0].type, ct[0].value, ct[1].type, ct[1].value) != (prev_token.type, prev_token.value, token.type, token.value) - -def need_newline_after(node): - # (k_nested is set for shorthands used in the middle of a line - we don't *YET* generate this ourselves (TODO: do for 0.2.6b+), but we do preserve it) - return node.short and node.short is not k_nested - -def output_min_wspace(root, minify_lines=True): - """convert a root back to a string, inserting as little whitespace as possible""" - output = [] - prev_token = Token.none - prev_vline = 0 - need_linebreak = False - - def output_tokens(token): - nonlocal prev_token, prev_vline, need_linebreak - - if token.children: - for comment in token.children: - if comment.hint == CommentHint.keep: - output.append(comment.value.replace(k_keep_prefix, "", 1)) - - if token.value is None: - return - - # (modified tokens may require whitespace not previously required - e.g. 0b/0x) - if (prev_token.endidx < token.idx or prev_token.modified or token.modified) and prev_token.value: - # TODO: can we systemtically add whitespace to imrpove compression? (failed so far) - - if need_linebreak or (not minify_lines and e(token.vline) and token.vline != prev_vline): - output.append("\n") - need_linebreak = False - elif need_whitespace_between(prev_token, token): - output.append(" ") - - output.append(token.value) - prev_token = token - if e(token.vline): - prev_vline = token.vline - - def post_node_output(node): - nonlocal need_linebreak - if need_newline_after(node): - need_linebreak = True - - root.traverse_nodes(tokens=output_tokens, post=post_node_output) - return "".join(output) - -def output_original_wspace(root, exclude_comments=False): - """convert a root back to a string, using original whitespace (optionally except comments)""" - output = [] - prev_token = Token.none - prev_welded_token = None - prev_vline = 0 - need_linebreak = False - - def get_wspace(pre, post, allow_linebreaks, need_linebreak=False): - source = default(pre.source, post.source) - text = source.text[pre.endidx:post.idx] - - if not text.isspace(): - # verify this range contains only whitespace/comments (may contain more due to reorders/removes) - tokens, _ = tokenize(PicoSource(None, text)) - if tokens: - text = text[:tokens[0].idx] - - if not allow_linebreaks and "\n" in text: - text = text[:text.index("\n")] + " " - if need_linebreak and "\n" not in text: - text += "\n" - - return text - - def output_tokens(token): - nonlocal prev_token, prev_welded_token, prev_vline, need_linebreak - - if prev_token.endidx != token.idx: - allow_linebreaks = e(token.vline) and token.vline != prev_vline - wspace = get_wspace(prev_token, token, allow_linebreaks, need_linebreak) - - if exclude_comments and token.children: - # only output spacing before and after the comments between the tokens - prespace = get_wspace(prev_token, token.children[0], allow_linebreaks) - postspace = get_wspace(token.children[-1], token, allow_linebreaks) - for comment in token.children: - if comment.hint == CommentHint.keep: - prespace += comment.value.replace(k_keep_prefix, "", 1) - - output.append(prespace) - if "\n" in wspace and "\n" not in prespace and "\n" not in postspace: - output.append("\n") - elif wspace and not prespace and not postspace: - output.append(" ") - output.append(postspace) - else: - output.append(wspace) - - prev_welded_token = None - need_linebreak = False - - # extra whitespace may be needed due to modified or deleted tokens - if prev_welded_token and token.value and (prev_welded_token.modified or token.modified or prev_welded_token != prev_token): - if need_whitespace_between(prev_welded_token, token): - output.append(" ") - - if token.value != None: - output.append(token.value) - prev_welded_token = token - - prev_token = token - if e(token.vline): - prev_vline = token.vline - - def post_node_output(node): - nonlocal need_linebreak - if need_newline_after(node): - need_linebreak = True - - root.traverse_nodes(tokens=output_tokens, post=post_node_output) - return "".join(output) - -from pico_process import PicoSource diff --git a/pico_output.py b/pico_output.py new file mode 100644 index 0000000..d184c9f --- /dev/null +++ b/pico_output.py @@ -0,0 +1,245 @@ +from utils import * +from pico_tokenize import tokenize, parse_fixnum +from pico_tokenize import Token, k_char_escapes, CommentHint, k_keep_prefix +from pico_parse import k_nested + +# essentially only returns decvalue right now, given mostly non-fract. inputs +# TODO: test with fract-ish inputs to see what's best to do. +def format_fixnum(value, sign=None, base=None): + """format a fixnum to a pico8 string""" + if sign: + if sign == '-': + value = -value & 0xffffffff + elif sign == '~': + value = ~value & 0xffffffff + else: + throw("invalid sign") + + intvalue = value >> 16 + dotvalue = value & 0xffff + + if base is None or base == 16: + hexvalue = "0x%x" % intvalue + if dotvalue: + hexvalue = "0x" if hexvalue == "0x0" else hexvalue + hexvalue += (".%04x" % dotvalue).rstrip('0') + + if base: + minvalue = hexvalue + + if base is None or base == 10: + def str_add_1(str): + if not str: + return "1" + elif str[-1] == ".": + return str_add_1(str[:-1]) + "." + elif str[-1] == "9": + return str_add_1(str[:-1]) + "0" + else: + return str[:-1] + chr(ord(str[-1]) + 1) + + numvalue = value / (1 << 16) + decvalue = "%.10f" % numvalue + while "." in decvalue: + nextvalue = decvalue[:-1] + nextupvalue = str_add_1(nextvalue) + if parse_fixnum(nextvalue) == value: + decvalue = nextvalue + elif parse_fixnum(nextupvalue) == value: + decvalue = nextupvalue + else: + break + if decvalue.startswith("0."): + decvalue = decvalue[1:] + + if base: + minvalue = decvalue + + if not base: + minvalue = hexvalue if len(hexvalue) < len(decvalue) else decvalue + + if sign: + minvalue = sign + minvalue + + if sign is None and value & 0x80000000 and value != 0x80000000: + negvalue = format_fixnum(value, sign='-', base=base) + if len(negvalue) < len(minvalue): + minvalue = negvalue + + notvalue = format_fixnum(value, sign='~', base=base) + if len(notvalue) < len(minvalue): + minvalue = notvalue + + return minvalue + +k_char_escapes_rev = {v: k for k, v in k_char_escapes.items() if k != '\n'} +k_char_escapes_rev.update({"\0": "0", "\x0e": "14", "\x0f": "15"}) + +k_char_escapes_rev_min = {k: v for k, v in k_char_escapes_rev.items() if k in "\0\n\r\"'\\"} + +def format_string_literal(value, use_ctrl_chars=True, use_complex_long=True, long=None, quote=None): + """format a pico8 string to a pico8 string literal""" + + if long != False: + if "\0" not in value and "\r" not in value and (use_complex_long or "]]" not in value): + newline = "\n" if value.startswith("\n") else "" + + for i in itertools.count(): + eqs = "=" * i + if f"]{eqs}]" not in value: + break + + strlong = f"[{eqs}[{newline}{value}]{eqs}]" + if long == True: + return strlong + else: + strlong = None + long = False + + if long != True: + if quote is None: + quote = '"' if value.count('"') <= value.count("'") else "'" + + exclude_esc = "'" if quote == '"' else '"' + + char_escapes_rev = k_char_escapes_rev_min if use_ctrl_chars else k_char_escapes_rev + + litparts = [] + for i, ch in enumerate(value): + if ch in char_escapes_rev and ch != exclude_esc: + esc = char_escapes_rev[ch] + if esc.isdigit() and i + 1 < len(value) and value[i + 1].isdigit(): + esc = esc.rjust(3, '0') + litparts.append("\\" + esc) + else: + litparts.append(ch) + + strlit = '%s%s%s' % (quote, "".join(litparts), quote) + if long == False: + return strlit + + return strlong if len(strlong) < len(strlit) else strlit + +def need_whitespace_between(prev_token, token): + combined = prev_token.value + token.value + ct, ce = tokenize(Source(None, combined)) + return ce or len(ct) != 2 or (ct[0].type, ct[0].value, ct[1].type, ct[1].value) != (prev_token.type, prev_token.value, token.type, token.value) + +def need_newline_after(node): + # (k_nested is set for shorthands used in the middle of a line - we don't *YET* generate this ourselves (TODO: do for 0.2.6b+), but we do preserve it) + return node.short and node.short is not k_nested + +def output_min_wspace(root, minify_lines=True): + """convert a root back to a string, inserting as little whitespace as possible""" + output = [] + prev_token = Token.none + prev_vline = 0 + need_linebreak = False + + def output_tokens(token): + nonlocal prev_token, prev_vline, need_linebreak + + if token.children: + for comment in token.children: + if comment.hint == CommentHint.keep: + output.append(comment.value.replace(k_keep_prefix, "", 1)) + + if token.value is None: + return + + # (modified tokens may require whitespace not previously required - e.g. 0b/0x) + if (prev_token.endidx < token.idx or prev_token.modified or token.modified) and prev_token.value: + # TODO: can we systemtically add whitespace to imrpove compression? (failed so far) + + if need_linebreak or (not minify_lines and e(token.vline) and token.vline != prev_vline): + output.append("\n") + need_linebreak = False + elif need_whitespace_between(prev_token, token): + output.append(" ") + + output.append(token.value) + prev_token = token + if e(token.vline): + prev_vline = token.vline + + def post_node_output(node): + nonlocal need_linebreak + if need_newline_after(node): + need_linebreak = True + + root.traverse_nodes(tokens=output_tokens, post=post_node_output) + return "".join(output) + +def output_original_wspace(root, exclude_comments=False): + """convert a root back to a string, using original whitespace (optionally except comments)""" + output = [] + prev_token = Token.none + prev_welded_token = None + prev_vline = 0 + need_linebreak = False + + def get_wspace(pre, post, allow_linebreaks, need_linebreak=False): + source = default(pre.source, post.source) + text = source.text[pre.endidx:post.idx] + + if not text.isspace(): + # verify this range contains only whitespace/comments (may contain more due to reorders/removes) + tokens, _ = tokenize(Source(None, text)) + if tokens: + if "\n" in text and allow_linebreaks: + need_linebreak = True + text = text[:tokens[0].idx] or text[tokens[-1].endidx:] + + if not allow_linebreaks and "\n" in text: + text = text[:text.index("\n")] + " " + if need_linebreak and "\n" not in text: + text += "\n" + + return text + + def output_tokens(token): + nonlocal prev_token, prev_vline, need_linebreak + + if token.value is None: + return + + if prev_token.endidx != token.idx or prev_token.modified or token.modified: + allow_linebreaks = e(token.vline) and token.vline != prev_vline + wspace = get_wspace(prev_token, token, allow_linebreaks, need_linebreak) + + if not wspace and prev_token.value != None and need_whitespace_between(prev_token, token): + wspace += " " + + if exclude_comments and token.children: + # only output spacing before and after the comments between the tokens + prespace = get_wspace(prev_token, token.children[0], allow_linebreaks) + postspace = get_wspace(token.children[-1], token, allow_linebreaks) + for comment in token.children: + if comment.hint == CommentHint.keep: + prespace += comment.value.replace(k_keep_prefix, "", 1) + + output.append(prespace) + if "\n" in wspace and "\n" not in prespace and "\n" not in postspace: + output.append("\n") + elif wspace and not prespace and not postspace: + output.append(" ") + output.append(postspace) + else: + output.append(wspace) + + need_linebreak = False + + output.append(token.value) + prev_token = token + if e(token.vline): + prev_vline = token.vline + + def post_node_output(node): + nonlocal need_linebreak + if need_newline_after(node): + need_linebreak = True + + root.traverse_nodes(tokens=output_tokens, post=post_node_output) + return "".join(output) + +from pico_process import Source diff --git a/pico_parse.py b/pico_parse.py index 1a989d2..f7382e5 100644 --- a/pico_parse.py +++ b/pico_parse.py @@ -11,11 +11,13 @@ class VarBase(): def __init__(m, kind, name): m.kind = kind m.name = name + m.is_const = None m.implicit = False # has implicit *access*? - m.reassigned = False + m.reassigned = 0 keys_kind = None rename = None + constval = None def __repr__(m): return f"{m.kind} {m.name}" @@ -150,6 +152,7 @@ def get_tokens(m): return tokens short = False # default property + assignment = False # default proprerty value = None # default proprty (TODO: bad, for consistency with Token) @property @@ -182,10 +185,18 @@ def append_token(m, type, value, near_next=False): m.children.append(m._create_for_insert(len(m.children), type, value, near_next)) m.children[-1].parent = m + def modify_token(m, i, value, expected=None): + m.children[i].modify(value, expected) + def erase_token(m, i, expected=None): m.children[i].erase(expected) - - def insert_existing(m, i, existing, near_next=False): # junks existing (so must be erased one way or another) + + def remove_token(m, i, expected=None): + if expected != None: + m.children[i].check(expected) + del m.children[i] + + def insert_existing(m, i, existing, near_next=False): src = m._create_for_insert(i, None, None, near_next) def reset_location(token): token.idx, token.endidx = src.idx, src.endidx @@ -195,13 +206,18 @@ def reset_location(token): m.children.insert(i, existing) m.children[i].parent = m - def erase_child(m, i): - m.children[i].erase() + def append_existing(m, existing, near_next=False): + m.insert_existing(len(m.children), existing, near_next) - def replace_with(m, target): # target must not reference m, but may reference copy(m) + def remove_child(m, i): + del m.children[i] + + def replace_with(m, target): # target must not reference m, but may reference m.copy() or m.move() old_parent = m.parent m.__dict__ = target.__dict__ m.parent = old_parent + for child in m.children: + child.parent = m def erase(m): m.replace_with(Node(None, [])) @@ -244,6 +260,7 @@ def parse(source, tokens, ctxt=None): errors = [] globals = LazyDict(lambda key: Global(key)) members = LazyDict(lambda key: Member(key)) + has_env = False scope.add(Local("_ENV", scope)) @@ -292,7 +309,7 @@ def require_ident(tokens=None): return peek(-1) add_error("identifier expected", fail=True) - def parse_var(token=None, new=None, member=False, label=False, implicit=False): + def parse_var(token=None, new=None, member=False, label=False, implicit=False, const=None): token = token or require_ident() name = token.value @@ -326,15 +343,28 @@ def parse_var(token=None, new=None, member=False, label=False, implicit=False): if not var: kind = VarKind.global_ var = globals[name] + if ctxt and ctxt.consts and name in ctxt.consts: + var.is_const = True + var.constval = ctxt.consts[name] + + if name == "_ENV" and not implicit: + nonlocal has_env + has_env = True if implicit and var: var.implicit = True var_kind = getattr(token, "var_kind", None) - if var and hasattr(token, "keys_kind"): - var.keys_kind = token.keys_kind - if var and hasattr(token, "rename"): - var.rename = token.rename + + if var: + if hasattr(token, "keys_kind"): + var.keys_kind = token.keys_kind + if hasattr(token, "rename"): + var.rename = token.rename + + const = getattr(token, "is_const", const) + if const != None: + var.is_const = const node = Node(NodeType.var, [token], name=name, kind=kind, var_kind=var_kind, var=var, new=bool(new), scope=search_scope) @@ -730,7 +760,8 @@ def parse_local(): tokens = [peek(-1)] newscope = Scope(scope, depth, funcdepth) - targets = parse_list(tokens, lambda: parse_var(new=newscope)) + const = getattr(tokens[0], "is_const", None) + targets = parse_list(tokens, lambda: parse_var(new=newscope, const=const)) sources = [] if accept("=", tokens): sources = parse_list(tokens, parse_expr) @@ -742,8 +773,9 @@ def parse_local(): return Node(NodeType.local, tokens, targets=targets, sources=sources) def mark_reassign(target): + target.assignment = True if target.type == NodeType.var and target.var: - target.var.reassigned = True + target.var.reassigned += 1 def parse_assign(first): tokens = [first] @@ -871,6 +903,7 @@ def parse_root(): root = parse_block() root.globals = globals root.members = members + root.has_env = has_env funcdepth -= 1 @@ -920,4 +953,16 @@ def is_short_block_stmt(node): def is_function_stmt(node): return node.type == NodeType.function and node.target +def get_precedence(node): + if node.type == NodeType.binary_op: + return k_binary_op_precs[node.op] + elif node.type == NodeType.unary_op: + return k_unary_ops_prec + +def is_right_assoc(node): + if node.type == NodeType.binary_op: + return node.op in k_right_binary_ops + else: + return False + from pico_process import Error diff --git a/pico_process.py b/pico_process.py index 33e46f7..166f30b 100644 --- a/pico_process.py +++ b/pico_process.py @@ -94,7 +94,7 @@ def get_source_location(path, text, idx, start_line=0, tabs=False): tab, line, col = None, *get_line_col(text, idx) return SourceLocation(path, tab, line + start_line, col) -class PicoSource: # keep this light - created for temp. tokenizations +class Source: # keep this light - created for temp. tokenizations """A pico8 source file - e.g. either the main one or an included file""" path = text = ... @@ -104,7 +104,7 @@ def __init__(m, path, text): def get_location(m, idx, tabs=False): # (0-based) return get_source_location(m.path, m.text, idx, tabs=tabs) -class CartSource(PicoSource): +class CartSource(Source): """The source of a pico8 cart, maps indexes in the preprocessed code to individual source files""" def __init__(m, cart): m.cart = cart @@ -154,7 +154,8 @@ def rename(m, **_): class PicoContext: """Defines information for how pico8 code is to be processed, e.g. the supported builtins and the supported pico8 version""" def __init__(m, deprecated=True, undocumented=True, patterns=True, srcmap=False, extra_builtins=None, not_builtins=None, - local_builtins=True, extra_local_builtins=None, sublang_getter=None, version=sys.maxsize, hint_comments=True): + local_builtins=True, extra_local_builtins=None, sublang_getter=None, version=sys.maxsize, hint_comments=True, + consts=None): builtins = set(main_builtins) local_builtins = set(builtins_copied_to_locals) if local_builtins else set() if deprecated: @@ -177,6 +178,7 @@ def __init__(m, deprecated=True, undocumented=True, patterns=True, srcmap=False, m.callback_builtins = builtins_with_callbacks m.srcmap = [] if srcmap else None + m.consts = consts m.sublang_getter = sublang_getter m.hint_comments = hint_comments m.version = version @@ -216,7 +218,7 @@ def fixup_process_args(args): return args_set, args def process_code(ctxt, source, input_count=False, count=False, lint=False, minify=False, rename=False, unminify=False, - stop_on_lint=True, fail=True, want_count=True): + stop_on_lint=True, want_count=True): need_lint, lint = fixup_process_args(lint) need_minify, minify = fixup_process_args(minify) need_rename, rename = fixup_process_args(rename) @@ -245,6 +247,8 @@ def process_code(ctxt, source, input_count=False, count=False, lint=False, minif if not errors or not stop_on_lint: if need_minify: + simplify_code(ctxt, root, minify, errors) + if need_rename: rename_tokens(ctxt, root, rename) @@ -257,14 +261,17 @@ def process_code(ctxt, source, input_count=False, count=False, lint=False, minif new_tokens = root.get_tokens() if need_parse else tokens print_token_count(count_tokens(new_tokens), handler=count) - if fail and errors: - throw("\n".join(map(str, errors))) - if e(new_text): source.text = new_text return ok, errors +def simplify_code(ctxt, root, minify, errors): + fold = minify.get("consts", True) + + if fold: + fold_consts(ctxt, root, errors) + def echo_code(code, echo=True): code = from_p8str(code) if echo == True: @@ -278,6 +285,7 @@ def echo_code(code, echo=True): from pico_lint import lint_code from pico_minify import minify_code, minify_needs_comments from pico_unminify import unminify_code +from pico_constfold import fold_consts from pico_rename import rename_tokens # re-export some things for examples/etc. diff --git a/pico_rename.py b/pico_rename.py index a2d6274..cdebd7c 100644 --- a/pico_rename.py +++ b/pico_rename.py @@ -1,8 +1,9 @@ from utils import * from pico_defs import from_p8str -from pico_tokenize import TokenType, Token, is_identifier, keywords, CommentHint +from pico_tokenize import TokenType, is_identifier, keywords, CommentHint +from pico_output import format_string_literal from pico_parse import VarKind, NodeType, VarBase -from pico_minify import format_string_literal, Focus +from pico_minify import Focus import fnmatch global_callbacks = { @@ -180,12 +181,7 @@ def collect_chars(token): if safe_only: preserved_members.default = True # can't reasonably guarantee safety of this - - def check_safety(node): - if node.type == NodeType.var and node.kind != VarKind.member and node.name == "_ENV": - preserved_globals.default = True - - root.traverse_nodes(check_safety) + preserved_globals.default = root.has_env # collect uses of identifier # (e.g. to give priority to more frequently used ones) diff --git a/pico_tokenize.py b/pico_tokenize.py index 8b7b988..845cf58 100644 --- a/pico_tokenize.py +++ b/pico_tokenize.py @@ -34,6 +34,13 @@ def __init__(m): def __str__(m): return repr(m.__dict__) + @property + def source_text(m): + if m.source and m.idx != None and m.endidx != None: + return m.source.text[m.idx:m.endidx] + else: + return None + def find_parent(m, type): parent = m.parent while parent and parent.type != type: @@ -110,48 +117,97 @@ def add_extra_child(m, child): def is_extra_child(m): return hasattr(m, "extra_i") + + def move(m): # create a "destructive" copy - old object is no longer usable unless replaced + cpy = copy(m) + for child in cpy.children: + child.parent = cpy + m.erase() + return cpy + + def copy(m): + cpy = copy(m) + cpy.children = [child.copy() for child in m.children] + for child in cpy.children: + child.parent = cpy + for key, val in cpy.__dict__.items(): + idx = list_find(m.children, val) + if idx >= 0: + cpy.__dict__[key] = cpy.children[idx] + return cpy class TokenType(Enum): number = string = ident = keyword = punct = ... class Token(TokenNodeBase): """A pico8 token, at 'source'.text['idx':'endidx'] (which is equal to its 'value'). Its 'type' is a TokenType. - For number/string tokens, the actual value can be read via parse_fixnum/parse_string_literal + For string tokens, the actual string is 'string_value' + For number tokens, the actual fixnum value is 'fixnum_value' Its children are the comments *before* it, if any.""" def __init__(m, type, value, source, idx, endidx, vline=None, modified=False): super().__init__() m.type, m.value, m.source, m.idx, m.endidx, m.vline, m.modified = type, value, source, idx, endidx, vline, modified - def modify(m, value): + def check(m, expected): + if isinstance(expected, tuple): + assert m.value in expected + else: + assert m.value == expected + + def modify(m, value, expected=None): + if expected != None: + m.check(expected) m.value = value m.modified = True + lazy_property.clear(m, "fixnum_value") + lazy_property.clear(m, "string_value") def erase(m, expected=None): if expected != None: - if isinstance(expected, tuple): - assert m.value in expected - else: - assert m.value == expected + m.check(expected) m.type, m.value, m.modified = None, None, True + @lazy_property + def fixnum_value(m): + return parse_fixnum(m.value) + + @lazy_property + def string_value(m): + return parse_string_literal(m.value) + @classmethod - def dummy(m, source, idx=None, vline=None): + def dummy(cls, source, idx=None, vline=None): if idx is None: idx = len(source.text) if source else 0 vline = sys.maxsize if source else 0 - return Token(None, None, source, idx, idx, vline) + return cls(None, None, source, idx, idx, vline) # note: vline is kept only for initial parsing and is not to be relied upon @classmethod - def synthetic(m, type, value, other, append=False, prepend=False): + def synthetic(cls, type, value, other, append=False, prepend=False): idx = other.endidx if append else other.idx endidx = other.idx if prepend else other.endidx - return Token(type, value, other.source, idx, endidx, modified=True) + return cls(type, value, other.source, idx, endidx, modified=True) Token.none = Token.dummy(None) +class ConstToken(Token): + def __init__(m, type, other, fixnum_value=None, string_value=None, value=None): + super().__init__(type, value, other.source, other.idx, other.endidx, modified=True) + m.fixnum_value = fixnum_value + m.string_value = string_value + if value is None: + lazy_property.clear(m, "value") + + @lazy_property + def value(m): # will not be called if minified normally, so output is suboptimal + if e(m.fixnum_value): + return format_fixnum(m.fixnum_value, sign='') + else: + return format_string_literal(m.string_value, long=False) + class CommentHint(Enum): none = preserve = lint = keep = ... @@ -164,10 +220,7 @@ def __init__(m, hint, hintdata=None, source=None, idx=None, endidx=None): @property def value(m): - if m.source and m.idx != None and m.endidx != None: - return m.source.text[m.idx:m.endidx] - else: - return None + return m.source_text def is_ident_char(ch): return '0' <= ch <= '9' or 'a' <= ch <= 'z' or 'A' <= ch <= 'Z' or ch in ('_', '\x1e', '\x1f') or ch >= '\x80' @@ -181,7 +234,7 @@ def is_identifier(str): class NextTokenMods: def __init__(m): - m.var_kind = m.keys_kind = m.func_kind = m.merge_prev = m.sublang = m.rename = None + m.var_kind = m.keys_kind = m.is_const = m.func_kind = m.merge_prev = m.sublang = m.rename = None m.comments = None def add_comment(m, cmt): @@ -260,6 +313,8 @@ def add_next_mods(token, mods): token.var_kind = mods.var_kind if mods.keys_kind != None: token.keys_kind = mods.keys_kind + if mods.is_const != None: + token.is_const = mods.is_const if mods.func_kind != None: token.func_kind = mods.func_kind if mods.merge_prev != None: @@ -303,6 +358,10 @@ def process_comment(orig_idx, comment, isblock): get_next_mods().keys_kind = False elif comment == "no-merge": get_next_mods().merge_prev = False + elif comment == "const": + get_next_mods().is_const = True + elif comment == "non-const": + get_next_mods().is_const = False elif comment.startswith(k_language_prefix) and not any(ch.isspace() for ch in comment): get_next_mods().sublang = comment[len(k_language_prefix):] elif comment.startswith(k_rename_prefix) and not any(ch.isspace() for ch in comment): @@ -489,10 +548,13 @@ def count_tokens(tokens): def parse_fixnum(origstr): """parse a fixnum from a pico8 string""" str = origstr.lower() - neg = False + neg = bnot = False if str.startswith("-"): str = str[1:] neg = True + elif str.startswith("~"): + str = str[1:] + bnot = True digits = "0123456789" if str.startswith("0x"): @@ -523,6 +585,8 @@ def parse_fixnum(origstr): throw(f"Invalid number: {origstr}") if neg: value = -value + elif bnot: + value = ~value return num_to_fixnum(value) @@ -589,4 +653,5 @@ def parse_string_literal(str): return "".join(litparts) from pico_parse import Node, VarKind +from pico_output import format_fixnum, format_string_literal from pico_process import Error diff --git a/pico_utils.py b/pico_utils.py index 2996790..6a91de3 100644 --- a/pico_utils.py +++ b/pico_utils.py @@ -353,5 +353,5 @@ def measure_p8scii(text, **opts): # see P8sciiMeasurer's ctor for params def bytes_to_string_contents(bytes): """convert a bytes objects to a pico8 string literal, without the surrounding "-s""" - from pico_minify import format_string_literal # already implemented here... + from pico_output import format_string_literal # already implemented here... return format_string_literal(decode_p8str(bytes), long=False, quote='"')[1:-1] diff --git a/run_bbs_tests.py b/run_bbs_tests.py index aa03d08..e6c0a51 100644 --- a/run_bbs_tests.py +++ b/run_bbs_tests.py @@ -221,12 +221,12 @@ def process_output(kind, output): return (cart, get_test_results(), new_cart_input, cart_output, deltas, best_path_for_pico8) def run(focus): - prefix = f"{focus}_" if focus else "" + filename = str(focus) if focus else "normal" input_json = path_join("test_bbs", "input.json") - output_json = path_join("test_bbs", prefix + "output.json") - compare_json = path_join("test_bbs", prefix + "compare.json") - unfocused_json = path_join("test_bbs", "compare.json") if g_opts.compare_unfocused else None + output_json = path_join("test_bbs", "output", filename + ".json") + compare_json = path_join("test_bbs", "compare", filename + ".json") + unfocused_json = path_join("test_bbs", "compare", "normal.json") if g_opts.compare_unfocused else None inputs = try_file_read_json(input_json, {}) outputs = try_file_read_json(output_json, {}) compares = try_file_read_json(compare_json, {}) @@ -263,7 +263,7 @@ def run(pico8=pico8, path=cart_pico8_path): p8_results.append(mt_pool.apply_async(run)) for p8_result in p8_results: - check_run(f"{cart}:p8-run", p8_result.get()) + check_run(f"p8-run", p8_result.get()) file_write_json(input_json, inputs, sort_keys=True, indent=4) file_write_json(output_json, outputs, sort_keys=True, indent=4) @@ -293,6 +293,7 @@ def run_all(): if __name__ == "__main__": init_tests(g_opts) - dir_ensure_exists("test_bbs") + for dir in ("output", "compare"): + dir_ensure_exists(path_join("test_bbs", dir)) run_all() sys.exit(end_tests()) diff --git a/run_tests.py b/run_tests.py index 19669b2..e80855e 100644 --- a/run_tests.py +++ b/run_tests.py @@ -43,7 +43,7 @@ def try_read_dir_contents(dir): def run_test(name, input, output, *args, private=False, check_output=True, from_output=False, read_stdout=False, norm_stdout=nop, exit_code=0, extra_outputs=None, output_reader=try_file_read, - pico8_output_val=None, pico8_output=None, copy_in_to_out=False): + pico8_output_val=None, pico8_output=None, pico8_run=None, copy_in_to_out=False): if g_opts.test: for wanted_test in g_opts.test: if fnmatch.fnmatch(name, wanted_test): @@ -92,8 +92,8 @@ def run_test(name, input, output, *args, private=False, check_output=True, from_ stdouts.append(f"ERROR: Extra file difference: {extra_outpath}, {extra_cmppath}") success = False - if run_success and g_opts.pico8 and not g_opts.no_pico8 and (pico8_output != None or pico8_output_val != None): - if pico8_output_val is None: + if run_success and g_opts.pico8 and not g_opts.no_pico8 and (pico8_output != None or pico8_output_val != None or pico8_run): + if pico8_output_val is None and pico8_output != None: pico8_output_val = file_read_text(path_join(prefix + "test_compare", pico8_output)) for pico8_exe in g_opts.pico8: p8_success, p8_stdout = run_pico8(pico8_exe, outpath, expected_printh=pico8_output_val) @@ -127,31 +127,46 @@ def run_stdout_test(name, input, *args, output=None, **kwargs): run_test(name, input, output, *args, **kwargs, read_stdout=True) def run(): - if run_test("minify", "input.p8", "output.p8", "--minify", + if run_test("minify", "input.p8", "output.p8", "--minify", "--no-minify-consts", "--preserve", "*.preserved_key,preserved_glob,preserving_obj.*", pico8_output="output.p8.printh"): run_test("unminify", "output.p8", "input-un.p8", "--unminify", from_output=True, pico8_output="output.p8.printh") - run_test("semiobfuscate", "input.p8", "output_semiob.p8", "--minify", + run_test("minifysimp", "input.p8", "output-simp.p8", "--minify", + "--preserve", "*.preserved_key,preserved_glob,preserving_obj.*", pico8_output="output.p8.printh") + run_test("semiobfuscate", "input.p8", "output_semiob.p8", "--minify", "--no-minify-consts", "--preserve", "*.*,preserved_glob", "--no-minify-spaces", "--no-minify-lines", pico8_output="output.p8.printh") - run_test("minrename", "input.p8", "output_minrename.p8", "--minify", + run_test("minrename", "input.p8", "output_minrename.p8", "--minify", "--no-minify-consts", "--preserve", "*,*.*", pico8_output="output.p8.printh") - run_test("auto_minrename", "input.p8", "output_minrename-ih.p8", "--minify", "--rename-safe-only", "--ignore-hints") - run_test("auto_minrename-oc", "input.p8", "output_minrename-oc.p8", "--minify", "--rename-safe-only", + run_test("auto_minrename", "input.p8", "output_minrename-ih.p8", "--minify", "--no-minify-consts", "--rename-safe-only", + "--ignore-hints", pico8_output="output.p8.printh") + run_test("auto_minrename-oc", "input.p8", "output_minrename-oc.p8", "--minify", "--no-minify-consts", "--rename-safe-only", "--ignore-hints", "--focus-chars", pico8_output="output.p8.printh") - run_test("auto_minrename-ob", "input.p8", "output_minrename-ob.p8", "--minify", "--rename-safe-only", + run_test("auto_minrename-ob", "input.p8", "output_minrename-ob.p8", "--minify", "--no-minify-consts", "--rename-safe-only", "--ignore-hints", "--focus-compressed", pico8_output="output.p8.printh") - run_test("minminify", "input.p8", "output_min.p8", "--minify-safe-only", "--focus-tokens", + run_test("minminify", "input.p8", "output_min.p8", "--minify-safe-only", "--no-minify-consts", "--focus-tokens", "--ignore-hints", "--no-minify-rename", "--no-minify-lines", pico8_output="output.p8.printh") - run_test("minifytokens", "input.p8", "output_tokens.p8", "--minify", "--focus-tokens", + run_test("minifytokens", "input.p8", "output_tokens.p8", "--minify", "--no-minify-consts", "--focus-tokens", "--no-minify-spaces", "--no-minify-lines", "--no-minify-comments", "--no-minify-rename", pico8_output="output.p8.printh") run_test("nopreserve", "nopreserve.p8", "nopreserve.p8", "--minify", "--no-preserve", "circfill,rectfill", pico8_output_val="yep") - if run_test("test", "test.p8", "test.p8", "--minify", pico8_output_val="DONE"): + run_test("safeonly", "safeonly.p8", "safeonly.p8", "--minify-safe-only") + run_test("const", "const.p8", "const.p8", "--minify", + "--no-minify-spaces", "--no-minify-lines", "--no-minify-comments", "--no-minify-rename", "--no-minify-tokens", + pico8_run=True) + run_test("const2", "const2.p8", "const2.p8", "--minify", + "--no-minify-spaces", "--no-minify-lines", "--no-minify-comments", "--no-minify-rename", "--no-minify-tokens", + pico8_run=True) + run_test("constcl", "constcl.p8", "constcl.p8", "--minify") + run_test("constcl-1", "constcl.p8", "constcl-1.p8", "--minify", "--const", "DEBUG", "true", "--const", "SPEED", "2.5", "--str-const", "VERSION", "v1.2") + run_test("constcl-2", "constcl.p8", "constcl-2.p8", "--minify", "--const", "DEBUG", "true", "--const", "SPEED", "-2.6", "--const", "hero", "~1") + run_test("constmin", "const.p8", "constmin.p8", "--minify", pico8_run=True) + if run_test("test", "test.p8", "test.p8", "--minify", "--no-minify-consts", pico8_output_val="DONE"): run_test("unmintest", "test.p8", "test-un.p8", "--unminify", from_output=True, pico8_output_val="DONE") - run_test("test-ob", "test.p8", "test-ob.p8", "--focus-compressed", "--minify", pico8_output_val="DONE") - run_test("test-oc", "test.p8", "test-oc.p8", "--focus-chars", "--minify", pico8_output_val="DONE") + run_test("test-ob", "test.p8", "test-ob.p8", "--focus-compressed", "--minify", "--no-minify-consts", pico8_output_val="DONE") + run_test("test-oc", "test.p8", "test-oc.p8", "--focus-chars", "--minify", "--no-minify-consts", pico8_output_val="DONE") + run_test("test-simp", "test.p8", "test-simp.p8", "--minify", pico8_output_val="DONE") run_test("globasmemb", "globasmemb.p8", "globasmemb.p8", "--minify", pico8_output_val="OK") if run_test("p82png", "testcvt.p8", "testcvt.png", "--extra-output", "test_output/testcvt.sprites.png", "spritesheet", @@ -161,7 +176,7 @@ def run(): "--label", "test_output/testcvt.label.png", "--title", "a fine title", "--title", "by you", "--merge", "test_output/testcvt.sprites.png", "gfx", "spritesheet", "--merge", "test_input/default2.p8", "map,sfx,music,gff") - run_test("test_png", "test.png", "test.png", "--minify") + run_test("test_png", "test.png", "test.png", "--minify", "--no-minify-consts") run_test("png2p8", "test.png", "testcvt.p8") if run_test("compress", "testcvt.p8", "testtmp.png", "--force-compression", check_output=False): run_test("compress_check", "testtmp.png", "test_post_compress.p8", from_output=True) @@ -192,7 +207,7 @@ def run(): "--script", path_join("test_input", "sublang.py")) run_test("unkform1", "unkform1", "unkform1") run_test("unkform2", "unkform2.png", "unkform2", "--format", "png", "--input-format", "auto") - run_test("mini", "mini.p8", "mini.p8", "--minify", "--no-minify-lines", + run_test("mini", "mini.p8", "mini.p8", "--minify", "--no-minify-lines", "--no-minify-consts", "--builtin", "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z", "--local-builtin", "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z") run_test("tinyrom", "tiny.rom", "tiny.lua") @@ -205,17 +220,19 @@ def run(): run_test("repl-ob", "repl.p8", "repl-ob.p8", "--minify", "--focus-compressed", pico8_output_val="finished") run_test("reformat", "input.p8", "input-reformat.p8", "--unminify", "--unminify-indent", "4") run_test("notnil", "notnil.p8", "notnil.p8", "--minify", pico8_output_val="passed") - run_test("wildcards", "wildcards.p8", "wildcards.p8", "--minify") - run_test("reorder", "reorder.p8", "reorder.p8", "-m", "--focus-tokens", "--no-minify-lines", + run_test("wildcards", "wildcards.p8", "wildcards.p8", "--minify", "--no-minify-consts") + run_test("reorder", "reorder.p8", "reorder.p8", "-m", "--focus-tokens", "--no-minify-lines", "--no-minify-consts", pico8_output="reorder.p8.printh") - run_test("reorder_safe", "reorder.p8", "reorder_safe.p8", "-M", "--focus-tokens", "--no-minify-lines", + run_test("reorder_simp", "reorder.p8", "reorder_simp.p8", "-m", "--focus-tokens", "--no-minify-lines", + pico8_output="reorder.p8.printh") + run_test("reorder_safe", "reorder.p8", "reorder_safe.p8", "-M", "--focus-tokens", "--no-minify-lines", "--no-minify-consts", "--ignore-hints", pico8_output="reorder.p8.printh") - run_test("reorder_safe_2", "reorder.p8", "reorder_safe_2.p8", "-m", "--focus-tokens", "--no-minify-lines", + run_test("reorder_safe_2", "reorder.p8", "reorder_safe_2.p8", "-m", "--focus-tokens", "--no-minify-lines", "--no-minify-consts", "--reorder-safe-only", pico8_output="reorder.p8.printh") - run_test("short", "short.p8", "short.p8", "-m", "--focus-chars", pico8_output_val="K\nK") - run_test("short-lines", "short.p8", "short-lines.p8", "-m", "--no-minify-lines", "--focus-chars", pico8_output_val="K\nK") - run_test("short-spaces", "short.p8", "short-spaces.p8", "-m", "--no-minify-spaces", "--focus-chars", pico8_output_val="K\nK") - run_test("short2", "short2.p8", "short2.p8", "-m", "--focus-compressed", "--no-minify-spaces") + run_test("short", "short.p8", "short.p8", "-m", "--no-minify-consts", "--focus-chars", pico8_output_val="K\nK") + run_test("short-lines", "short.p8", "short-lines.p8", "-m", "--no-minify-consts", "--no-minify-lines", "--focus-chars", pico8_output_val="K\nK") + run_test("short-spaces", "short.p8", "short-spaces.p8", "-m", "--no-minify-consts", "--no-minify-spaces", "--focus-chars", pico8_output_val="K\nK") + run_test("short2", "short2.p8", "short2.p8", "-m", "--no-minify-consts", "--focus-compressed", "--no-minify-spaces") def main(raw_args): global g_opts diff --git a/shrinko8.py b/shrinko8.py index c8ae129..126b7aa 100644 --- a/shrinko8.py +++ b/shrinko8.py @@ -5,9 +5,10 @@ from pico_cart import Cart, CartFormat, read_cart, write_cart, get_bbs_cart_url, merge_cart from pico_export import read_cart_export, read_pod_file, ListOp from pico_tokenize import k_hint_split_re +from pico_constfold import parse_constant, LuaString import argparse -k_version = 'v1.1.3b' +k_version = 'v1.2.0' def SplitBySeps(val): return k_hint_split_re.split(val) @@ -46,6 +47,7 @@ def ParsableCountHandler(prefix, name, size, limit): pgroup.add_argument("-oc", "--focus-chars", action="store_true", help="when minifying, focus on reducing the amount of characters") pgroup.add_argument("-ob", "--focus-compressed", action="store_true", help="when minifying, focus on reducing the code's compressed size") pgroup.add_argument("--no-minify-rename", action="store_true", help="disable variable renaming in minification") +pgroup.add_argument("--no-minify-consts", action="store_true", help="disable constant folding in minification") pgroup.add_argument("--no-minify-spaces", action="store_true", help="disable space removal in minification") pgroup.add_argument("--no-minify-lines", action="store_true", help="disable line removal in minification") pgroup.add_argument("--no-minify-comments", action="store_true", help="disable comment removal in minification (requires --no-minify-spaces)") @@ -57,6 +59,8 @@ def ParsableCountHandler(prefix, name, size, limit): pgroup.add_argument("--rename-members-as-globals", action="store_true", help='rename globals and members the same way (same as --preserve "*=*.*")') pgroup.add_argument("--reorder-safe-only", action="store_true", help="only do statement reordering that's always safe to do (subset of --minify-safe-only)") pgroup.add_argument("--rename-map", help="log renaming of identifiers (from minify step) to this file") +pgroup.add_argument("--const", nargs=2, action="append", metavar=("NAME", "VALUE"), help="define a constant that will be replaced with the VALUE across the entire file") +pgroup.add_argument("--str-const", nargs=2, action="append", metavar=("NAME", "VALUE"), help="same as --const, but the value is interpreted as a string") pgroup = parser.add_argument_group("lint options") pgroup.add_argument("-l", "--lint", action="store_true", help="enable checking the cart for common issues") @@ -217,6 +221,7 @@ def main_inner(raw_args): "tokens": not args.no_minify_tokens, "reorder": not args.no_minify_reorder, "focus": args.focus, + "consts": not args.no_minify_consts, } args.rename = bool(args.minify) and not args.no_minify_rename @@ -235,6 +240,14 @@ def main_inner(raw_args): args.unminify = { "indent": args.unminify_indent } + + if args.const or args.str_const: + if args.const: + args.const = {name: parse_constant(val) or throw(f"cannot parse <{val}>. If it's meant to be a string, try using --str-const instead") + for name, val in args.const} + if args.str_const: + args.const = args.const or {} + args.const.update({name: LuaString(val) for name, val in args.str_const}) args.preproc_cb, args.postproc_cb, args.sublang_cb = None, None, None if args.script: @@ -363,20 +376,21 @@ def handle_processing(args, main_cart, extra_carts): local_builtins=not args.global_builtins_only, extra_local_builtins=args.local_builtin, srcmap=args.rename_map, sublang_getter=args.sublang_cb, version=cart.version_id, - hint_comments=not args.ignore_hints) + hint_comments=not args.ignore_hints, consts=args.const) if args.preproc_cb: args.preproc_cb(cart=cart, src=src, ctxt=ctxt, args=args) ok, errors = process_code(ctxt, src, input_count=args.input_count, count=args.count, lint=args.lint, minify=args.minify, rename=args.rename, unminify=args.unminify, stop_on_lint=not args.no_lint_fail, - fail=False, want_count=not args.no_count_tokenize) + want_count=not args.no_count_tokenize) if errors: + # can be errors, lint warnings, or hint warnings... had_warns = True print("Lint warnings:" if ok else "Compilation errors:") for error in sorted(errors): print(error.format(args.error_format)) - if not ok or not args.no_lint_fail: + if not ok or (args.lint and not args.no_lint_fail): return False, ok if args.rename_map: diff --git a/test_compare/const.p8 b/test_compare/const.p8 new file mode 100644 index 0000000..998a461 --- /dev/null +++ b/test_compare/const.p8 @@ -0,0 +1,355 @@ +pico-8 cartridge // http://www.pico-8.com +version 36 +__lua__ +stop() -- for pure syntax check +-- neg +?0 +?65535 +?1 +?32768 +?32768.00002 +?4660.33779 +?-false +?-nil +-- abs +?1.3 +?65534.70002 +?32768.00002 +?abs(0x8000) +-- flr +?5 +?5 +?5 +?0 +?65531 +?65530 +?65530 +?65535 +?32768 +-- ceil +?5 +?6 +?6 +?1 +?65531 +?65531 +?65531 +?0 +?ceil(0x7fff.0001) +-- add +?3 +?32768.99999 +?.99999 +?"1" + "2" +?me + 1 +?1 + me +-- sub +?65535 +?65535.99999 +?"a" - "a" +-- mul +?4.5 +?3.29998 +?65442.52021 +?5535 +?60001 +?0x4000 * 2 +?32768 +?123 * 456 +?65413 * 456 +?1245 * 4253 +-- div +?4 +?2.4 +?12 / 0 +?0 / 0 +?1008.24614 +?100 / 0.001 +?65535.6154 +?52932.87871 +?65535.25 +?65535.25 +?.75 +-- idiv +?4 +?2 +?65533 +?65533 +?2 +?8 +?65527 +?4 \ 0 +-- mod +?2 +?3 +?12 % 65531 +?65524 % 65531 +?12 % 0 +?2.2 +?1.2 +?.0005 +?.3 +-- eq +?false +?true +?false +?true +?false +?true +?3 == me +-- neq +?true +?false +?true +?true +?true +?false +?"" ~= {} +-- lt +?false +?true +?false +?true +?true +?true +?3 < "4" +-- le +?true +?true +?false +?true +?false +?true +?false <= true +-- gt +?false +?false +?true +?false +?true +?false +?true +?true +-- ge +?true +?false +?true +?false +?true +?true +?true +-- max +?23 +?23.3 +?1 +?max(4) +-- min +?65413 +?3 +?32768 +-- mid +?3 +?3 +?3 +?65533 +?123.456 +-- bnot +?65535.99999 +?65534.99999 +?.99999 +?~true +?~"x" +?65534.8 +?bnot(1,2) +-- band +?544.32935 +?.33777 +?band() +-- bor +?47806.34176 +?3 +?bor(1) +-- bxor +?47262.01241 +?.99999 +?.00002 +-- shl +?20480 +?32768 +?4660.33777 +?0 +?0 +?2330.16889 +?0x1234.5678 << 0.9 +?65520 +-- shr +?.00123 +?.00002 +?65535.99999 +?4660.33777 +?0 +?65535.99999 +?65535.99999 +?0xe468.acf +?65533.5 +-- lshr +?.00123 +?.00002 +?0 +?0 +?0 +?0xe468.acf +?32765.5 +-- rotl +?12288.00008 +?0xa.6 +?0x2.98 +?4 +?4 <<> 0.5 +?2 +-- rotr +?12288.00008 +?0x2.98 +?0xa.6 +?4 +?4 >>< 0.5 +?8 +-- not +?true +?true +?false +?false +-- and +?4 +?nil +?false +? me +?nil +?it and me +?it and 4 +-- or +?true +?3 +?4 +? me +?3 +?it or me +?it or 4 +-- len +?#123 +?0 +?3 +?6 +-- cat +?"12" +?"bla◝ナ\r\n¹\0" +?"12" +?"12" +?"-32768-2" +?"1" .. 2.3 +?"1" .. false +-- misc +?23 +?35 +local a = foo() +?a * 3 + 36 +local function --[[const]] nocrash() end +local --[[const]] f = max() +?8 +printh,tostr=41,42 +-- misc2 +--[[const]] ; ?61.5 +--[[const]] ; ?579 +--[[const]] ssog5 = 456, 789; ?579 +--[[const]] ; ?123 +?nil +foo, foo2 = foo(), foo(); ?123 +--[[const]] foo = foo(); ?579 +--[[const]] ssog14, --[[const]] ssog15 = foo(); ?ssog15 +--[[const]] ssog22 =nil ; ?5 +ssog22=1 +--[[const]] ; ?5 +?nil +local ssog26 ; ?5 +ssog26=1 +; ?5 +?nil +local ssog31; ssog31=ssog31,ssog31; ssog31,ssog31=ssog31; ?nil +-- misc3 +local a0 = foo() ;({}).x=foo() ;({}).x=foo() ;({}).x=foo() +?foo() +;({}).x = 4 +-- if +?true +?false +?true +?false +?true +?nil +?false +?nil +?0 +?false +if foo then ?nil +else ?false +end +?nil +?true +if foo then ?true +else?nil +end +if foo then ?true +else ?false +end +if foo then ?true +else?nil +end +if foo then ?true +end +if foo then ?true +else?nil +end +if foo then ?true +else?0 +end +if foo then ?true +else ?false +end +if foo then ?true +elseif bar then ?1 +else?nil +end +if foo then ?true +elseif bar then ?1 +else ?false +end +if foo then ?true +elseif bar then ?1 +else?nil +end +if foo then ?true +elseif bar then ?1 +end +if foo then ?true +elseif bar then ?1 +else?nil +end +if foo then ?true +elseif bar then ?1 +else?0 +end +if foo then ?true +elseif bar then ?1 +else ?false +end +?"" +-- if misc +?"" +do local a=3end ?a +?a +if foo then --[[non-const]] local a=3 end ?a +?a +do local function a() end end ?a +?a +do do::a::end goto a end ::a:: +do goto b end ::b:: do return end ?3 diff --git a/test_compare/const2.p8 b/test_compare/const2.p8 new file mode 100644 index 0000000..72d28b7 --- /dev/null +++ b/test_compare/const2.p8 @@ -0,0 +1,23 @@ +pico-8 cartridge // http://www.pico-8.com +version 36 +__lua__ +stop() -- for pure syntax check +_ENV["min"]=123 +function x() + function max() + end + local function min() + end + --[[const]] local M,m = max(1,2),min(1,2) + ?M + ?m +end +--[[const]] local M = max(1,2) +?M +?1 +local M,m = max(1,2),min(1,2) +?M +?m +--[[preserve]]ssog1 = 123; ?ssog1 +--[[const]]; ?123 +--[[const]]ssog3 = 123; ssog3 += 1; ?ssog3 diff --git a/test_compare/constcl-1.p8 b/test_compare/constcl-1.p8 new file mode 100644 index 0000000..c735cb8 --- /dev/null +++ b/test_compare/constcl-1.p8 @@ -0,0 +1,8 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ +?"debug version v1.2" +n=0function _update()n+=1.25?n +end +__meta:title__ +[[const]] SPEED = 0.5 -- default value diff --git a/test_compare/constcl-2.p8 b/test_compare/constcl-2.p8 new file mode 100644 index 0000000..c3090fa --- /dev/null +++ b/test_compare/constcl-2.p8 @@ -0,0 +1,8 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ +?"debug version "..(e or"???") +n=0function _update()n+=-1.3?-2 +end +__meta:title__ +[[const]] SPEED = 0.5 -- default value diff --git a/test_compare/constcl.p8 b/test_compare/constcl.p8 new file mode 100644 index 0000000..c45595a --- /dev/null +++ b/test_compare/constcl.p8 @@ -0,0 +1,8 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ +if e then?"debug version "..(i or"???") +end n=0function _update()n+=.25?n +end +__meta:title__ +[[const]] SPEED = 0.5 -- default value diff --git a/test_compare/constmin.p8 b/test_compare/constmin.p8 new file mode 100644 index 0000000..56e194e --- /dev/null +++ b/test_compare/constmin.p8 @@ -0,0 +1,295 @@ +pico-8 cartridge // http://www.pico-8.com +version 36 +__lua__ +stop()?0 +?-1 +?1 +?32768 +?32768.00002 +?4660.33779 +?-false +?-nil +?1.3 +?-1.3 +?32768.00002 +?abs(32768) +?5 +?5 +?5 +?0 +?-5 +?-6 +?-6 +?-1 +?32768 +?5 +?6 +?6 +?1 +?-5 +?-5 +?-5 +?0 +?ceil(32767.00002) +?3 +?~32767 +?.99999 +?"1"+"2" +?e+1 +?1+e +?-1 +?~0 +?"a"-"a" +?4.5 +?3.29998 +?-93.4798 +?5535 +?60001 +?16384*2 +?32768 +?123*456 +?-123*456 +?1245*4253 +?4 +?2.4 +?12/0 +?0/0 +?1008.24614 +?100/.001 +?~.3846 +?52932.87871 +?-.75 +?-.75 +?.75 +?4 +?2 +?-3 +?-3 +?2 +?8 +?-9 +?4\0 +?2 +?3 +?12%-5 +?-12%-5 +?12%0 +?2.2 +?1.2 +?.0005 +?.3 +?false +?true +?false +?true +?false +?true +?3==e +?true +?false +?true +?true +?true +?false +?""~={} +?false +?true +?false +?true +?true +?true +?3<"4" +?true +?true +?false +?true +?false +?true +?false<=true +?false +?false +?true +?false +?true +?false +?true +?true +?true +?false +?true +?false +?true +?true +?true +?23 +?23.3 +?1 +?max(4) +?-123 +?3 +?32768 +?3 +?3 +?3 +?-3 +?123.456 +?~0 +?~1 +?.99999 +?~true +?~"x" +?~1.2 +?bnot(1,2) +?544.32935 +?.33777 +?band() +?47806.34176 +?3 +?bor(1) +?47262.01241 +?.99999 +?.00002 +?20480 +?32768 +?4660.33777 +?0 +?0 +?2330.16889 +?4660.33777<<.9 +?-16 +?.00123 +?.00002 +?~0 +?4660.33777 +?0 +?~0 +?~0 +?0xe468.acf +?-2.5 +?.00123 +?.00002 +?0 +?0 +?0 +?0xe468.acf +?32765.5 +?12288.00008 +?0xa.6 +?0x2.98 +?4 +?4<<>.5 +?2 +?12288.00008 +?0x2.98 +?0xa.6 +?4 +?4>><.5 +?8 +?true +?true +?false +?false +?4 +?nil +?false +?e +?nil +?l and e +?l and 4 +?true +?3 +?4 +?e +?3 +?l or e +?l or 4 +?#123 +?0 +?3 +?6 +?"12" +?"bla◝ナ\r\n¹\0" +?"12" +?"12" +?"-32768-2" +?"1"..2.3 +?"1"..false +?23 +?35 +local e=n()?e*3+36 +local function l()end local l=max()?8 +printh,tostr=41,42?61.5 +?579 +u=456,789?579 +?123 +?nil +n,a=n(),n()?123 +n=n()?579 +i,r=n()?r +s=nil?5 +s=1?5 +?nil +local l?5 +l=1?5 +?nil +local l l=l,l l,l=l?nil +local l=n();({}).e=n();({}).e=n();({}).e=n()?n() +;({}).e=4?true +?false +?true +?false +?true +?nil +?false +?nil +?0 +?false +if n then?nil +else?false +end?nil +?true +if n then?true +else?nil +end if n then?true +else?false +end if n then?true +else?nil +end if n then?true +end if n then?true +else?nil +end if n then?true +else?0 +end if n then?true +else?false +end if n then?true +elseif f then?1 +else?nil +end if n then?true +elseif f then?1 +else?false +end if n then?true +elseif f then?1 +else?nil +end if n then?true +elseif f then?1 +end if n then?true +elseif f then?1 +else?nil +end if n then?true +elseif f then?1 +else?0 +end if n then?true +elseif f then?1 +else?false +end?"" +?"" +do local e=3end?e +?e +if n then local e=3end?e +?e +do local function e()end end?e +?e +do do::e::end goto e end::e::do goto l end::l::do return end?3 +__meta:title__ + +neg diff --git a/test_compare/output-simp.p8 b/test_compare/output-simp.p8 new file mode 100644 index 0000000..4684fab --- /dev/null +++ b/test_compare/output-simp.p8 @@ -0,0 +1,32 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ +print=printh?"hello ᶜ7there♥ら" +🐱,u,h,s,k,y,l,l=11,12,13,14,15,16,17,17t(stat(band()))-- this one comment, i do want! +t()a=0e=0e=0print"this is included"?"#[disable[[this for now/ever]]]" +local l={1,2,3}print(#l)print(22)local l,e="preserved_key",{preserved_key=123}?e[l] +local l="preserved_glob"preserved_glob=123?_ENV[l] +local l={}l["whatever"]=123?l.whatever +function l.subfunc()end function l:subfunc()end?l:subfunc() +local l,e="a",{a=123}?e[l] +local l,e=split"l,e,r,123",{l=123,e=234,r=345}?e[l[2]] +local l="o"o=123?_ENV[l] +local l="l:e#~~r,","!t$u+123-h\nif\ns"do local _ENV={assert=assert}assert(true)end for _ENV in all{{o=1},{o=2}}do o+=1end function some_future_pico8_api()end some_future_pico8_api(1,2,3)local l={preserved1=1,preserved2=2}l.preserved1+=1?l["preserved1"] +l=setmetatable({preserved3=3},c)?l["preserved3"] +n={preserved1=1,preserved2=2}n.preserved1+=1?n["preserved1"] +n=setmetatable({preserved3=3},c)?n["preserved3"] +local l={assert=assert,add=add}do local _ENV=l assert(add({},1)==1)end do local _ENV={assert=assert,add=add}assert(add({},1)==1)end local l for _ENV in all{{o=1,c=5},{o=2,c=6}}do o+=c+c*o l=deli{2}end assert(l==2)local l={key1=1,key2=2,i=3}l.key1=l.i while(false); +while(false)sin=cos cos=sin +local l={1},{1,2,3,4}local l,e=true,1,1.2345,4660,4660.33777,-1,-1.2345,60875.66224,32776,0xf000.f,26214,.00002local e="hi","hello",'"hi"',"'hello'",'"hi"',"'hi'","","","a\nb","\\","\0¹²³⁴⁵⁶","¹²³⁴⁵⁶⁷","\\\\\\\\\\\\","\n\n\n\n\n\n","¹²³⁴⁵⁶]]"local e=[[]],[[hi]],[['hi']],[["'hi'"]],[["""""'''''hi'''''"""""]],[[♥♥♥♥]],[[]],[[ + +]],[==[\\\\\\\\\ + +]]]=]]===]]==]local e=-256,64512,65280^4,256,255.99999if(not l)l=-1 +function r(...)return 1.2 ..4 .. .....0end?r(3) +?1 +?((~(((((((tonum(true)|1)~2)&3)>>1)..1)-(4))*3))^2)^1 +local l=({})[1],(function()end)()local e,n,o,l,c=sin(1,2),cos((cos())),(cos((cos()))),{d=ord,f=pal}local l=ord"123",pal{1,2},l:d("ord"),l:f({1,2}),sin(1)local r={ord"1",[2]=3,o=4,(ord"1")}l+=1e,n=sin(1,2),cos((cos()))o,c=(cos((cos())))function x()return 1,2,ord"1",(ord"1")end while false do end repeat until true for l in(all{})do end print("test"..@16 .."str")?"sh1" +?"sh2" +print"sh3"print"sh4"i="renaming bug"function d()return i end?d() +a=0a=1function new_name(new_name,l)return new_name.new_member,l.new_member end function new_name(new_name2,l,e)return new_name2.new_member end function f(l,e,f,n,o,c)return l+e+f+n+o+c end?f(1,2,4,8,16,32) +v=?"END!" diff --git a/test_compare/output_tokens.p8 b/test_compare/output_tokens.p8 index 2e2342e..ec7b475 100644 --- a/test_compare/output_tokens.p8 +++ b/test_compare/output_tokens.p8 @@ -18,7 +18,8 @@ t() (also, testing comment removal) ]] -x,b=0,0--[[]]b=0 +x,b=0,0--[[]] +b=0 -- include -- no header needed diff --git a/test_compare/reorder_simp.p8 b/test_compare/reorder_simp.p8 new file mode 100644 index 0000000..c481e51 --- /dev/null +++ b/test_compare/reorder_simp.p8 @@ -0,0 +1,69 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ +l=time +do assert(true)printh"124"end +do assert(true)printh"12"end +function d()return 1,2,3end +do local n,o=d(),4,9printh(3 ..n..4)end +do local n,o=d()printh(3 ..n..o..4)end +do local n=d()assert(true)printh(3 ..n..4)end +do local n,o=d()assert(true)printh(3 ..n..o..4)end +do local n=4,9assert(true)printh"34"end +do local n=?"w/e" +assert(n~=nil)printh"34"end +do printh(4)end +do printh(4)end +do printh(3)end +do printh(3)end +do local n=(function()return 1end)()printh(3 ..n)end +do local n=(function()return 3end)()printh(3 ..n)end +do local o,n local function d()return 6end +n,o=3,4n=2n,o=n+5,o+d()printh(n..o) +end +do local o,n local function d()return n end +n=3o=d()printh(n..o) +end +n,o,e=4,5,6printh(n..o..e) +n,o=o-4,2e=n+1printh(n..o..e) +function d()return n end +n=10o=d()printh(n..o) +n,o=11,function()return d()end o=o()printh(n..o) +do +local n +local print=function(o)n=o end +local o=(function()?45 +end)() +printh(n) +end +do +local n,_ENV=printh,{c=13} +local o=c +n(o..c) +end +t={} +t.o,t.d=3,4printh(t.o..t.d) +t.o=3t.o=4printh(t.o) +t.d,t.o=t.o+1,3printh(t.o..t.d) +t.o=5t.d=t["o"]printh(t.o..t.d) +t["a"]=6t.o=7printh(t.o) +a,(printh"one"or{}).e=0,printh"two" +f,(printh"three"or{}).l=0,printh"four" +l() +n,o=sqrt(4),sqrt(9)printh(n..o) +n,o=flr(2.3),flr(3.9)printh(n..o) +function max()return n end +n=4o=max(5,6)printh(n..n) +function u()return n end +n=6o=u()printh(n..n) +r=setmetatable({},{__add=function()return n end})l() +n=20o=r+r printh(n..o) +do +local n,_ENV=printh,setmetatable({o=0},{__newindex=function(n,o,d)rawset(n,o,d+n.o)end}) +o=3d=4n(o..d) +end +do +local n=setmetatable({o=0},{__newindex=function(n,o,d)rawset(n,o,d+n.o)end}) +n.o=7n.d=8printh(n.o..n.d) +end +printh"over..." diff --git a/test_compare/repl-com.png b/test_compare/repl-com.png index 818ad81..af76a96 100644 Binary files a/test_compare/repl-com.png and b/test_compare/repl-com.png differ diff --git a/test_compare/repl-ob.p8 b/test_compare/repl-ob.p8 index 2be1c0c..eee17ed 100644 --- a/test_compare/repl-ob.p8 +++ b/test_compare/repl-ob.p8 @@ -6,7 +6,7 @@ __lua__ -- for the original commented source code -- (The below had the comments stripped due to cart size limits) -------------------------------------- -local e,n,l=_ENV,{},{}for e,t in pairs(_ENV)do n[e]=t if type(t)=="function"then l[e]=true end end local _ENV=n nc,nk=true function p(t,e)for n=1,#e do if sub(e,n,n)==t then return n end end end function b(e,n)return sub(e,n,n)end local n,t,o=split"a,b,f,n,r,t,v,\\,\",',\n,*,#,-,|,+,^",split"⁷,⁸,ᶜ,\n,\r, ,ᵇ,\\,\",',\n,¹,²,³,⁴,⁵,⁶",{}for e=1,#n do o[n[e]]=t[e]end function y(n)return n>="0"and n<="9"end function nl(n)return n>="A"and n<="Z"or n>="a"and n<="z"or n=="_"or n>="█"or y(n)end function en(l,n,i,r)local e=""while n<=#l do local t=b(l,n)if t==i then break end if t=="\\"then n+=1local e=b(l,n)t=o[e]if e=="x"then e=tonum("0x"..sub(l,n+1,n+2))if e then n+=2else r"bad hex escape"end t=chr(e)elseif y(e)then local o=n while y(e)and n=256then r"bad decimal escape"end t=chr(e)elseif e=="z"then repeat n+=1e=b(l,n)until not p(e," \r ᶜᵇ\n")if e==""then r()end t=""n-=1elseif e==""then r()t=""end if not t then r("bad escape: "..e)t=""end elseif t=="\n"then r"unterminated string"break end e..=t n+=1end if n>#l then r("unterminated string",true)end return e,n+1end function nn(e,n,t,l)if b(e,n)=="["then n+=1local l=n while b(e,n)=="="do n+=1end local l="]"..sub(e,l,n-1).."]"local r=#l if b(e,n)=="["then n+=1if b(e,n)=="\n"then n+=1end local o=n while n<=#e and sub(e,n,n+r-1)~=l do n+=1end if n>=#e then t()end return sub(e,o,n-1),n+r end end if l then t"invalid long brackets"end return nil,n end function n4(t,u)local n,a,r,c,s,h,f,o=1,1,{},{},{},{}local function i(n,e)if u then n9(n,o)end f=n and not e end while n<=#t do o=n local e,d,l=b(t,n)if p(e," \r ᶜᵇ\n")then n+=1d=true if e=="\n"then a+=1end elseif e=="-"and b(t,n+1)=="-"then n+=2if b(t,n)=="["then l,n=nn(t,n,i)end if not l then while n<=#t and b(t,n)~="\n"do n+=1end end if u then d=true else add(r,true)end elseif y(e)or e=="."and y(b(t,n+1))then local f,d="0123456789",true if e=="0"and p(b(t,n+1),"xX")then f..="AaBbCcDdEeFf"n+=2elseif e=="0"and p(b(t,n+1),"bB")then f="01"n+=2end while true do e=b(t,n)if e=="."and d then d=false elseif not p(e,f)then break end n+=1end l=sub(t,o,n-1)if not tonum(l)then i"bad number"l="0"end add(r,tonum(l))elseif nl(e)then while nl(b(t,n))do n+=1end add(r,sub(t,o,n-1))elseif e=="'"or e=='"'then l,n=en(t,n+1,e,i)add(r,{t=l})elseif e=="["and p(b(t,n+1),"=[")then l,n=nn(t,n,i,true)add(r,{t=l})else n+=1local l,f,d=unpack(split(sub(t,n,n+2),""))if l==e and f==e and p(e,".>")then n+=2if d=="="and p(e,">")then n+=1end elseif l==e and f~=e and p(e,"<>")and p(f,"<>")then n+=2if d=="="then n+=1end elseif l==e and p(e,".:^<>")then n+=1if f=="="and p(e,".^<>")then n+=1end elseif l=="="and p(e,"+-*/\\%^&|<>=~!")then n+=1elseif p(e,"+-*/\\%^&|<>=~#(){}[];,?@$.:")then else i("bad char: "..e)end add(r,sub(t,o,n-1))end if not d then add(c,a)add(s,o)add(h,n-1)end if f then r[#r],f=false,false end end return r,c,s,h end function nf(t,n)for e=1,#n do if n[e]==t then return e end end end function nu(n)return unpack(n,1,n.n)end function ee(e)local n={}for e,t in next,e do n[e]=t end return n end local n=split"and,break,do,else,elseif,end,false,for,function,goto,if,in,local,nil,not,or,repeat,return,then,true,until,while"ns={}for n in all(n)do ns[n]=true end local function nn(n)return type(n)=="string"and b(n,#n)=="="end nv=split"end,else,elseif,until"function ny(n,ne)local r,q,t=n4(n,true)local n,i,u,x,f,s,h,e,c,m,a,v=1,0,0,{}local function o(e)n9(e,t[n-1]or 1)end local function p(n)return function()return n end end local function _(e)local n=f[e]if n then return function(t)return t[n][e]end else n=f._ENV return function(t)return t[n]._ENV[e]end end end local function nt()local n=f["..."]if not n or n~=v then o"unexpected '...'"end return function(e)return nu(e[n]["..."])end end local function z(e)local n=f[e]if n then return function(t)return t[n],e end else n=f._ENV return function(t)return t[n]._ENV,e end end end local function t(e)local t=r[n]n+=1if t==e then return end if t==nil then o()end o("expected: "..e)end local function d(e)if not e then e=r[n]n+=1end if e==nil then o()end if type(e)=="string"and nl(b(e,1))and not ns[e]then return e end if type(e)=="string"then o("invalid identifier: "..e)end o"identifier expected"end local function l(e)if r[n]==e then n+=1return true end end local function g()f=setmetatable({},{__index=f})i+=1end local function k()f=getmetatable(f).__index i-=1end local function b(l,t)local e,n={},#t for n=1,n-1do e[n]=t[n](l)end if n>0then local t=pack(t[n](l))if t.n~=1then for l=1,t.n do e[n+l-1]=t[l]end n+=t.n-1else e[n]=t[1]end end e.n=n return e end local function w(e)local n={}add(n,(e()))while l","do add(n,(e()))end return n end local function y(r,o,i)local n={}if i then add(n,i)elseif not l")"then while true do add(n,(e()))if l")"then break end t","end end if o then return function(e)local t=r(e)return t[o](t,nu(b(e,n)))end,true,nil,function(e)local t=r(e)return t[o],pack(t,nu(b(e,n)))end else return function(e)return r(e)(nu(b(e,n)))end,true,nil,function(e)return r(e),b(e,n)end end end local function nl()local o,u,c,a={},{},1while not l"}"do a=nil local i,f if l"["then i=e()t"]"t"="f=e()elseif r[n+1]=="="then i=p(d())t"="f=e()else i=p(c)f=e()c+=1a=#o+1end add(o,i)add(u,f)if l"}"then break end if not l";"then t","end end return function(e)local t={}for n=1,#o do if n==a then local l,n=o[n](e),pack(u[n](e))for e=1,n.n do t[l+e-1]=n[e]end else t[o[n](e)]=u[n](e)end end return t end end local function j(s,h)local n,b,e if s then if h then g()n=d()f[n]=i e=z(n)else n={d()}while l"."do add(n,d())end if l":"then add(n,d())b=true end if#n==1then e=z(n[1])else local t=_(n[1])for e=2,#n-1do local l=t t=function(t)return l(t)[n[e]]end end e=function(e)return t(e),n[#n]end end end end local n,r={}if b then add(n,"self")end t"("if not l")"then while true do if l"..."then r=true else add(n,d())end if l")"then break end t","if r then o"unexpected param after '...'"end end end g()for n in all(n)do f[n]=i end if r then f["..."]=i end local l,o,f=x,a,v x,a,v={},u+1,i local i=c()for n in all(x)do n()end x,a,v=l,o,f t"end"k()return function(t)if h then add(t,{})end local l=ee(t)local o=#l local n=function(...)local t,e=pack(...),l if#e~=o then local n={}for t=0,o do n[t]=e[t]end e=n end local l={}for e=1,#n do l[n[e]]=t[e]end if r then l["..."]=pack(unpack(t,#n+1,t.n))end add(e,l)local n=i(e)deli(e)if n then if type(n)=="table"then return nu(n)end return n()end end if s then local e,t=e(t)e[t]=n else return n end end end local function v()local l=r[n]n+=1local n if l==nil then o()end if l=="nil"then return p()end if l=="true"then return p(true)end if l=="false"then return p(false)end if type(l)=="number"then return p(l)end if type(l)=="table"then return p(l.t)end if l=="{"then return nl()end if l=="("then n=e()t")"return function(e)return(n(e))end,true end if l=="-"then n=e(11)return function(e)return-n(e)end end if l=="~"then n=e(11)return function(e)return~n(e)end end if l=="not"then n=e(11)return function(e)return not n(e)end end if l=="#"then n=e(11)return function(e)return#n(e)end end if l=="@"then n=e(11)return function(e)return@n(e)end end if l=="%"then n=e(11)return function(e)return%n(e)end end if l=="$"then n=e(11)return function(e)return$n(e)end end if l=="function"then return j()end if l=="..."then return nt()end if l=="\\"then n=d()return function()return et(n)end,true,function()return el(n)end end if d(l)then return _(l),true,z(l)end o("unexpected token: "..l)end local function z(e,t,l,r)local n if e=="^"and t<=12then n=r(12)return function(e)return l(e)^n(e)end end if e=="*"and t<10then n=r(10)return function(e)return l(e)*n(e)end end if e=="/"and t<10then n=r(10)return function(e)return l(e)/n(e)end end if e=="\\"and t<10then n=r(10)return function(e)return l(e)\n(e)end end if e=="%"and t<10then n=r(10)return function(e)return l(e)%n(e)end end if e=="+"and t<9then n=r(9)return function(e)return l(e)+n(e)end end if e=="-"and t<9then n=r(9)return function(e)return l(e)-n(e)end end if e==".."and t<=8then n=r(8)return function(e)return l(e)..n(e)end end if e=="<<"and t<7then n=r(7)return function(e)return l(e)<>"and t<7then n=r(7)return function(e)return l(e)>>n(e)end end if e==">>>"and t<7then n=r(7)return function(e)return l(e)>>>n(e)end end if e=="<<>"and t<7then n=r(7)return function(e)return l(e)<<>n(e)end end if e==">><"and t<7then n=r(7)return function(e)return l(e)>>"and t<3then n=r(3)return function(e)return l(e)>n(e)end end if e=="<="and t<3then n=r(3)return function(e)return l(e)<=n(e)end end if e==">="and t<3then n=r(3)return function(e)return l(e)>=n(e)end end if e=="=="and t<3then n=r(3)return function(e)return l(e)==n(e)end end if(e=="~="or e=="!=")and t<3then n=r(3)return function(e)return l(e)~=n(e)end end if e=="and"and t<2then n=r(2)return function(e)return l(e)and n(e)end end if e=="or"and t<1then n=r(1)return function(e)return l(e)or n(e)end end end local function nt(u,l,a)local i=r[n]n+=1local o,f if a then if i=="."then o=d()return function(n)return l(n)[o]end,true,function(n)return l(n),o end end if i=="["then o=e()t"]"return function(n)return l(n)[o(n)]end,true,function(n)return l(n),o(n)end end if i=="("then return y(l)end if i=="{"or type(i)=="table"then n-=1f=v()return y(l,nil,f)end if i==":"then o=d()if r[n]=="{"or type(r[n])=="table"then f=v()return y(l,o,f)end t"("return y(l,o)end end local e=z(i,u,l,e)if not e then n-=1end return e end e=function(r)local n,e,t,l=v()while true do local r,o,i,f=nt(r or 0,n,e)if not r then break end n,e,t,l=r,o,i,f end return n,t,l end local function v()local e,n=e()if not n then o"cannot assign to value"end return n end local function nt()local n=w(v)t"="local e=w(e)if#n==1and#e==1then return function(t)local n,l=n[1](t)n[l]=e[1](t)end else return function(t)local l,r={},{}for e=1,#n do local n,e=n[e](t)add(l,n)add(r,e)end local e=b(t,e)for n=#n,1,-1do l[n][r[n]]=e[n]end end end end local function nl(t,l)local r=r[n]n+=1local n=sub(r,1,-2)local n=z(n,0,t,function()return e()end)if not n then o"invalid compound assignment"end return function(e)local t,l=l(e)t[l]=n(e)end end local function nr()if l"function"then return j(true,true)else local n,e=w(d),l"="and w(e)or{}g()for e=1,#n do f[n[e]]=i end if#n==1and#e==1then return function(t)add(t,{[n[1]]=e[1](t)})end else return function(t)local l,r={},b(t,e)for e=1,#n do l[n[e]]=r[e]end add(t,l)end end end end local function z(e)local t=q[n-1]h=function()return t~=q[n]end if not e or h()then o(n<=#r and"bad shorthand"or nil)end end local function q()local r,o,e,n=r[n]=="(",e()if l"then"then e,n=c()if l"else"then n=c()t"end"elseif l"elseif"then n=q()else t"end"end else z(r)e=c()if not h()and l"else"then n=c()end h=nil end return function(t)if o(t)then return e(t)elseif n then return n(t)end end end local function v(...)local n=m m=u+1local e=c(...)m=n return e end local function y(n,e)if n==true then return end return n,e end local function no()local r,o,n=r[n]=="(",e()if l"do"then n=v()t"end"else z(r)n=v()h=nil end return function(e)while o(e)do if stat(1)>=1then na()end local n,e=n(e)if n then return y(n,e)end end end end local function z()local l,r=i,v(true)t"until"local o=e()while i>l do k()end return function(n)repeat if stat(1)>=1then na()end local e,t=r(n)if not e then t=o(n)end while#n>l do deli(n)end if e then return y(e,t)end until t end end local function ni()if r[n+1]=="="then local r=d()t"="local o=e()t","local d,e=e(),l","and e()or p(1)t"do"g()f[r]=i local l=v()t"end"k()return function(n)for e=o(n),d(n),e(n)do if stat(1)>=1then na()end add(n,{[r]=e})local e,t=l(n)deli(n)if e then return y(e,t)end end end else local l=w(d)t"in"local e=w(e)t"do"g()for n in all(l)do f[n]=i end local o=v()t"end"k()return function(n)local e=b(n,e)while true do local r,t={},{e[1](e[2],e[3])}if t[1]==nil then break end e[3]=t[1]for n=1,#l do r[l[n]]=t[n]end if stat(1)>=1then na()end add(n,r)local e,t=o(n)deli(n)if e then return y(e,t)end end end end end local function p()if not m or a and m=1then na()end return function()return n(nu(e))end end else return function(e)return b(e,n)end end end end local function g(e)local n=d()t"::"if s[n]and s[n].e==u then o"label already defined"end s[n]={l=i,e=u,o=e,r=#e}end local function v()local t,e,l,n=d(),s,i add(x,function()n=e[t]if not n then o"label not found"end if a and n.ee and n.r<#n.o then o"goto past local"end end)return function()if stat(1)>=1then na()end return 0,n end end local function d(f)local i=r[n]n+=1if i==";"then return end if i=="do"then local n=c()t"end"return n end if i=="if"then return q()end if i=="while"then return no()end if i=="repeat"then return z()end if i=="for"then return ni()end if i=="break"then return p()end if i=="return"then return m(),true end if i=="local"then return nr()end if i=="goto"then return v()end if i=="::"then return g(f)end if i=="function"and r[n]~="("then return j(true)end if i=="?"then local e,t=_"print",w(e)return function(n)e(n)(nu(b(n,t)))end end n-=1local i,e,f,t=n,e()if l","or l"="then n=i return nt()elseif nn(r[n])then return nl(e,f)elseif u<=1and nc then return function(n)local n=pack(e(n))if not(t and n.n==0)then add(nd,n)end nk=n[1]end else if not t then o"statement has no effect"end return function(n)e(n)end end end c=function(e)s=setmetatable({},{__index=s})s[u]=i u+=1local a,f,o=u,e and 32767or i,{}while n<=#r and not nf(r[n],nv)and not(h and h())do local n,e=d(o)if n then add(o,n)end if e then l";"break end end while i>f do k()end u-=1s=getmetatable(s).__index return function(e)local l,r,t,n=1,#o while l<=r do t,n=o[l](e)if t then if type(t)~="number"then break end if n.e~=a then break end l=n.r while#e>n.l do deli(e)end t,n=nil end l+=1end while#e>f do deli(e)end return t,n end end f=nc and{_ENV=0,_env=0,_=0}or{_ENV=0}local e=c()if n<=#r then o"unexpected end"end for n in all(x)do n()end return function(n)local n=nc and{_ENV=n,_env=n,_=nk}or{_ENV=n}local n=e{[0]=n}if n then return nu(n)end end end nx,n3=10,false local t={["\0"]="000",["ᵉ"]="014",["ᶠ"]="015"}for n,e in pairs(o)do if not p(n,"'\n")then t[e]=n end end function er(n)local e=1while e<=#n do local l=b(n,e)local t=t[l]if t then n=sub(n,1,e-1).."\\"..t..sub(n,e+1)e+=#t end e+=1end return'"'..n..'"'end function eo(n)if type(n)~="string"then return false end if ns[n]then return false end if#n==0or y(b(n,1))then return false end for e=1,#n do if not nl(b(n,e))then return false end end return true end function f(e,t)local n=type(e)if n=="nil"then return"nil"elseif n=="boolean"then return e and"true"or"false"elseif n=="number"then return tostr(e,n3)elseif n=="string"then return er(e)elseif n=="table"and not t then local n,t,r="{",0,0for e,l in next,e do if t==nx then n=n..",<...>"break end if t>0then n=n..","end local l=f(l,1)if e==r+1then n=n..l r=e elseif eo(e)then n=n..e.."="..l else n=n.."["..f(e,1).."]="..l end t+=1end return n.."}"else return"<"..tostr(n)..">"end end function ei(n,e)if e==nil then return n end if not n then n=""end local t=min(21,#e)for t=1,t do if#n>0then n..="\n"end local t=e[t]if type(t)=="table"then local e=""for n=1,t.n do if#e>0then e=e..", "end e=e..f(t[n])end n..=e else n..=t end end local l={}for n=t+1,#e do l[n-t]=e[n]end return n,l end poke(24365,1)cls()h="> "a,x,_="",1,0c,z=1,20w,j={""},1ne=false m,u=0,1nh,n0=true,true s={7,4,3,5,6,8,5,12,14,7,11,5}e.print=function(n,...)if pack(...).n~=0or not nh then return print(n,...)end add(nd,tostr(n))end function n_()poke(24368,1)end function nz()return function()if stat(30)then return stat(31)end end end function nm(l,r)local e,n,t=1,0,0if not l then return e,n,t end while e<=#l do local l=b(l,e)local o=l>="█"if n>=(o and 31or 32)then t+=1n=0end if r then r(e,l,n,t)end if l=="\n"then t+=1n=0else n+=o and 2or 1end e+=1end return e,n,t end function nt(t,l)local n,e=0,0local o,r,t=nm(t,function(t,i,r,o)if l==t then n,e=r,o end end)if l>=o then n,e=r,t end if r>0then t+=1end return n,e,t end function nr(l,r,e)local t,n=1,false local r,o,l=nm(l,function(o,f,i,l)if e==l and r==i and not n then t=o n=true end if(e=l and r or r-1end if o>0then l+=1end return t,l end function n7(n,t,l,e)if type(e)=="function"then nm(n,function(n,r,o,i)print(r,t+o*4,l+i*6,e(n))end)else print(n and"⁶rw"..n,t,l,e)end end function ef(n,r,o)local i,e,f,t=n4(n)local e=1n7(n,r,o,function(r)while e<=#t and t[e]e then u-=1end rectfill(0,u*6,127,(u+1)*6-1,0)end end local function d(n,e)for t=0,2do local l=pget(n+t,e+5)pset(n+t,e+5,l==0and s[12]or 0)end end local function l(r)local l=h..a.." "local o,t,e=nt(l,#h+c)if e>x then n(e-x)elseif e=21then _-=1goto n end local n=n*6rectfill(0,n,127,n+x*6-1,0)if x>21then rectfill(0,126,127,127,0)end ef(l,0,n)print(h,0,n,s[4])if z>=10and r~=false and not v then d(o*4,n+t*6)end end local function f(e)n(1)u-=1print("[enter] ('esc' to abort)",0,u*6,s[3])while true do flip()n_()for n in nz()do if n=="•"then ne=true g=""nd={}return false end if n=="\r"or n=="\n"then m+=e return true end end end end::n::local t,e if nd or g then t,e=nr(g,0,m)if e-m<=20and nd then g,nd=ei(g,nd)t,e=nr(g,0,m)if#nd==0and not v then nd=nil end end end if not v then camera()end if m==0and not v then l(not g)end if g then local r,t=sub(g,t),min(e-m,20)n(t)n7(r,0,(u-t)*6,s[1])if t0or stat(n,...)elseif n==31then if#ni>0then return deli(ni,1)else local n=stat(n,...)if n=="•"then ne=true end return n end else return stat(n,...)end end function ec(n)if _set_fps then _set_fps(n._update60 and 60or 30)end if n._init then n._init()end d=true while true do if _update_buttons then _update_buttons()end if holdframe then holdframe()end if n._update60 then n._update60()elseif n._update then n._update()end if n._draw then n._draw()end flip()no=true na()end d=false end function et(n)if nf(n,{"i","interrupt"})then return nh elseif nf(n,{"f","flip"})then return n0 elseif nf(n,{"r","repl"})then return nc elseif nf(n,{"mi","max_items"})then return nx elseif nf(n,{"h","hex"})then return n3 elseif nf(n,{"cl","colors"})then return s elseif nf(n,{"c","code"})then local n={[0]=a}for e=1,#w-1do n[e]=w[#w-e]end return n elseif nf(n,{"cm","compile"})then return function(n)return eu(n)end elseif nf(n,{"x","exec"})then return function(n,e)n5(n,e)end elseif nf(n,{"v","eval"})then return function(n,e)return n6(n,e)end elseif nf(n,{"p","print"})then return function(n,...)e.print(f(n),...)end elseif nf(n,{"ts","tostr"})then return function(n)return f(n)end elseif nf(n,{"rst","reset"})then run()elseif nf(n,{"run"})then ec(e)else assert(false,"unknown \\-command")end end function el(e)local function t(n)return n and n~=0and true or false end local n if nf(e,{"i","interrupt"})then n=function(n)nh=t(n)end elseif nf(e,{"f","flip"})then n=function(n)n0=t(n)end elseif nf(e,{"r","repl"})then n=function(n)nc=t(n)end elseif nf(e,{"mi","max_items"})then n=function(n)nx=tonum(n)or-1end elseif nf(e,{"h","hex"})then n=function(n)n3=t(n)end elseif nf(e,{"cl","colors"})then n=function(n)s=n end else assert(false,"unknown \\-command assign")end local n={__newindex=function(t,l,e)n(e)end}return setmetatable(n,n),0end np=stat(4)n1,nw=0,false poke(24412,10,2)function k(n)if stat(28,n)then if n~=ng then ng,n1=n,0end return n1==0or n1>=10and n1%2==0elseif ng==n then ng=nil end end function _update()local e=false local function t(t)local e,n,l=nt(h..a,#h+c)if n8 then e=n8 end n+=t if not(n>=0and n0and 100or 0c=max(nr(h..a,n,l)-#h,1)e=true end local function f(n)w[j]=a j+=n a=w[j]if n<0then c=#a+1else c=max(nr(h..a,32,0)-#h,1)local n=b(a,c)if n~=""and n~="\n"then c-=1end end e=true end local function i()if#a>0then if#w>50then del(w,w[1])end w[#w]=a add(w,"")j=#w e=true end end local function d(n)if c+n>0then a=sub(a,1,c+n-1)..sub(a,c+n+1)c+=n e=true end end local function l(n)a=sub(a,1,c-1)..n..sub(a,c)c+=#n e=true end local r,u,n=stat(28,224)or stat(28,228),stat(28,225)or stat(28,229),-1if k(80)then if c>1then c-=1e=true end elseif k(79)then if c<=#a then c+=1e=true end elseif k(82)then if(r or not t(-1))and j>1then f(-1)end elseif k(81)then if(r or not t(1))and j<#w then f(1)end else local t=stat(31)n=ord(t)if t=="•"then if#a==0then extcmd"pause"else nd,n2={}i()end elseif t=="\r"or t=="\n"then if u then l"\n"else nj(a)if not nd then l"\n"else i()end end elseif r and k(40)then nj(a,true)i()elseif t~=""and n>=32and n<154then if nw and n>=128then t=chr(n-63)end l(t)elseif n==193then l"\n"elseif n==192then o(-1)elseif n==196then o(1)elseif n==203then nw=not nw q,nb="shift now selects "..(nw and"punycase"or"symbols"),40elseif k(74)then if r then c=1e=true else o(-1)end elseif k(77)then if r then c=#a+1e=true else o(1)end elseif k(42)then d(-1)elseif k(76)then d(0)end end local t=stat(4)if t~=np or n==213then l(t)np=t end if n==194or n==215then if a~=""and a~=np then np=a printh(a,"@clip")if n==215then a=""c=1end q="press again to put in clipboard"else q=""end end if stat(120)then local n repeat n=serial(2048,24448,128)l(chr(peek(24448,n)))until n==0end if e then z,n8=20end n1+=1n_()end function nq(n,e)local e,t=coresume(cocreate(e))if not e then printh("error #"..n..": "..t)print("error #"..n.."\npico8 broke something again,\nthis cart may not work.\npress any button to ignore")while btnp()==0do flip()end cls()end end nq(1,function()assert(pack(n6"(function (...) return ... end)(1,2,nil,nil)").n==4)end)nq(2,function()assert(n6"function() local temp, temp2 = {max(1,3)}, -20;return temp[1] + temp2; end"()==-17)end)printh"finished"stop()while true do if holdframe then holdframe()end _update()_draw()flip()end +local e,n,l=_ENV,{},{}for e,t in pairs(_ENV)do n[e]=t if type(t)=="function"then l[e]=true end end local _ENV=n nc,nk=true function p(t,e)for n=1,#e do if sub(e,n,n)==t then return n end end end function b(e,n)return sub(e,n,n)end local n,t,o=split"a,b,f,n,r,t,v,\\,\",',\n,*,#,-,|,+,^",split"⁷,⁸,ᶜ,\n,\r, ,ᵇ,\\,\",',\n,¹,²,³,⁴,⁵,⁶",{}for e=1,#n do o[n[e]]=t[e]end function y(n)return n>="0"and n<="9"end function nl(n)return n>="A"and n<="Z"or n>="a"and n<="z"or n=="_"or n>="█"or y(n)end function en(l,n,i,r)local e=""while n<=#l do local t=b(l,n)if t==i then break end if t=="\\"then n+=1local e=b(l,n)t=o[e]if e=="x"then e=tonum("0x"..sub(l,n+1,n+2))if e then n+=2else r"bad hex escape"end t=chr(e)elseif y(e)then local o=n while y(e)and n=256then r"bad decimal escape"end t=chr(e)elseif e=="z"then repeat n+=1e=b(l,n)until not p(e," \r ᶜᵇ\n")if e==""then r()end t=""n-=1elseif e==""then r()t=""end if not t then r("bad escape: "..e)t=""end elseif t=="\n"then r"unterminated string"break end e..=t n+=1end if n>#l then r("unterminated string",true)end return e,n+1end function nn(e,n,t,l)if b(e,n)=="["then n+=1local l=n while b(e,n)=="="do n+=1end local l="]"..sub(e,l,n-1).."]"local r=#l if b(e,n)=="["then n+=1if b(e,n)=="\n"then n+=1end local o=n while n<=#e and sub(e,n,n+r-1)~=l do n+=1end if n>=#e then t()end return sub(e,o,n-1),n+r end end if l then t"invalid long brackets"end return nil,n end function n4(t,u)local n,a,r,c,s,h,f,o=1,1,{},{},{},{}local function i(n,e)if u then n9(n,o)end f=n and not e end while n<=#t do o=n local e,d,l=b(t,n)if p(e," \r ᶜᵇ\n")then n+=1d=true if e=="\n"then a+=1end elseif e=="-"and b(t,n+1)=="-"then n+=2if b(t,n)=="["then l,n=nn(t,n,i)end if not l then while n<=#t and b(t,n)~="\n"do n+=1end end if u then d=true else add(r,true)end elseif y(e)or e=="."and y(b(t,n+1))then local f,d="0123456789",true if e=="0"and p(b(t,n+1),"xX")then f..="AaBbCcDdEeFf"n+=2elseif e=="0"and p(b(t,n+1),"bB")then f="01"n+=2end while true do e=b(t,n)if e=="."and d then d=false elseif not p(e,f)then break end n+=1end l=sub(t,o,n-1)if not tonum(l)then i"bad number"l="0"end add(r,tonum(l))elseif nl(e)then while nl(b(t,n))do n+=1end add(r,sub(t,o,n-1))elseif e=="'"or e=='"'then l,n=en(t,n+1,e,i)add(r,{t=l})elseif e=="["and p(b(t,n+1),"=[")then l,n=nn(t,n,i,true)add(r,{t=l})else n+=1local l,f,d=unpack(split(sub(t,n,n+2),""))if l==e and f==e and p(e,".>")then n+=2if d=="="and p(e,">")then n+=1end elseif l==e and f~=e and p(e,"<>")and p(f,"<>")then n+=2if d=="="then n+=1end elseif l==e and p(e,".:^<>")then n+=1if f=="="and p(e,".^<>")then n+=1end elseif l=="="and p(e,"+-*/\\%^&|<>=~!")then n+=1elseif p(e,"+-*/\\%^&|<>=~#(){}[];,?@$.:")then else i("bad char: "..e)end add(r,sub(t,o,n-1))end if not d then add(c,a)add(s,o)add(h,n-1)end if f then r[#r],f=false,false end end return r,c,s,h end function nf(t,n)for e=1,#n do if n[e]==t then return e end end end function nu(n)return unpack(n,1,n.n)end function ee(e)local n={}for e,t in next,e do n[e]=t end return n end local n=split"and,break,do,else,elseif,end,false,for,function,goto,if,in,local,nil,not,or,repeat,return,then,true,until,while"ns={}for n in all(n)do ns[n]=true end local function nn(n)return type(n)=="string"and b(n,#n)=="="end nv=split"end,else,elseif,until"function ny(n,ne)local r,q,t=n4(n,true)local n,i,u,x,f,s,h,e,c,m,a,v=1,0,0,{}local function o(e)n9(e,t[n-1]or 1)end local function p(n)return function()return n end end local function _(e)local n=f[e]if n then return function(t)return t[n][e]end else n=f._ENV return function(t)return t[n]._ENV[e]end end end local function nt()local n=f["..."]if not n or n~=v then o"unexpected '...'"end return function(e)return nu(e[n]["..."])end end local function z(e)local n=f[e]if n then return function(t)return t[n],e end else n=f._ENV return function(t)return t[n]._ENV,e end end end local function t(e)local t=r[n]n+=1if t==e then return end if t==nil then o()end o("expected: "..e)end local function d(e)if not e then e=r[n]n+=1end if e==nil then o()end if type(e)=="string"and nl(b(e,1))and not ns[e]then return e end if type(e)=="string"then o("invalid identifier: "..e)end o"identifier expected"end local function l(e)if r[n]==e then n+=1return true end end local function g()f=setmetatable({},{__index=f})i+=1end local function k()f=getmetatable(f).__index i-=1end local function b(l,t)local e,n={},#t for n=1,n-1do e[n]=t[n](l)end if n>0then local t=pack(t[n](l))if t.n~=1then for l=1,t.n do e[n+l-1]=t[l]end n+=t.n-1else e[n]=t[1]end end e.n=n return e end local function w(e)local n={}add(n,(e()))while l","do add(n,(e()))end return n end local function y(r,o,i)local n={}if i then add(n,i)elseif not l")"then while true do add(n,(e()))if l")"then break end t","end end if o then return function(e)local t=r(e)return t[o](t,nu(b(e,n)))end,true,nil,function(e)local t=r(e)return t[o],pack(t,nu(b(e,n)))end else return function(e)return r(e)(nu(b(e,n)))end,true,nil,function(e)return r(e),b(e,n)end end end local function nl()local o,u,c,a={},{},1while not l"}"do a=nil local i,f if l"["then i=e()t"]"t"="f=e()elseif r[n+1]=="="then i=p(d())t"="f=e()else i=p(c)f=e()c+=1a=#o+1end add(o,i)add(u,f)if l"}"then break end if not l";"then t","end end return function(e)local t={}for n=1,#o do if n==a then local l,n=o[n](e),pack(u[n](e))for e=1,n.n do t[l+e-1]=n[e]end else t[o[n](e)]=u[n](e)end end return t end end local function j(s,h)local n,b,e if s then if h then g()n=d()f[n]=i e=z(n)else n={d()}while l"."do add(n,d())end if l":"then add(n,d())b=true end if#n==1then e=z(n[1])else local t=_(n[1])for e=2,#n-1do local l=t t=function(t)return l(t)[n[e]]end end e=function(e)return t(e),n[#n]end end end end local n,r={}if b then add(n,"self")end t"("if not l")"then while true do if l"..."then r=true else add(n,d())end if l")"then break end t","if r then o"unexpected param after '...'"end end end g()for n in all(n)do f[n]=i end if r then f["..."]=i end local l,o,f=x,a,v x,a,v={},u+1,i local i=c()for n in all(x)do n()end x,a,v=l,o,f t"end"k()return function(t)if h then add(t,{})end local l=ee(t)local o=#l local n=function(...)local t,e=pack(...),l if#e~=o then local n={}for t=0,o do n[t]=e[t]end e=n end local l={}for e=1,#n do l[n[e]]=t[e]end if r then l["..."]=pack(unpack(t,#n+1,t.n))end add(e,l)local n=i(e)deli(e)if n then if type(n)=="table"then return nu(n)end return n()end end if s then local e,t=e(t)e[t]=n else return n end end end local function v()local l=r[n]n+=1local n if l==nil then o()end if l=="nil"then return p()end if l=="true"then return p(true)end if l=="false"then return p(false)end if type(l)=="number"then return p(l)end if type(l)=="table"then return p(l.t)end if l=="{"then return nl()end if l=="("then n=e()t")"return function(e)return(n(e))end,true end if l=="-"then n=e(11)return function(e)return-n(e)end end if l=="~"then n=e(11)return function(e)return~n(e)end end if l=="not"then n=e(11)return function(e)return not n(e)end end if l=="#"then n=e(11)return function(e)return#n(e)end end if l=="@"then n=e(11)return function(e)return@n(e)end end if l=="%"then n=e(11)return function(e)return%n(e)end end if l=="$"then n=e(11)return function(e)return$n(e)end end if l=="function"then return j()end if l=="..."then return nt()end if l=="\\"then n=d()return function()return et(n)end,true,function()return el(n)end end if d(l)then return _(l),true,z(l)end o("unexpected token: "..l)end local function z(e,t,l,r)local n if e=="^"and t<=12then n=r(12)return function(e)return l(e)^n(e)end end if e=="*"and t<10then n=r(10)return function(e)return l(e)*n(e)end end if e=="/"and t<10then n=r(10)return function(e)return l(e)/n(e)end end if e=="\\"and t<10then n=r(10)return function(e)return l(e)\n(e)end end if e=="%"and t<10then n=r(10)return function(e)return l(e)%n(e)end end if e=="+"and t<9then n=r(9)return function(e)return l(e)+n(e)end end if e=="-"and t<9then n=r(9)return function(e)return l(e)-n(e)end end if e==".."and t<=8then n=r(8)return function(e)return l(e)..n(e)end end if e=="<<"and t<7then n=r(7)return function(e)return l(e)<>"and t<7then n=r(7)return function(e)return l(e)>>n(e)end end if e==">>>"and t<7then n=r(7)return function(e)return l(e)>>>n(e)end end if e=="<<>"and t<7then n=r(7)return function(e)return l(e)<<>n(e)end end if e==">><"and t<7then n=r(7)return function(e)return l(e)>>"and t<3then n=r(3)return function(e)return l(e)>n(e)end end if e=="<="and t<3then n=r(3)return function(e)return l(e)<=n(e)end end if e==">="and t<3then n=r(3)return function(e)return l(e)>=n(e)end end if e=="=="and t<3then n=r(3)return function(e)return l(e)==n(e)end end if(e=="~="or e=="!=")and t<3then n=r(3)return function(e)return l(e)~=n(e)end end if e=="and"and t<2then n=r(2)return function(e)return l(e)and n(e)end end if e=="or"and t<1then n=r(1)return function(e)return l(e)or n(e)end end end local function nt(u,l,a)local i=r[n]n+=1local o,f if a then if i=="."then o=d()return function(n)return l(n)[o]end,true,function(n)return l(n),o end end if i=="["then o=e()t"]"return function(n)return l(n)[o(n)]end,true,function(n)return l(n),o(n)end end if i=="("then return y(l)end if i=="{"or type(i)=="table"then n-=1f=v()return y(l,nil,f)end if i==":"then o=d()if r[n]=="{"or type(r[n])=="table"then f=v()return y(l,o,f)end t"("return y(l,o)end end local e=z(i,u,l,e)if not e then n-=1end return e end e=function(r)local n,e,t,l=v()while true do local r,o,i,f=nt(r or 0,n,e)if not r then break end n,e,t,l=r,o,i,f end return n,t,l end local function v()local e,n=e()if not n then o"cannot assign to value"end return n end local function nt()local n=w(v)t"="local e=w(e)if#n==1and#e==1then return function(t)local n,l=n[1](t)n[l]=e[1](t)end else return function(t)local l,r={},{}for e=1,#n do local n,e=n[e](t)add(l,n)add(r,e)end local e=b(t,e)for n=#n,1,-1do l[n][r[n]]=e[n]end end end end local function nl(t,l)local r=r[n]n+=1local n=sub(r,1,-2)local n=z(n,0,t,function()return e()end)if not n then o"invalid compound assignment"end return function(e)local t,l=l(e)t[l]=n(e)end end local function nr()if l"function"then return j(true,true)else local n,e=w(d),l"="and w(e)or{}g()for e=1,#n do f[n[e]]=i end if#n==1and#e==1then return function(t)add(t,{[n[1]]=e[1](t)})end else return function(t)local l,r={},b(t,e)for e=1,#n do l[n[e]]=r[e]end add(t,l)end end end end local function z(e)local t=q[n-1]h=function()return t~=q[n]end if not e or h()then o(n<=#r and"bad shorthand"or nil)end end local function q()local r,o,e,n=r[n]=="(",e()if l"then"then e,n=c()if l"else"then n=c()t"end"elseif l"elseif"then n=q()else t"end"end else z(r)e=c()if not h()and l"else"then n=c()end h=nil end return function(t)if o(t)then return e(t)elseif n then return n(t)end end end local function v(...)local n=m m=u+1local e=c(...)m=n return e end local function y(n,e)if n==true then return end return n,e end local function no()local r,o,n=r[n]=="(",e()if l"do"then n=v()t"end"else z(r)n=v()h=nil end return function(e)while o(e)do if stat(1)>=1then na()end local n,e=n(e)if n then return y(n,e)end end end end local function z()local l,r=i,v(true)t"until"local o=e()while i>l do k()end return function(n)repeat if stat(1)>=1then na()end local e,t=r(n)if not e then t=o(n)end while#n>l do deli(n)end if e then return y(e,t)end until t end end local function ni()if r[n+1]=="="then local r=d()t"="local o=e()t","local d,e=e(),l","and e()or p(1)t"do"g()f[r]=i local l=v()t"end"k()return function(n)for e=o(n),d(n),e(n)do if stat(1)>=1then na()end add(n,{[r]=e})local e,t=l(n)deli(n)if e then return y(e,t)end end end else local l=w(d)t"in"local e=w(e)t"do"g()for n in all(l)do f[n]=i end local o=v()t"end"k()return function(n)local e=b(n,e)while true do local r,t={},{e[1](e[2],e[3])}if t[1]==nil then break end e[3]=t[1]for n=1,#l do r[l[n]]=t[n]end if stat(1)>=1then na()end add(n,r)local e,t=o(n)deli(n)if e then return y(e,t)end end end end end local function p()if not m or a and m=1then na()end return function()return n(nu(e))end end else return function(e)return b(e,n)end end end end local function g(e)local n=d()t"::"if s[n]and s[n].e==u then o"label already defined"end s[n]={l=i,e=u,o=e,r=#e}end local function v()local t,e,l,n=d(),s,i add(x,function()n=e[t]if not n then o"label not found"end if a and n.ee and n.r<#n.o then o"goto past local"end end)return function()if stat(1)>=1then na()end return 0,n end end local function d(f)local i=r[n]n+=1if i==";"then return end if i=="do"then local n=c()t"end"return n end if i=="if"then return q()end if i=="while"then return no()end if i=="repeat"then return z()end if i=="for"then return ni()end if i=="break"then return p()end if i=="return"then return m(),true end if i=="local"then return nr()end if i=="goto"then return v()end if i=="::"then return g(f)end if i=="function"and r[n]~="("then return j(true)end if i=="?"then local e,t=_"print",w(e)return function(n)e(n)(nu(b(n,t)))end end n-=1local i,e,f,t=n,e()if l","or l"="then n=i return nt()elseif nn(r[n])then return nl(e,f)elseif u<=1and nc then return function(n)local n=pack(e(n))if not(t and n.n==0)then add(nd,n)end nk=n[1]end else if not t then o"statement has no effect"end return function(n)e(n)end end end c=function(e)s=setmetatable({},{__index=s})s[u]=i u+=1local a,f,o=u,e and 32767or i,{}while n<=#r and not nf(r[n],nv)and not(h and h())do local n,e=d(o)if n then add(o,n)end if e then l";"break end end while i>f do k()end u-=1s=getmetatable(s).__index return function(e)local l,r,t,n=1,#o while l<=r do t,n=o[l](e)if t then if type(t)~="number"then break end if n.e~=a then break end l=n.r while#e>n.l do deli(e)end t,n=nil end l+=1end while#e>f do deli(e)end return t,n end end f=nc and{_ENV=0,_env=0,_=0}or{_ENV=0}local e=c()if n<=#r then o"unexpected end"end for n in all(x)do n()end return function(n)local n=nc and{_ENV=n,_env=n,_=nk}or{_ENV=n}local n=e{[0]=n}if n then return nu(n)end end end np,nw=10,false local t={["\0"]="000",["ᵉ"]="014",["ᶠ"]="015"}for n,e in pairs(o)do if not p(n,"'\n")then t[e]=n end end function er(n)local e=1while e<=#n do local l=b(n,e)local t=t[l]if t then n=sub(n,1,e-1).."\\"..t..sub(n,e+1)e+=#t end e+=1end return'"'..n..'"'end function eo(n)if type(n)~="string"then return false end if ns[n]then return false end if#n==0or y(b(n,1))then return false end for e=1,#n do if not nl(b(n,e))then return false end end return true end function f(e,t)local n=type(e)if n=="nil"then return"nil"elseif n=="boolean"then return e and"true"or"false"elseif n=="number"then return tostr(e,nw)elseif n=="string"then return er(e)elseif n=="table"and not t then local n,t,r="{",0,0for e,l in next,e do if t==np then n=n..",<...>"break end if t>0then n=n..","end local l=f(l,1)if e==r+1then n=n..l r=e elseif eo(e)then n=n..e.."="..l else n=n.."["..f(e,1).."]="..l end t+=1end return n.."}"else return"<"..tostr(n)..">"end end function ei(n,e)if e==nil then return n end if not n then n=""end local t=min(21,#e)for t=1,t do if#n>0then n..="\n"end local t=e[t]if type(t)=="table"then local e=""for n=1,t.n do if#e>0then e=e..", "end e=e..f(t[n])end n..=e else n..=t end end local l={}for n=t+1,#e do l[n-t]=e[n]end return n,l end poke(24365,1)cls()h="> "a,x,_="",1,0c,z=1,20w,j={""},1ne=false m,u=0,1nh,n0=true,true s={7,4,3,5,6,8,5,12,14,7,11,5}e.print=function(n,...)if pack(...).n~=0or not nh then return print(n,...)end add(nd,tostr(n))end function n_()poke(24368,1)end function nz()return function()if stat(30)then return stat(31)end end end function nx(l,r)local e,n,t=1,0,0if not l then return e,n,t end while e<=#l do local l=b(l,e)local o=l>="█"if n>=(o and 31or 32)then t+=1n=0end if r then r(e,l,n,t)end if l=="\n"then t+=1n=0else n+=o and 2or 1end e+=1end return e,n,t end function nt(t,l)local n,e=0,0local o,r,t=nx(t,function(t,i,r,o)if l==t then n,e=r,o end end)if l>=o then n,e=r,t end if r>0then t+=1end return n,e,t end function nr(l,r,e)local t,n=1,false local r,o,l=nx(l,function(o,f,i,l)if e==l and r==i and not n then t=o n=true end if(e=l and r or r-1end if o>0then l+=1end return t,l end function n6(n,t,l,e)if type(e)=="function"then nx(n,function(n,r,o,i)print(r,t+o*4,l+i*6,e(n))end)else print(n and"⁶rw"..n,t,l,e)end end function ef(n,r,o)local i,e,f,t=n4(n)local e=1n6(n,r,o,function(r)while e<=#t and t[e]e then u-=1end rectfill(0,u*6,127,(u+1)*6-1,0)end end local function d(n,e)for t=0,2do local l=pget(n+t,e+5)pset(n+t,e+5,l==0and s[12]or 0)end end local function l(r)local l=h..a.." "local o,t,e=nt(l,#h+c)if e>x then n(e-x)elseif e=21then _-=1goto n end local n=n*6rectfill(0,n,127,n+x*6-1,0)if x>21then rectfill(0,126,127,127,0)end ef(l,0,n)print(h,0,n,s[4])if z>=10and r~=false and not v then d(o*4,n+t*6)end end local function f(e)n(1)u-=1print("[enter] ('esc' to abort)",0,u*6,s[3])while true do flip()n_()for n in nz()do if n=="•"then ne=true g=""nd={}return false end if n=="\r"or n=="\n"then m+=e return true end end end end::n::local t,e if nd or g then t,e=nr(g,0,m)if e-m<=20and nd then g,nd=ei(g,nd)t,e=nr(g,0,m)if#nd==0and not v then nd=nil end end end if not v then camera()end if m==0and not v then l(not g)end if g then local r,t=sub(g,t),min(e-m,20)n(t)n6(r,0,(u-t)*6,s[1])if t0or stat(n,...)elseif n==31then if#ni>0then return deli(ni,1)else local n=stat(n,...)if n=="•"then ne=true end return n end else return stat(n,...)end end function ec(n)if _set_fps then _set_fps(n._update60 and 60or 30)end if n._init then n._init()end d=true while true do if _update_buttons then _update_buttons()end if holdframe then holdframe()end if n._update60 then n._update60()elseif n._update then n._update()end if n._draw then n._draw()end flip()no=true na()end d=false end function et(n)if nf(n,{"i","interrupt"})then return nh elseif nf(n,{"f","flip"})then return n0 elseif nf(n,{"r","repl"})then return nc elseif nf(n,{"mi","max_items"})then return np elseif nf(n,{"h","hex"})then return nw elseif nf(n,{"cl","colors"})then return s elseif nf(n,{"c","code"})then local n={[0]=a}for e=1,#w-1do n[e]=w[#w-e]end return n elseif nf(n,{"cm","compile"})then return function(n)return eu(n)end elseif nf(n,{"x","exec"})then return function(n,e)nm(n,e)end elseif nf(n,{"v","eval"})then return function(n,e)return n7(n,e)end elseif nf(n,{"p","print"})then return function(n,...)e.print(f(n),...)end elseif nf(n,{"ts","tostr"})then return function(n)return f(n)end elseif nf(n,{"rst","reset"})then run()elseif nf(n,{"run"})then ec(e)else assert(false,"unknown \\-command")end end function el(e)local function t(n)return n and n~=0and true or false end local n if nf(e,{"i","interrupt"})then n=function(n)nh=t(n)end elseif nf(e,{"f","flip"})then n=function(n)n0=t(n)end elseif nf(e,{"r","repl"})then n=function(n)nc=t(n)end elseif nf(e,{"mi","max_items"})then n=function(n)np=tonum(n)or-1end elseif nf(e,{"h","hex"})then n=function(n)nw=t(n)end elseif nf(e,{"cl","colors"})then n=function(n)s=n end else assert(false,"unknown \\-command assign")end local n={__newindex=function(t,l,e)n(e)end}return setmetatable(n,n),0end nb=stat(4)n1,n3=0,false poke(24412,10,2)function k(n)if stat(28,n)then if n~=ng then ng,n1=n,0end return n1==0or n1>=10and n1%2==0elseif ng==n then ng=nil end end function _update()local e=false local function t(t)local e,n,l=nt(h..a,#h+c)if n8 then e=n8 end n+=t if not(n>=0and n0and 100or 0c=max(nr(h..a,n,l)-#h,1)e=true end local function f(n)w[j]=a j+=n a=w[j]if n<0then c=#a+1else c=max(nr(h..a,32,0)-#h,1)local n=b(a,c)if n~=""and n~="\n"then c-=1end end e=true end local function i()if#a>0then if#w>50then del(w,w[1])end w[#w]=a add(w,"")j=#w e=true end end local function d(n)if c+n>0then a=sub(a,1,c+n-1)..sub(a,c+n+1)c+=n e=true end end local function l(n)a=sub(a,1,c-1)..n..sub(a,c)c+=#n e=true end local r,u,n=stat(28,224)or stat(28,228),stat(28,225)or stat(28,229),-1if k(80)then if c>1then c-=1e=true end elseif k(79)then if c<=#a then c+=1e=true end elseif k(82)then if(r or not t(-1))and j>1then f(-1)end elseif k(81)then if(r or not t(1))and j<#w then f(1)end else local t=stat(31)n=ord(t)if t=="•"then if#a==0then extcmd"pause"else nd,n2={}i()end elseif t=="\r"or t=="\n"then if u then l"\n"else nj(a)if not nd then l"\n"else i()end end elseif r and k(40)then nj(a,true)i()elseif t~=""and n>=32and n<154then if n3 and n>=128then t=chr(n-63)end l(t)elseif n==193then l"\n"elseif n==192then o(-1)elseif n==196then o(1)elseif n==203then n3=not n3 q,n5="shift now selects "..(n3 and"punycase"or"symbols"),40elseif k(74)then if r then c=1e=true else o(-1)end elseif k(77)then if r then c=#a+1e=true else o(1)end elseif k(42)then d(-1)elseif k(76)then d(0)end end local t=stat(4)if t~=nb or n==213then l(t)nb=t end if n==194or n==215then if a~=""and a~=nb then nb=a printh(a,"@clip")if n==215then a=""c=1end q="press again to put in clipboard"else q=""end end if stat(120)then local n repeat n=serial(2048,24448,128)l(chr(peek(24448,n)))until n==0end if e then z,n8=20end n1+=1n_()end function nq(n,e)local e,t=coresume(cocreate(e))if not e then printh("error #"..n..": "..t)print("error #"..n.."\npico8 broke something again,\nthis cart may not work.\npress any button to ignore")while btnp()==0do flip()end cls()end end nq(1,function()assert(pack(n7"(function (...) return ... end)(1,2,nil,nil)").n==4)end)nq(2,function()assert(n7"function() local temp, temp2 = {max(1,3)}, -20;return temp[1] + temp2; end"()==-17)end)printh"finished"stop()while true do if holdframe then holdframe()end _update()_draw()flip()end __meta:title__ keep:------------------------------------ keep: Please see 'Commented Source Code' section in the BBS diff --git a/test_compare/repl-un.p8 b/test_compare/repl-un.p8 index e404411..8a30711 100644 --- a/test_compare/repl-un.p8 +++ b/test_compare/repl-un.p8 @@ -1411,7 +1411,7 @@ function f(e, t) end end -function nb(n, e) +function n5(n, e) if e == nil then return n end @@ -1546,7 +1546,7 @@ function V(n, t, l, e) end end -function np(n, r, o) +function nb(n, r, o) local i, e, f, t = nl(n) local e = 1 V(n, r, o, function(r) @@ -1632,7 +1632,7 @@ function _draw() if x > 21 then rectfill(0, 126, 127, 127, 0) end - np(l, 0, n) + nb(l, 0, n) print(h, 0, n, s[4]) if A >= 10 and r ~= false and not v then d(o * 4, n + t * 6) @@ -1666,7 +1666,7 @@ function _draw() if G or g then t, e = E(g, 0, m) if e - m <= 20 and G then - g, G = nb(g, G) + g, G = n5(g, G) t, e = E(g, 0, m) if #G == 0 and not v then G = nil @@ -1731,7 +1731,7 @@ r, d, F = false, false, false j = {} function nr(n, e) - i, nw = n, e + i, n3 = n, e assert(false, n) end @@ -1743,7 +1743,7 @@ function Y(n, e) return W("return " .. n, e, true) end -function nx(n) +function np(n) local e = cocreate(ni) ::n:: local n, e = coresume(e, n) @@ -1756,7 +1756,7 @@ function nx(n) return n, e end -function n3(n, e) +function nw(n, e) local n, e = C(n, e) return "line " .. e + 1 .. " col " .. n + 1 end @@ -1806,7 +1806,7 @@ function nu(e, l) end end if i then - n, i = i .. "\nat " .. n3(e, nw) + n, i = i .. "\nat " .. nw(e, n3) end O = n j = {} @@ -1850,7 +1850,7 @@ e.stat = function(n, ...) end end -function nm(n) +function nx(n) if _set_fps then _set_fps(n._update60 and 60 or 30) end @@ -1901,7 +1901,7 @@ function ns(n) return n elseif q(n, {"cm", "compile"}) then return function(n) - return nx(n) + return np(n) end elseif q(n, {"x", "exec"}) then return function(n, e) @@ -1922,7 +1922,7 @@ function ns(n) elseif q(n, {"rst", "reset"}) then run() elseif q(n, {"run"}) then - nm(e) + nx(e) else assert(false, "unknown \\-command") end diff --git a/test_compare/repl.map b/test_compare/repl.map index 2a3129e..d26dd68 100644 --- a/test_compare/repl.map +++ b/test_compare/repl.map @@ -68,12 +68,12 @@ global ns <- cmd_exec global nh <- cmd_assign global n0 <- requote global n2 <- is_identifier -global nb <- results_to_str -global np <- str_print_input -global nw <- g_error_idx -global nx <- try_parse -global n3 <- pos_to_str -global nm <- do_mainloop +global n5 <- results_to_str +global nb <- str_print_input +global n3 <- g_error_idx +global np <- try_parse +global nw <- pos_to_str +global nx <- do_mainloop local n <- right local n <- i local n <- ti diff --git a/test_compare/repl.p8 b/test_compare/repl.p8 index 40f4f1e..98036f4 100644 --- a/test_compare/repl.p8 +++ b/test_compare/repl.p8 @@ -171,7 +171,7 @@ if(#n==0or y(b(n,1)))return false for e=1,#n do if(not D(b(n,e)))return false end return true end function f(e,t)local n=type(e)if n=="nil"then return"nil"elseif n=="boolean"then return e and"true"or"false"elseif n=="number"then return tostr(e,T)elseif n=="string"then return n0(e)elseif n=="table"and not t then local n,t,r="{",0,0for e,l in next,e do if(t==S)n=n..",<...>"break if(t>0)n=n.."," -local l=f(l,1)if e==r+1then n=n..l r=e elseif n2(e)then n=n..e.."="..l else n=n.."["..f(e,1).."]="..l end t+=1end return n.."}"else return"<"..tostr(n)..">"end end function nb(n,e)if(e==nil)return n +local l=f(l,1)if e==r+1then n=n..l r=e elseif n2(e)then n=n..e.."="..l else n=n.."["..f(e,1).."]="..l end t+=1end return n.."}"else return"<"..tostr(n)..">"end end function n5(n,e)if(e==nil)return n if(not n)n="" local t=min(21,#e)for t=1,t do if(#n>0)n..="\n" local t=e[t]if type(t)=="table"then local e=""for n=1,t.n do if(#e>0)e=e..", " @@ -189,16 +189,16 @@ if((e=l and r or r-1 if(o>0)l+=1 return t,l end function V(n,t,l,e)if(type(e)=="function")U(n,function(n,r,o,i)print(r,t+o*4,l+i*6,e(n))end)else print(n and"⁶rw"..n,t,l,e) -end function np(n,r,o)local i,e,f,t=nl(n)local e=1V(n,r,o,function(r)while e<=#t and t[e]e)u-=1 rectfill(0,u*6,127,(u+1)*6-1,0)end end local function d(n,e)for t=0,2do local l=pget(n+t,e+5)pset(n+t,e+5,l==0and s[12]or 0)end end local function l(r)local l=h..a.." "local o,t,e=C(l,#h+c)if e>x then n(e-x)elseif e=21)_-=1goto n local n=n*6rectfill(0,n,127,n+x*6-1,0)if(x>21)rectfill(0,126,127,127,0) -np(l,0,n)print(h,0,n,s[4])if(A>=10and r~=false and not v)d(o*4,n+t*6) +nb(l,0,n)print(h,0,n,s[4])if(A>=10and r~=false and not v)d(o*4,n+t*6) end local function f(e)n(1)u-=1print("[enter] ('esc' to abort)",0,u*6,s[3])while true do flip()nf()for n in nd()do if(n=="•")X=true g=""G={}return false if(n=="\r"or n=="\n")m+=e return true -end end end::n::local t,e if G or g then t,e=E(g,0,m)if e-m<=20and G then g,G=nb(g,G)t,e=E(g,0,m)if(#G==0and not v)G=nil +end end end::n::local t,e if G or g then t,e=E(g,0,m)if e-m<=20and G then g,G=n5(g,G)t,e=E(g,0,m)if(#G==0and not v)G=nil end end if(not v)camera() if(m==0and not v)l(not g) if g then local r,t=sub(g,t),min(e-m,20)n(t)V(r,0,(u-t)*6,s[1])if t0or stat(n,...)elseif n==31then if#j>0then return deli(j,1)else local n=stat(n,...)if(n=="•")X=true -return n end else return stat(n,...)end end function nm(n)if(_set_fps)_set_fps(n._update60 and 60or 30) +return n end else return stat(n,...)end end function nx(n)if(_set_fps)_set_fps(n._update60 and 60or 30) if(n._init)n._init() d=true while true do if(_update_buttons)_update_buttons() if(holdframe)holdframe() if n._update60 then n._update60()elseif n._update then n._update()end if(n._draw)n._draw() -flip()F=true I()end d=false end function ns(n)if q(n,{"i","interrupt"})then return M elseif q(n,{"f","flip"})then return N elseif q(n,{"r","repl"})then return J elseif q(n,{"mi","max_items"})then return S elseif q(n,{"h","hex"})then return T elseif q(n,{"cl","colors"})then return s elseif q(n,{"c","code"})then local n={[0]=a}for e=1,#w-1do n[e]=w[#w-e]end return n elseif q(n,{"cm","compile"})then return function(n)return nx(n)end elseif q(n,{"x","exec"})then return function(n,e)W(n,e)end elseif q(n,{"v","eval"})then return function(n,e)return Y(n,e)end elseif q(n,{"p","print"})then return function(n,...)e.print(f(n),...)end elseif q(n,{"ts","tostr"})then return function(n)return f(n)end elseif q(n,{"rst","reset"})then run()elseif q(n,{"run"})then nm(e)else assert(false,"unknown \\-command")end end function nh(e)local function t(n)return n and n~=0and true or false end local n if q(e,{"i","interrupt"})then n=function(n)M=t(n)end elseif q(e,{"f","flip"})then n=function(n)N=t(n)end elseif q(e,{"r","repl"})then n=function(n)J=t(n)end elseif q(e,{"mi","max_items"})then n=function(n)S=tonum(n)or-1end elseif q(e,{"h","hex"})then n=function(n)T=t(n)end elseif q(e,{"cl","colors"})then n=function(n)s=n end else assert(false,"unknown \\-command assign")end local n={__newindex=function(t,l,e)n(e)end}return setmetatable(n,n),0end Q=stat(4)K,R=0,false poke(24412,10,2)function k(n)if stat(28,n)then if(n~=nn)nn,K=n,0 +flip()F=true I()end d=false end function ns(n)if q(n,{"i","interrupt"})then return M elseif q(n,{"f","flip"})then return N elseif q(n,{"r","repl"})then return J elseif q(n,{"mi","max_items"})then return S elseif q(n,{"h","hex"})then return T elseif q(n,{"cl","colors"})then return s elseif q(n,{"c","code"})then local n={[0]=a}for e=1,#w-1do n[e]=w[#w-e]end return n elseif q(n,{"cm","compile"})then return function(n)return np(n)end elseif q(n,{"x","exec"})then return function(n,e)W(n,e)end elseif q(n,{"v","eval"})then return function(n,e)return Y(n,e)end elseif q(n,{"p","print"})then return function(n,...)e.print(f(n),...)end elseif q(n,{"ts","tostr"})then return function(n)return f(n)end elseif q(n,{"rst","reset"})then run()elseif q(n,{"run"})then nx(e)else assert(false,"unknown \\-command")end end function nh(e)local function t(n)return n and n~=0and true or false end local n if q(e,{"i","interrupt"})then n=function(n)M=t(n)end elseif q(e,{"f","flip"})then n=function(n)N=t(n)end elseif q(e,{"r","repl"})then n=function(n)J=t(n)end elseif q(e,{"mi","max_items"})then n=function(n)S=tonum(n)or-1end elseif q(e,{"h","hex"})then n=function(n)T=t(n)end elseif q(e,{"cl","colors"})then n=function(n)s=n end else assert(false,"unknown \\-command assign")end local n={__newindex=function(t,l,e)n(e)end}return setmetatable(n,n),0end Q=stat(4)K,R=0,false poke(24412,10,2)function k(n)if stat(28,n)then if(n~=nn)nn,K=n,0 return K==0or K>=10and K%2==0elseif nn==n then nn=nil end end function _update()local e=false local function t(t)local e,n,l=C(h..a,#h+c)if(ne)e=ne n+=t if(not(n>=0and n0and 100or 0c=max(E(h..a,n,l)-#h,1)e=true end local function f(n)w[z]=a z+=n a=w[z]if n<0then c=#a+1else c=max(E(h..a,32,0)-#h,1)local n=b(a,c)if(n~=""and n~="\n")c-=1 diff --git a/test_compare/safeonly.p8 b/test_compare/safeonly.p8 new file mode 100644 index 0000000..48531a8 --- /dev/null +++ b/test_compare/safeonly.p8 @@ -0,0 +1,4 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ +b=c()a=3?a diff --git a/test_compare/short-spaces.p8 b/test_compare/short-spaces.p8 index f803d4c..b071324 100644 --- a/test_compare/short-spaces.p8 +++ b/test_compare/short-spaces.p8 @@ -2,5 +2,5 @@ pico-8 cartridge // http://www.pico-8.com version 41 __lua__ -if (true) printh"K" else printh"NOPE" -while (true) printh"K" break +if (true) printh"K" else printh"NOPE" +while (true) printh"K" break diff --git a/test_compare/sublang.p8 b/test_compare/sublang.p8 index 0a7a15d..04389ba 100644 --- a/test_compare/sublang.p8 +++ b/test_compare/sublang.p8 @@ -1,7 +1,7 @@ pico-8 cartridge // http://www.pico-8.com version 41 __lua__ -f=123function n()end n[[circfill 50 50 20 7 +f=123function n()_ENV=_ENV end n[[circfill 50 50 20 7 e <- pack rawset e e f rawset e i d]]print(e)print(e.e)n""function i()end i"d=1,f=2,0.5=13,val,f=22,if=bad" diff --git a/test_compare/sublang.txt b/test_compare/sublang.txt index e8e03b3..2ef1f74 100644 --- a/test_compare/sublang.txt +++ b/test_compare/sublang.txt @@ -1,2 +1,2 @@ Lint warnings: -test_input/sublang.p8:4:27: evally: Identifier 'g_same_global' not found +test_input/sublang.p8:6:27: evally: Identifier 'g_same_global' not found diff --git a/test_compare/test-simp.p8 b/test_compare/test-simp.p8 new file mode 100644 index 0000000..1c975e0 --- /dev/null +++ b/test_compare/test-simp.p8 @@ -0,0 +1,170 @@ +pico-8 cartridge // http://www.pico-8.com +version 36 +__lua__ +n=1assert(true,1)assert(n==1,2)assert(true,2.5)assert(true,3)assert(true,4)n=1d=1assert(n==1and d==1,5)assert(true,6)r,f,e=1,{},3r,f.n,f[1],e=e,2,4,r assert(r==3and e==1and f["n"]==2and f[1]==4,8)do local n=r+1assert(n==4,9)local n=n*2assert(n==8,9.1)end assert(r==3,9.2)local n=_ENV assert(n==_ENV,10)assert(true,11)function u()return 1,2,3end local n,d,o,e,t,a=0,u()assert(n==0and d==1and o==2and e==3and t==nil and a==nil,12)function u(...)return...end assert(u(1,2,3)==1,13)r,f=(u(1,2))assert(r==1and f==nil,14)r,f=u(1,2),3assert(r==1and f==3,15)assert(pack(u(1,2,nil,3,nil,nil)).n==6,16)function u(...)return...,...,...end assert(pack(u(1,2,3)).n==5,17)for n=1,3do assert(select(n,u(1,2,3))==1,18)end assert(select(4,u(1,2,3))==2,19)l=0for n=5,1,-2do l=1assert(n==5or n==3or n==1,20)end assert(l==1,20.5)for n=5,1do assert(false,21)end l=0for n,e in ipairs{4,5}do assert(n==1and e==4or n==2and e==5,22)l+=1end assert(l==2,22.5)if(l==2)l+=1else assert(false,23) +assert(l==3,23.5)if l==2then assert(false,24)elseif l==3then l+=1else assert(false,24.5)end assert(l==4,24.6)if(l==2)assert(false,25)else l+=1 +assert(l==5,25.5)if(l==2)assert(false,25.8)else l+=1 +assert(l==6,25.9)if(l==6)l=0l=1else assert(false,26) +assert(l==1,27)if(l==5)assert(false,28)else l=2 +assert(l==2,29)i=1while(l>0)l-=1i*=2 +assert(i==4and l==0,30)while(i>0)i-=1l+=1 +assert(l==4and i==0,31)while l>0do l-=1i+=1if(i==3)break +end assert(l==1and i==3,32)repeat l+=1i-=1until l==1or l==3assert(l==3and i==1,33)function u()return end function m()end assert(u()==nil and pack(u()).n==0,34)assert(m()==nil and pack(m()).n==0,35)function h(...)return...end r={1,2,t=1,u=2,3,4,[12]=4,h(5,6,nil,8)}assert(r[1]==1and r[2]==2and r[3]==3and r[4]==4and r[5]==5and r[6]==6,36)assert(r[7]==nil and r[8]==8and r["t"]==1and r.u==2and r[12]==4,37)function h(...)return{...}end do local function n(...)return{...,t=3}end assert(#n(1,2)==1and n(1,2).t==3,38)end assert(#h(1,2)==2,39)assert(true,40)assert(true,41)assert(-2^4==-16and(-2)^4==16,42)assert(true,43.1)e={a=function(n)return n.d end,d=3}assert(e:a()==3and e.a{d=4}==4,44)setmetatable(e,{__index=function(e,n)return n end})assert(e.i=="i",45)e.o=e function e.o.o.d(n)return n end assert(e.d(false)==false,46)function e.o.o:l(n)return self,n end assert(e:l(true)==e and select(2,e:l(true))==true,47)do n=1do::n::n+=1if(n==4)goto e +goto n end::e::assert(n==4,48)end do::n::do goto n assert(false,49)::n::end end n=0for e,d in next,{5}do assert(e==1and d==5,50)n+=1end assert(n==1,50.5)do local n,_ENV=add,{assert=assert}n(_ENV,3)assert(_ENV[1]==3,51)end local function e(n)_ENV=n end local d=_ENV e{assert=assert,k=123}assert(k==123,52)e(d)function u()return 9,0,1end function x(n)return n()end function D(n)return(n())end assert(pack(x(u)).n==3and pack(D(u)).n==1,53)n=72n-=8n>>>=16assert(n==.00098,54)if n<1then if(n==0)n=123 +else n=321end assert(n==.00098,55)do local n=1function n()end end assert(E==nil,56)n=1repeat until assert(true,57)do repeat until assert(true,57.5)end local function n()return 3end assert(-n()+n()==0,58)local function d()return n end assert(d()()==3,59)local function e(n,d)local o=function()n+=1return n end if(d and d>0)return o,e(n*2,d-1)else return o +end local o,t,a,l=e(10),e(20),e(30,1)assert(o()==11and t()==21and e(0)()==1and a()==31and l()==61and o()==12and t()==22and e(0)()==1and a()==32and l()==62,60)function w(n)return n end assert(w"me"=="me"and w[[me]]=="me",61)b={r=function(e,n)return n end}assert(b:r"me"=="me"and#b:r{}==0,62)do while true do do::n::end goto n end::n::end function O()return 1end assert(O()==1,63)do function N()return 1end assert(N()==1,64)end do local n=1::n::assert(true,65)if(n>1)assert(c()==4and true,66)goto e +local e=3c=function()e+=1return e end n+=1goto n end::e::do local n=1::n::local e=n d=c c=function()e+=1return e end n+=1if(n==3)goto d else goto n +end::d::assert(c()==3and c()==4and d()==2and c()==5and d()==3,67)do goto n::n::end local e=0function ord(n,d)assert(e==n,68)e+=1return d end local e={}ord(0,e).e,ord(1,e).e=ord(2,2),ord(3,function()return 3end)(ord(4,1),ord(5,1))assert(e.e==2,69)local e=2assert(e==2,70)function n(e,n)assert(n==2,71)end n(1,2)p=0g={10,20}function d()p+=1return p end g[d()]+=1assert(g[1]==11and g[2]==20,72)e=0e+=16assert(e==16,73)assert(true,73.5)e=2assert(e==2,74)e=3assert(e==3,74.1)s=1while(s<10)s+=1if(s==5)break +assert(s==5,75)s=3assert(s==3,76)e=4assert(e==4,77)do e=0end e=123assert(e==123,78)do local e,n=print print=function(e)n=e end?1 +assert(n==1,79)print=e end do local n local print=function(e)n=e end?2 +assert(n==2,80)end n=1({e=1}).e=2assert(n==1,81)n={1}({e=1}).e=2assert(n[1]==1,81.1)n="1"({e=1}).e=2assert(n=="1",81.2)n=function()end({e=1}).e=2assert(n()==nil,81.3)printh"DONE" +__gfx__ +dcf6c3968fc4f9d8632d39e67f3953076b48f7b7a87b8f3a1c7d3c647677c0aa345a17500639192c448e3ae773c1ef4b9b8ced74657b1a43f0d23d321a0ad40a +4c49a63747ffeabf17d2d2b36f4ee65c129fbbb1663d2dc2ab0daefb2326aec74f6acf74d9ab50d107f004901073e9cfa89bf6908d49a65e45a6804661ba5179 +__map__ +7abd0b061b85dc7a3d47cae943ce8effc7516254f412bd5cfe0ccd4857697bd16a75fa3860d4b836040c9d134e2c4816dd157b192c7815d386d6a9838d997b68f1082175bcdb96c6dcac0476ff502de82a4c22f317e3e8f3fe2133ce4bdd5d16be563ae2414cbb4f6df414c07c602ff7d6836f48be07f9c16da0b3a334de4848 +__gff__ +aa52cccc84159c6328341bff1000c7ebefa69980293c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +123000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004 +__music__ +07 12345678 +00 00000000 +00 00000000 +__label__ +v0606660600060000660000060600660666060006600000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +6v606000600060006060000060606060606060006060000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +66v06600600060006060000060606060660060006060000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +606v6000600060006060000066606060606060006060000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +6060v660666066606600000066606600606066606660000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v00000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v0000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000v000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0123456789abcde00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__meta:hello__ +hello1 +hello2 +__meta:hello2__ +hello3 +hello4 +__meta:title__ +this tests a good deal of syntax diff --git a/test_input/const.p8 b/test_input/const.p8 new file mode 100644 index 0000000..4fb071a --- /dev/null +++ b/test_input/const.p8 @@ -0,0 +1,329 @@ +pico-8 cartridge // http://www.pico-8.com +version 36 +__lua__ +stop() -- for pure syntax check +-- neg +?-(0) +?-(1) +?-(-1) +?-(0x8000) +?-(0x7fff.ffff) +?-(0xedcb.a987) +?-false +?-nil +-- abs +?abs(1.3) +?abs(-1.3) +?abs(0x8000.0001) +?abs(0x8000) +-- flr +?flr(5) +?flr(5.2) +?flr(5.9) +?flr(0.1) +?flr(-5) +?flr(-5.2) +?flr(-5.9) +?flr(-0.1) +?flr(0x8000.0001) +-- ceil +?ceil(5) +?ceil(5.2) +?ceil(5.9) +?ceil(0.1) +?ceil(-5) +?ceil(-5.2) +?ceil(-5.9) +?ceil(-0.1) +?ceil(0x7fff.0001) +-- add +?1 + 2 +?0x7fff.ffff+1 +?0xffff.ffff+1 +?"1" + "2" +?me + 1 +?1 + me +-- sub +?1 - 2 +?0 - 0x.0001 +?"a" - "a" +-- mul +?3 * 1.5 +?3 * 1.1 +?-12.3 * 7.6 +?123 * 45 +?123 * -45 +?0x4000 * 2 +?-0x4000 * 2 +?123 * 456 +?-123 * 456 +?1245 * 4253 +-- div +?12 / 3 +?12 / 5 +?12 / 0 +?0 / 0 +?1 / 0.001 +?100 / 0.001 +?-1 / 2.6 +?0x8000 / 2.6 +?-3 / 4 +?3 / -4 +?-3 / -4 +-- idiv +?12 \ 3 +?12 \ 5 +?-12 \ 5 +?12 \ -5 +?-12 \ -5 +?10.5 \ 1.3 +?-10.5 \ 1.3 +?4 \ 0 +-- mod +?12 % 5 +?-12 % 5 +?12 % -5 +?-12 % -5 +?12 % 0 +?5.6 % 3.4 +?-5.6 % 3.4 +?12 % 0.3 +?0.3 % 12 +-- eq +?3 == 4 +?3.1 == 3.1 +?3 == "3" +?"3" == "3" +?1 == true +?false == false +?3 == me +-- neq +?3 != 0x3.0001 +?4 ~= 4 +?"04" != "4" +?4 != "4" +?false != true +?nil != nil +?"" ~= {} +-- lt +?3 < 3 +?3 < 4 +?4 < 3 +?-3 < 4 +?"3" < "4" +?"3" < "30" +?3 < "4" +-- le +?3 <= 3 +?3 <= 4 +?4 <= 3 +?-3 <= 4 +?"\xe8" <= "z" +?"3" <= "3" +?false <= true +-- gt +?3 > 3 +?3 > 4 +?4 > 3 +?-3 > 4 +?"3" > "" +?"" > "" +?"3\0" > "3" +?"\x80" > "a" +-- ge +?3 >= 3 +?3 >= 4 +?4 >= 3 +?-3 >= 4 +?"3" >= "" +?"" >= "" +?"\xff" >= "\xfe" +-- max +?max(-123, 23) +?max(23.3, 3) +?max(0x8000, 1) +?max(4) +-- min +?min(-123, 23) +?min(23.3, 3) +?min(0x8000, 1) +-- mid +?mid(3,0,5) +?mid(0,3,5) +?mid(5,3,0) +?mid(-5,-3,0) +?mid(0x8000,0x7fff,123.456) +-- bnot +?~(0) +?~(1) +?~(0xffff) +?~true +?~"x" +?bnot(1.2) +?bnot(1,2) +-- band +?0x1234.5678&0xaaaa.5555 +?band(0x1234.5678,0x.ffff) +?band() +-- bor +?0x1234.5678|0xaaaa.5555 +?bor(1,2) +?bor(1) +-- bxor +?0x1234.5678^^0xaaaa.5555 +?0xffff~0xffff.ffff +?bxor(0x1234.5678, 0x1234.5679) +-- shl +?0x5 << 0xc +?0x.0001 << 31 +?0x1234.5678 << 0 +?0x1234.5678 << 32 +?0x1234.5678 << 0x7fff +?0x1234.5678 << -1 +?0x1234.5678 << 0.9 +?shl(-1, 4) +-- shr +?0x5 >> 0xc +?0x4000 >> 30 +?0x8000 >> 31 +?0x1234.5678 >> 0 +?0x1234.5678 >> 32 +?0xf234.5678 >> 32 +?0xf234.5678 >> 0x7fff +?0xf234.5678 >> -1 +?shr(-0x5,1) +-- lshr +?0x5 >>> 0xc +?0x8000 >>> 31 +?0x1234.5678 >>> 32 +?0xf234.5678 >>> 32 +?0xf234.5678 >>> 0x7fff +?0xf234.5678 >>> -1 +?lshr(-0x5,1) +-- rotl +?0x5.3 <<> 16 +?0x5.3 <<> 33 +?0x5.3 <<> 0x7fff +?4 <<> 0 +?4 <<> 0.5 +?4 <<> -1 +-- rotr +?0x5.3 >>< 16 +?0x5.3 >>< 33 +?0x5.3 >>< 0x7fff +?4 >>< 0 +?4 >>< 0.5 +?4 >>< -1 +-- not +?not nil +?not false +?not true +?not 0 +-- and +?3 and 4 +?nil and 4 +?false and 4 +?3 and me +?nil and me +?it and me +?it and 4 +-- or +?true or 4 +?3 or 4 +?nil or 4 +?nil or me +?3 or me +?it or me +?it or 4 +-- len +?#123 +?#"" +?#"123" +?#"123➡️✽…" +-- cat +?"1" .. "2" +?"bla" .. "\xff\xe0\r\n\1\0" +?1 .. "2" +?"1" .. 2 +?0x8000 .. -2 +?"1" .. 2.3 +?"1" .. false +-- misc +?3 + 4 * 5 +?(3 + 4) * 5 +local a, --[[const]] b = foo(), 3 * 4 +local --[[const]] c = 1 | 2 +?a * c + b * c +local function --[[const]] nocrash() end +local --[[const]] d, --[[const]] e = nil, false +local --[[const]] f = max() +?3 + max(4, 5) +printh,tostr=41,42 +-- misc2 +--[[const]] ssog1 = 123; ?ssog1/2 +--[[const]] ssog2, --[[const]] ssog3 = 123, 456; ?ssog2+ssog3 +--[[const]] ssog4, --[[const]] ssog5 = 123, 456, 789; ?ssog4+ssog5 +--[[const]] ssog6, --[[const]] ssog7 = 123; ?ssog6 +?ssog7 +foo, --[[const]] ssog11, foo2 = foo(), 123, foo(); ?ssog11 +--[[const]] ssog12, foo, --[[const]] ssog13 = 123, foo(), 456; ?ssog12+ssog13 +--[[const]] ssog14, --[[const]] ssog15 = foo(); ?ssog15 +--[[const]] ssog21, --[[const]] ssog22 = max(4,5); ?ssog21 +ssog22=1 +--[[const]] ssog23, --[[const]] ssog24 = max(4,5); ?ssog23 +?ssog24 +local ssog25, ssog26 = max(4,5); ?ssog25 +ssog26=1 +local ssog27, ssog28 = 5; ?ssog27 +?ssog28 +local ssog30, ssog31; ssog31=ssog31,ssog31; ssog31,ssog31=ssog31; ?ssog30 +-- misc3 +local a0, a1 = foo(), 1 +({}).x=foo() +--[[const]]ssogmisc3,({}).x=2,foo() +local a2 = 44 +({}).x=foo() +?foo() +--[[const]]ssoga3, ({}).x = 3, 4 +-- if +if (1==1) ?true else ?false +if (1==2) ?true else ?false +if 1==1 then ?true else ?false end +if 1==2 then ?true else ?false end +if 1==1 then ?true elseif 1==1 then ?nil else ?false end +if 1==2 then ?true elseif 1==1 then ?nil else ?false end +if 1==2 then ?true elseif 1==3 then ?nil else ?false end +if 1==2 then ?true elseif 1==1 then ?nil elseif 1==1 then ?0 else ?false end +if 1==2 then ?true elseif 1==3 then ?nil elseif 1==1 then ?0 else ?false end +if 1==2 then ?true elseif 1==3 then ?nil elseif 1==4 then ?0 else ?false end +if 1==2 then ?true elseif foo then ?nil else ?false end +if 1==2 then ?true elseif 1==1 then ?nil end +if 1==2 then ?true elseif 1==2 then ?nil end +if 1==1 then ?true end +if 1==2 then ?true end +if foo then ?true elseif 1==1 then ?nil else ?false end +if foo then ?true elseif 1==2 then ?nil else ?false end +if foo then ?true elseif 1==1 then ?nil end +if foo then ?true elseif 1==2 then ?nil end +if foo then ?true elseif 1==1 then ?nil elseif 1==1 then ?0 else ?false end +if foo then ?true elseif 1==3 then ?nil elseif 1==1 then ?0 else ?false end +if foo then ?true elseif 1==3 then ?nil elseif 1==4 then ?0 else ?false end +if foo then ?true elseif bar then ?1 elseif 1==1 then ?nil else ?false end +if foo then ?true elseif bar then ?1 elseif 1==2 then ?nil else ?false end +if foo then ?true elseif bar then ?1 elseif 1==1 then ?nil end +if foo then ?true elseif bar then ?1 elseif 1==2 then ?nil end +if foo then ?true elseif bar then ?1 elseif 1==1 then ?nil elseif 1==1 then ?0 else ?false end +if foo then ?true elseif bar then ?1 elseif 1==3 then ?nil elseif 1==1 then ?0 else ?false end +if foo then ?true elseif bar then ?1 elseif 1==3 then ?nil elseif 1==4 then ?0 else ?false end +?"" +-- if misc +?"" +if 1==1 then --[[non-const]] local a=3 end ?a +if 1==2 then --[[non-const]] local a=3 end ?a +if 1==1 then if foo then --[[non-const]] local a=3 end end ?a +if 1==2 then if foo then --[[non-const]] local a=3 end end ?a +if 1==1 then local function a() end end ?a +if 1==2 then local function a() end end ?a +do if 1==1 then ::a:: end goto a end ::a:: +do if 1==2 then ::b:: end goto b end ::b:: +if 1==1 then return end ?3 \ No newline at end of file diff --git a/test_input/const2.p8 b/test_input/const2.p8 new file mode 100644 index 0000000..27e4bff --- /dev/null +++ b/test_input/const2.p8 @@ -0,0 +1,23 @@ +pico-8 cartridge // http://www.pico-8.com +version 36 +__lua__ +stop() -- for pure syntax check +_ENV["min"]=123 +function x() + function max() + end + local function min() + end + --[[const]] local M,m = max(1,2),min(1,2) + ?M + ?m +end +--[[const]] local M,m = max(1,2),min(1,2) +?M +?m +local M,m = max(1,2),min(1,2) +?M +?m +--[[preserve]]ssog1 = 123; ?ssog1 +--[[const]]ssog2 = 123; ?ssog2 +--[[const]]ssog3 = 123; ssog3 += 1; ?ssog3 \ No newline at end of file diff --git a/test_input/constcl.p8 b/test_input/constcl.p8 new file mode 100644 index 0000000..0caf08a --- /dev/null +++ b/test_input/constcl.p8 @@ -0,0 +1,10 @@ +__lua__ +--[[const]] SPEED = 0.5 -- default value +if DEBUG then + ?'debug version ' .. (VERSION or '???') +end +hero = 0 +function _update() + hero += SPEED/2 + ?hero +end \ No newline at end of file diff --git a/test_input/safeonly.p8 b/test_input/safeonly.p8 new file mode 100644 index 0000000..3678a66 --- /dev/null +++ b/test_input/safeonly.p8 @@ -0,0 +1,4 @@ +__lua__ +gnoenv=foo() +gnoenv2=3 +?gnoenv2 \ No newline at end of file diff --git a/test_input/sublang.p8 b/test_input/sublang.p8 index 09ab963..9de7e96 100644 --- a/test_input/sublang.p8 +++ b/test_input/sublang.p8 @@ -1,6 +1,8 @@ __lua__ g_some_global = 123 -function eval() end +function eval() + _ENV = _ENV -- without this, our sublang could never work and shrinko8 could rightly(ish) delete g_some_global=123 above +end eval--[[language::evally]][[ circfill 50 50 20 7 g_another_glob <- pack diff --git a/utils.py b/utils.py index e043b3b..1bd27a9 100644 --- a/utils.py +++ b/utils.py @@ -629,6 +629,10 @@ def __get__(m, obj, cls): @staticmethod def is_set(obj, name): return name in obj.__dict__ + + @staticmethod + def clear(obj, name): + obj.__dict__.pop(name, None) class lazy_classproperty(object): """Method decorator that turns it into a lazily-evaluated class property""" @@ -1444,13 +1448,6 @@ def nop(value): """The identity function""" return value -def find(items, filter): - """Find the first item in 'items' that passes 'filter'""" - for item in items: - if filter(item): - return item - return None - def product(items): """Return the product of 'items'""" result = 1 @@ -1575,6 +1572,13 @@ def list_remove(list, func): del list[i] i -= 1 +def list_find(list, val): + """Find the index of the given element in the list""" + try: + return list.index(val) + except ValueError: + return -1 + def list_find_where(list, func): """Find the index of the first element in a list that matches predicate 'func'""" for i, item in enumerate(list): @@ -2459,6 +2463,11 @@ def count_significant_bits(a): assert a >= 0 return a.bit_length() +def count_leading_zero_bits(a, bits): + """Return how many leading zero bits 'a' has, counting from 'bits'""" + assert a >= 0 + return bits - a.bit_length() + def count_trailing_zero_bits(a): """Return how many trailing zero bits 'a' has""" assert a >= 0