diff --git a/lslopt/lslfoldconst.py b/lslopt/lslfoldconst.py index 511c54e..9e7e8d7 100644 --- a/lslopt/lslfoldconst.py +++ b/lslopt/lslfoldconst.py @@ -1015,9 +1015,10 @@ class foldconst(object): node['SEF'] = True return - if nt in ('JUMP', '@', 'V++', 'V--', '--V', '++V'): - # These all have side effects, as in, can't be eliminated as - # statements. + if nt in ('JUMP', '@', 'V++', 'V--', '--V', '++V', 'LAMBDA'): + # Except LAMBDA, these all have side effects, as in, can't be + # eliminated as statements. + # LAMBDA can't be eliminated without scrolling Loc's. return assert False, 'Internal error: This should not happen, node type = ' \ diff --git a/lslopt/lsloutput.py b/lslopt/lsloutput.py index 2af2661..4867460 100644 --- a/lslopt/lsloutput.py +++ b/lslopt/lsloutput.py @@ -366,6 +366,9 @@ class outscript(object): if nt == 'EXPR': return self.dent() + self.OutExpr(child[0]) + ';\n' + if nt == 'LAMBDA': + return '' + assert False, "Internal error: node type not handled: " + nt # pragma: no cover def output(self, treesymtab, options = ('optsigns','optfloats')): diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index 7d9f833..a55b0fa 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -544,12 +544,14 @@ class parser(object): rotation_literal: '<' expression ',' expression ',' expression ',' expression '>' list_literal: '[' optional_expression_list ']' - assignment: lvalue '=' expression | lvalue '+=' expression + assignment: xlvalue '=' expression | lvalue '+=' expression | lvalue '-=' expression | lvalue '*=' expression | lvalue '/=' expression | lvalue '%=' expression - %EXTENDED RULES: - | lvalue '|=' expression | lvalue '&=' expression - | lvalue '<<=' expression | lvalue '>>=' expression + | lvalue '|=' expression %if extendedassignment + | lvalue '&=' expression %if extendedassignment + | lvalue '<<=' expression %if extendedassignment + | lvalue '>>=' expression %if extendedassignment + xlvalue: lvalue | IDENT '[' expression ']' %if lazylists lvalue: IDENT | IDENT '.' IDENT """ tok0 = self.tok[0] @@ -677,7 +679,21 @@ class parser(object): raise EParseTypeMismatch(self) return {'nt':'V++' if tok0 == '++' else 'V--', 't':lvalue['t'], 'ch':[lvalue]} if AllowAssignment and (tok0 in self.assignment_toks - or self.extendedassignment and tok0 in self.extassignment_toks): + or self.extendedassignment and tok0 in self.extassignment_toks + or self.lazylists and tok0 == '['): + if tok0 == '[': + if lvalue['nt'] != 'IDENT': + raise EParseSyntax(self) + if lvalue['t'] != 'list': + raise EParseTypeMismatch(self) + self.NextToken() + idxexpr = self.Parse_expression() + if idxexpr['t'] != 'integer': + raise EParseTypeMismatch(self) + self.expect(']') + self.NextToken() + self.expect('=') + self.NextToken() expr = self.Parse_expression() rtyp = expr['t'] @@ -690,6 +706,37 @@ class parser(object): if tok0 != '*=' or typ == 'float': expr = self.autocastcheck(expr, typ) rtyp = typ + # Lazy list handler + if tok0 == '[': + # Define aux function if it doesn't exist + # (leaves users room for writing their own replacement, e.g. + # one that fills with something other than zeros) + if 'lazy_list_set' not in self.symtab[0]: + self.PushScope() + paramscope = self.scopeindex + params = (['list', 'integer', 'list'], + ['L', 'i', 'v']) + self.AddSymbol('f', 0, 'lazy_list_set', Loc=self.usedspots, + Type='list', ParamTypes=params[0], ParamNames=params[1]) + self.AddSymbol('v', paramscope, 'L', Type='list') + self.AddSymbol('v', paramscope, 'i', Type='integer') + self.AddSymbol('v', paramscope, 'v', Type='list') + self.PushScope() + localscope = self.scopeindex + self.AddSymbol('v', localscope, 'ins', Type='integer', + Local=True) + # Add body (apologies for the wall of text) + self.tree[self.usedspots] = {'ch': [{'ch': [{'scope':localscope, 'ch': [{'ch': [{'scope':paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'scope':paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'v'}], 'nt': '!=', 't': 'integer'}], 'nt': 'DECL', 't': 'integer', 'name': 'ins'}, {'ch': [{'ch': [{'ch': [{'scope':localscope, 'nt': 'IDENT', 't': 'integer', 'name': 'ins'}], 'nt': '++V', 't': 'integer'}, {'scope':paramscope, 'nt': 'IDENT', 't': 'integer', 'name': 'i'}], 'nt': '<', 't': 'integer'}, {'ch': [{'ch': [{'scope':paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'ch': [{'scope':paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'ch': [{'scope':localscope, 'nt': 'IDENT', 't': 'integer', 'name': 'ins'}, {'scope':localscope, 'nt': 'IDENT', 't': 'integer', 'name': 'ins'}], 'nt': '^', 't': 'integer'}], 'nt': '+', 't': 'list'}], 'nt': '=', 't': 'list'}], 'nt': 'EXPR', 't': 'list'}], 'nt': 'WHILE', 't': None}, {'ch': [{'ch': [{'scope':paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'scope':paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'v'}, {'ch': [{'scope':localscope, 'nt': 'IDENT', 't': 'integer', 'name': 'ins'}, {'scope':paramscope, 'nt': 'IDENT', 't': 'integer', 'name': 'i'}], 'nt': '=', 't': 'integer'}, {'scope':localscope, 'nt': 'IDENT', 't': 'integer', 'name': 'ins'}], 'nt': 'FNCALL', 't': 'list', 'name': 'llListReplaceList'}], 'nt': 'RETURN', 't': None, 'LIR': True}], 'nt': '{}', 't': None, 'LIR': True}], 't': 'list', 'pnames': ['L', 'i', 'v'], 'scope': 0, 'pscope': 1, 'nt': 'FNDEF', 'ptypes': ['list', 'integer', 'list'], 'name': 'lazy_list_set'} + self.usedspots += 1 + self.PopScope() + self.PopScope() + + return {'nt':'=', 't':'list', 'ch':[lvalue, { + 'nt':'FNCALL', 't':'list', 'name':'lazy_list_set', + 'ch':[lvalue.copy(), idxexpr, + {'nt':'LIST','t':'list', 'ch':[expr]}] + }]} + # Lots of drama for checking types. This is pretty much like # addition, subtraction, multiply, divide, etc. all in one go. if tok0 == '=': @@ -1825,8 +1872,8 @@ class parser(object): # TODO: Enable switch statements. #self.enableswitch = 'enableswitch' in options - # TODO: Enable brackets for list elements e.g. (float)mylist[3], or mylist[5]=4 - #self.lazylists = 'lazylists' in options + # Allow brackets for assignment of list elements e.g. mylist[5]=4 + self.lazylists = 'lazylists' in options # TODO: Enable break/continue #self.breakcont = 'breakcont' in options @@ -1889,7 +1936,9 @@ class parser(object): self.pos = 0 self.tok = self.GetToken() - self.tree = [] + # Reserve spots at the beginning for functions we add + self.tree = [{'nt':'LAMBDA','t':None}] + self.usedspots = 0 # Start the parsing proper self.Parse_script() diff --git a/main.py b/main.py index 7bf6a80..8d0117b 100644 --- a/main.py +++ b/main.py @@ -42,14 +42,14 @@ Options (+ means active by default, - means inactive by default): directives like: # 123 "filename". optimize + Runs the optimizer. optsigns + Optimize signs in float and integer constants. - optfloats + Optimize a float when it is an integral value. - constfold + Fold constant expressions to their values. - foldtabs - Tabs can't be copy-pasted, so they aren't optimized by - default. But with support from the viewer, they can be - folded too and make it to the uploaded source. This - option overrides that check, enabling optimization of - strings with tabs. The resulting source isn't guaranteed - to be copy-paste-able to a different script, though. + optfloats + Optimize floats that represent an integral value. + constfold + Fold constant expressions to their values, and simplify + some expressions. + foldtabs - Tabs can't be copy-pasted, so expressions that produce + tabs (like llUnescapeURL("%09") aren't optimized by + default. This option overrides that check, enabling + optimization of strings with tabs. The resulting source + isn't guaranteed to be copy-paste-able to the viewer. duplabels - Normally, a duplicate label within a function is allowed by the syntax by using {} blocks; however, the server will just refuse to save the script (under Mono) or do @@ -61,6 +61,9 @@ Options (+ means active by default, - means inactive by default): process, it turns the script into unreadable gibberish, hard to debug, but this gets big savings for complex scripts. + lazylists - Support syntax like mylist[index] = 5; rather than using + llListReplaceList. Not recommended, as it adds a new + function. ''' % sys.argv[0]) return 1