Commit graph

129 commits

Author SHA1 Message Date
Sei Lisa
6ef4c03994 Remove support for labels as immediate children of IF/ELSE/WHILE/FOR/DO.
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).
2018-04-01 20:05:35 +02:00
Sei Lisa
075d3aba0c Change the AST node type from dict to object
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.
2018-04-01 02:14:00 +02:00
Sei Lisa
877d5fc10c Optimize -a == -b, !(a - b), !(a + b)
-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.
2018-03-23 19:02:42 +01:00
Sei Lisa
d0d6d6744b Bump copyright year
The help text in main.py was still at 2015. Oops.
2018-03-23 16:36:45 +01:00
Sei Lisa
a1f1f13739 Better optimization of (non-)equality comparisons.
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.
2018-03-23 16:04:18 +01:00
Sei Lisa
e42479756b Fix bug with nested if's; fix missing EXPR wrap.
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.
2017-11-13 04:04:13 +01:00
Sei Lisa
a4b3c1eadd Handle list+list more sanely. 2017-11-04 22:50:32 +01:00
Sei Lisa
0b1ad7c110 Add more results of operating an expression with itself.
Implemented a<a=0, a^a=0, a+-a=0, -a+a=0, a|a=a, a&a=a
2017-11-03 00:45:11 +01:00
Sei Lisa
4251d4c7f9 Improve CompareTrees.
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 ==.
2017-11-03 00:12:10 +01:00
Sei Lisa
a87022b73f Get rid of StSw craziness and use lsllastpass to make another pass.
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.
2017-11-02 18:20:14 +01:00
Sei Lisa
e4eaab9e84 Move the print_node() subtree dump debug function to lslcommon. 2017-11-02 16:34:35 +01:00
Sei Lisa
097c054494 Optimize 'if' statements better.
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.
2017-10-30 19:23:01 +01:00
Sei Lisa
ed49170d87 Remove empty SEF events.
If the state becomes empty, add an empty timer() event. timer() is the parameter-less event with the shortest name.
2017-10-30 18:05:35 +01:00
Sei Lisa
e13f210695 Make DoesSomething apply to labels optionally.
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.
2017-10-30 18:05:28 +01:00
Sei Lisa
70f39d7f5e Remove "magic names" where possible, using new data from fndata.txt 2017-10-27 23:39:26 +02:00
Sei Lisa
3bb5102f06 Improve llStringLength detection, to catch more cases.
For example, if (llStringLength(s) > 0)  ->  if (!(s == ""))
2017-10-27 16:07:36 +02:00
Sei Lisa
1dde2a1fb9 Use lslfuncs.less instead of > >= < <= to ensure proper type handling.
Float functions could be compared against integer args, or vice versa.
2017-10-27 11:20:42 +02:00
Sei Lisa
c0176ad738 Fix order change in comparisons when one side is not SEF.
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.
2017-10-27 11:13:03 +02:00
Sei Lisa
106bb81543 Refine a comment.
Powers of two can't be zero anyway.
2017-10-26 01:07:48 +02:00
Sei Lisa
4ba71993a4 Optimize min/max in comparisons; undo problematic optimization.
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).
2017-10-26 00:58:14 +02:00
Sei Lisa
22e057e2ec Move side-effect-free check for functions to a method.
This will in future allow us to check argument-dependent SEFness.
2017-10-25 19:24:12 +02:00
Sei Lisa
2580248c17 Minor reorganization and documentation of a section. 2017-10-25 18:04:27 +02:00
Sei Lisa
7e39635b09 Transform !(x != y) into x == y.
Can happen with lists.
2017-10-21 20:38:54 +02:00
Sei Lisa
923309e4a1 Refine test for min and max.
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.
2017-10-21 11:31:53 +02:00
Sei Lisa
ea9711642c Add type casting comparison to CompareTrees.
It can now resolve e.g. (string)llGetPermissions() == (string)llGetPermissions() to TRUE.
2017-10-21 11:03:43 +02:00
Sei Lisa
dc117c2cbf Use CompareTrees to optimize the == operator.
Now we can.
2017-10-21 11:02:38 +02:00
Sei Lisa
e6b23a2d7a Fix CompareTrees to not compare unstable functions as equal. 2017-10-21 10:44:09 +02:00
Sei Lisa
3f6f8ed8ad Internal code reorganization.
- 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.
2017-10-20 18:19:48 +02:00
Sei Lisa
e8fb659592 Allow -2147483648 to count as a power of 2 in an optimization.
A case we oversought when implementing absorption of more than two flag tests into a single number.
2017-10-18 00:04:35 +02:00
Sei Lisa
2bee2db148 Remove duplicate check for '|'
Removed the 'if' and applied an indentation change; no further changes are done.
2017-10-17 23:59:41 +02:00
Sei Lisa
be767f24f0 Move things around to better places; undo 8d33746.
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.
2017-10-17 23:53:24 +02:00
Sei Lisa
b4749d8fa4 Add debug code to display a subtree in a friendlier format than repr(). 2017-10-17 23:32:33 +02:00
Sei Lisa
4cbdbefe1b Fold again after applying distributibity or absortion.
Failure to do so was blocking some constant folding, like in:

