Float addition is commutative. Swap the constant to the left side if it's not there. This is a "cheap" version of a bigger change that is planned, to minimize stack usage as much as possible on savepoints.
Float addition is not associative, therefore we don't optimize e.g. 1 + (2 + float).
Integer addition is commutative and associative. If there's a constant, we swap it to the left side. If there's a chained summation of the form const + (const + expression), we apply associativity to turn it into (const + const) + expression and reoptimize.
This doesn't cover all possible cases. Expressions of the form (const + expr) + (const + expr) are not optimized. We need to flatten sums if we want to do the right thing here, but that's not yet implemented.
Get rid of some older code and TODOs that are no longer needed.
The missing bit was to mark labels are SEF when they are not referenced. Label references are now counted at parse time, enabling us to do this.
Also, make FoldStmt clearer when the node is an expression.
This extremely uncommon coding pattern was becoming a hell to support. It has caused many bugs in past that need them being treated as special cases.
Getting rid of the possibility entirely seems like the best approach.
It's still supported if the code is not to be optimized (e.g. with --pretty).
That was long overdue. Obviously, this is a large commit.
The new nr (node record) class has built-in dump capabilities, rather than using print_node().
SEF always exists now, and is a boolean, rather than using the existence of SEF as the flag. This was changed for sanity. However, other flags like 'X' are still possibly absent, and in some cases the absence itself has meaning (in the case of 'X', its absence means that the node has not yet been analyzed).
Similarly, an event is distinguished from a UDF by checking for the existence of the 'scope' attribute. This trick works because events are not in the symbol table therefore they have no scope. But this should probably be changed in future to something more rational and faster.
A few minor bugfixes were applied while going through the code.
- Some tabs used as Unicode were written as byte strings. Add the u'\t' prefix.
- After simplifying a%1 -> a&0, fold again the node and return. It's not clear why it didn't return, and whether it depended on subsequent passes (e.g. after DCR) for possibly optimizing out the result. Now we're sure.
- A few places lacked a SEF declaration.
- Formatting changes to split lines that spilled the margin.
- Some comment changes.
- Expanded lazy_list_set definition while adapting it to object format. The plan was to re-compress it after done, but decided to leave it in expanded form.
- Added a few TODOs & FIXMEs, resisting the temptation to fix them in the same commit:
- TODO: ~-~-~-expr -> expr + -3.
- FIXME: Now that we have CompareTrees, we can easily check if expr + -expr cancels out and remove a TODO. Low-hanging fruit.
- TODO: Check what we can do when comparing non-SEF and non-CONST values in '>' (current code relies on converting '>' to '<' for applying more optimizations, but that may miss some opportunities).
- FIXME: Could remove one comparison in nt == '&&' or nt == '||'. Low-hanging fruit.
-a == -b -> a == b
If both a and b either are constants or have a minus sign, negate both.
!(a - b) can be optimized to a == b.
!(a + b) can be optimized to -a == b, relying on the first optimization to remove redundant minus signs.
int != int was not properly optimized, because the != was transformed into the equivalent !(int == int) at an earlier stage. Fixed.
!(a ^ b) can be optimized to a == b, so do it.
097c054 introduced a bug that we hadn't caught until now.
In some occasions, it could swap nested conditions in such a way that the 'else' of the outer statement was made to belong to the inner one, like this:
if (a)
if (b)
stuff;
else
stuff;
That is of course parsed with the 'else' belonging to if(b).
Fix implemented at output time, by detecting 'if(a) stmt; else y;' with stmt being an 'if' without 'else', and wrapping the stmt in {} like this: 'if(a){if(b) x;} else y;'. This has some similarity with parenthesis addition.
But the fix has the corner case that, since {} hides visibility of labels, when the inner 'if' has a label as direct child, it can't be swapped lest the label becomes out of scope. So these cases are detected and skipped in the constant folding module.
In the case of 'if(cond);', we transform it to 'cond;', but we forgot to wrap the cond in an EXPR node as required. Fixed too.
Reorganize into different statements with early return.
Add constants, unary operators and binary operators. Check if operator is commutative and check with operands swapped when so.
Constant equality is somewhat sketchy at the moment: just compare the values with Python's ==.
This has been a TODO item for long. Now that we have lsllastpass, it's actually easy to implement.
Adds an LSLTypeDefaults dictionary to lslcommon, just in case the state-changing function returns a value and we need to insert a return statement.
We've also added subtree-local info to lsllastpass (lost when we return to the parent after visiting a subtree).
This fixes a bug where naked switch statements could appear as a result of optimization, and cause the compilation to fail.
if (!cond) X; else Y; -> if (cond) Y; else X;
if (int1 == int2) X; else Y; -> if (int1 ^ int2) Y; else X;
When 'cond' is of a type other than 'key': if (cond) ; else X; -> if (!cond) X; (this required changing if(str) to its compiled equivalent if(!(str == "")), so that 'cond' is always either key or integer).
if (cond) ; -> cond; and folds it as a statement, which may eliminate it if it's SEF. This is done after eliminating 'else ;' so that it also optimizes 'if (cond) ; else ;' the same way.
This removes a TODO item.
Allows detection of empty events, for example, even if they have labels.
Also, it is OK if there's a label inserted in a nested {}; that case wasn't contemplated.
The comment was wrong anyway. If one side changes x and the other side uses x, then order is still important, no matter whether one side is SEF.
But the reversal is safe when one side is a constant, so we still perform it, to enable optimization of some important cases.
For floats:
When const >= function.max, comparisons of function > const always yield FALSE.
When const < function.min, comparisons of function > const always yield TRUE.
When const > function.max, comparisons of function < const always yield TRUE.
When const <= function.min, comparisons of function < const always yield FALSE.
For integers:
When min = -1, cond(function > -1) is the same as cond(!~function).
When min = -1, cond(function < 0) is the same as cond(~function).
To implement the above, we got rid of the cond(x < 0) -> cond(x & 0x80000000) optimization, which has caused more trouble than it has solved for just 1 byte gain.
When min = 0, cond(function > 0) is the same as cond(function).
When min = 0, cond(function < 1) is the same as cond(!function).
Similar expressions can be obtained for max in [-1, 0], but it's not worth it, as there are no functions with -1 as maximum, and the ones with max=0 also have min=0 (always return 0).
We had dormant code to check for boolean-ness of functions, which is now active. But it didn't cover all possible booleans. Now it does.
An idea for the future is to associate ranges to expressions, and attach them to calculable functions. For example, (integer)llFrand(2) could be resolved to a boolean.
- Separate library loading code into a new module. parser.__init__() no longer loads the library; it accepts (but does not depend on) a library as a parameter.
- Add an optional library argument to parse(). It's no longer mandatory to create a new parser for switching to a different builtins or seftable file.
- Move warning() and types from lslparse to lslcommon.
- Add .copy() to uses of base_keywords, to not rely on it being a frozen set.
- Adjust the test suite.
We oversought that the optimization that 8d33746 applied was already present, so no need to duplicate it.
A better place for handling '|' was under the code that already did so. No functionality change involved.
It can't be done always: flag1 and flag2 must be nonzero powers of two. In that case, we can transform it to:
!~(x|~(flag1|flag2)) = !~(x|constant)
The -2147483648 case has trouble with the sign hack detector and I couldn't trigger it.
Rather than assert that the types are correct, use the force type functions on the parameters:
ff, fk, fs, q2f, v2f, and the new fi, fl.
These functions have also been modified to ensure that the input type supports an implicit typecast to the target type and perform it, or emit ELSLInvalidType otherwise, rather than an assertion failure. fl in particular returns the original list if it isn't changed, or a copy if it is.
A couple bugs were found in testfuncs.py as a result, which have been fixed as well. A test has been added to ensure that the exception that caught these bugs remains in place.
The isxxxx functions are no longer necessary, so they are removed. Same goes for the painful cast handling process in foldconst, which was basically performing this task, and not necessarily well.
This approach is much more robust and should have been used since the beginning, but I didn't figure it out then.