if (a&1 || a&2 || a&4) -> if (a&(3|4))

Fixed. Now it correctly outputs if (a&7).
2017-10-17 23:28:14 +02:00
Sei Lisa
6c73ba95c2 Allow (x&r) && (x&s) && (x&t) to be collapsed into a single number.
This is an addendum to 3a849fe that allows more than two tests to be chained and optimized.
2017-10-17 23:26:13 +02:00
Sei Lisa
8d337467cb Fold the children of | as conditions when | is folded as condition. 2017-10-17 23:25:26 +02:00
Sei Lisa
a6a16c6e8f Fold ==0, ==1, ==-1 in general expressions, not just in conditions.
Also add a couple FIXMEs.
2017-10-17 02:38:08 +02:00
Sei Lisa
3a849fe4b9 Optimize a common condition of (x & flag1) && (x & flag2)
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.
2017-10-13 02:59:32 +02:00
Sei Lisa
6ad2ef04d7 Add support for function calls in CompareTrees. 2017-10-13 00:30:47 +02:00
Sei Lisa
7ae7fa4396 Apply two optimizations to bitwise operations.
Based on basic Boolean algebra: distributive and absorption laws.
2017-10-12 22:48:43 +02:00
Sei Lisa
9e7a5d1cdf Change strategy for the checking of function input types.
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.
2017-10-12 17:16:16 +02:00
Sei Lisa
08c69eee0f Simplify a = a = b -> a = b 2017-09-22 16:14:50 +02:00
Sei Lisa
bdd42199eb Fix bug where assignments were treated as statements.
This caused e.g. b = a = a to raise an exception, when a = a was folded into a ';' which isn't an expression.
2017-09-22 16:04:44 +02:00
Sei Lisa
de29a9aa07 Fix for,while,if,do condition test during optimization.
That's exactly what 'cond' is for. Things like a loop with a constant zero vector condition should be recognized.

It was correct before just by chance, because FoldCond currently transforms a constant node into an integer node, but let's not rely on that.
2017-09-15 01:36:13 +02:00
Sei Lisa
6738615360 Separate library function optimization into a different file.
No other functional changes. This required quite some reorganization affecting many files. As a side effect, PythonType2LSL and LSLType2Python aren't duplicate anymore.
2017-08-25 20:35:24 +02:00
Sei Lisa
36947b7b12 Rename lslfuncparams.py -> lslfuncopt.py
This is preparation work for splitting function optimization into a separate file, which will be the same that currently is lslfuncparams.py.
2017-08-25 20:35:24 +02:00
Sei Lisa
3ec460539e Implement the equivalent of GetObjectDetails for GPP/GLPP.
Removes the TODO item added in the last commit.
2017-08-25 20:35:24 +02:00
Sei Lisa
2a6e065c66 Optimize some llList2XXXX functions.
- Resolve for lists that aren't constants, but that are SEF, and reference a constant element, e.g. llList2String([llGetKey(), 5], 1) -> 5.
- Make it work with invalid conversions, if they are SEF.
- Simplify invalid conversions when the list argument is a llGetObjectDetails call and SEF.
- Simplify llList2String(llGetObjectDetails(id, [single_element]), 0) (or -1) to (string)llGetObjectDetails...; same with llList2Key and llList2Integer, converting it to (integer)((string)llGetObjectDetails...).
- Add TODO item to do it similarly for llGet[Link]PrimitiveParams.

Adds auxiliary functions to return a node or constant from a list in any of its forms, its length, and a constant for a node or constant.

Adds some utility members for LSL<->Python type conversion (one of them duplicated from lslparse.py).

Needs maintenance of the types returned by llGetObjectDetails.
2017-08-25 20:35:24 +02:00
Sei Lisa
9a820a62d0 Simplify (type)sametype_expr -> sametype_expr. 2017-08-25 20:35:09 +02:00
Sei Lisa
2e09a3a986 Make some simplifications and keep PyFlakes happy. 2017-08-23 11:55:37 +02:00
Sei Lisa
ca3885c549 Expand conditions of types other than integer, string, key.
if(list!=[]) is always better than if(list), which compiles to if(!(list==[])).
if(float) can't be optimized, but it is equivalent to if(float!=0.0) which can. Same for vector, rotation.
2017-08-17 01:58:30 +02:00