In the same places as state changes are allowed, i.e. in places where a parent of the AST node is a WHILE/DO/FOR or an IF without ELSE, it's allowed to use return statements with expressions that return void, e.g. llDie(), provided the function itself is declared as returning void.
The construction, when found, is rewritten to '{<void expression>; return;}' because the optimizer is not designed to deal with these monsters.
We've renamed the variable SuspiciousStSw to PruneBug, because it's used for both purposes now, though a better name might have been PruneBugPendingChecks, because these are only errors if the IF has an ELSE. We've also added the exception to raise as part of the data stored in the list.
Per report by Tonaie Resident.
Revert "Add support for C11-style _Pragma operator on processpre".
Revert "Add unit test for the _Pragma operator".
This reverts commits 31fcb331c7 and e261ac2121.
This should rather be the job of the preprocessor, which should generate #pragma lines. gcc does that.
A minor difference is that strings and whitespace are parsed according to LSL rules, not to C rules, since this processing is performed in the lexer.
This could be fixed, but is it worth the trouble?
Instead of a tree of symbol tables, we keep a running stack of active symbol tables while parsing. The only case in which this causes problems is forward reference resolution for jump labels, which is solved by storing a copy of the stack at the point the jump was found.
Since we need to add variables, we need to know which scope to add them to. Add this information to the {} node, which is what creates a new scope.
An alternative would be to scan for any variable or label declaration within the braces and use that or create a new one if none, which is more expensive and may waste symbol tables.
Rather than using a hardcoded table of list-to-type extraction function, add a 'ListTo' attribute to the function data. No error is raised if more than one function exists to convert to the same type.
This change is of questionable usefulness, but it should soothe those allergic to magic names/numbers. I cringed a bit myself.
While on it, change the syntax error that was raised when the corresponding conversion function did not exist, to a tailor-made error.
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.
LSO allows this. The compiler does too, but it chokes in RAIL.
This affected a test, which has been adjusted too.
Untyped lazy list elements can no longer be used in isolation in expression lists (including FOR initializator and iterator).
Also rename the terribly named 'self.forbidlabels' to 'self.optenabled' which is more descriptive.
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).
While not strictly a bug because it would be caught later in the function (it passes the tests either way), it made me nervous to leave a dangling NextToken().
This may cause more trouble than it's worth, but it's how LSL behaves and one of our objectives is to document the darker corners of LSL. Mono chokes at the RAIL postprocessing stage, not in compilation proper. LSO chokes at runtime for string, key and list, and works fine for the other types.
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.
Since our syntax extensions transform the source at parse time, all syntax extensions are disabled. The optimizations are disabled too, as it doesn't make sense to prettify and optimize at the same time (the optimizer would remove the constants that we're trying to keep).
Addresses #4 in a more user-friendly way.
Still somewhat messy, but still reported as soon as it can be detected.
If an ELSE token is detected at the top level, for example, the error position will be rewound to the state change and reported there.
This means that in this situation:
x()
{
if (1)
{
state default;
x(2);
}
else ;
}
default{timer(){}}
an error will be reported in x(2), because the ELSE hasn't been found at that point, therefore the state change statement isn't found to be at fault yet.
However, in this case:
x()
{
if (1)
state default;
else
x(2);
}
default{timer(){}}
the error WILL be reported at the state change statement.
This commit also changes the position where the exception is reported, to be at the STATE token. As an inconsequential side effect, EParseCantChangeState takes precedence over undefined identifiers, in case the state change is to an undefined state, but only in cases where it can be immediately detected.
- 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.
Also simplify and fix the matching expression for #line (gcc inserts numeric flags at the end).
It still has many problems. It's O(n^2). It's calculated at every EParse, and EParse can be triggered and ignored while scanning vectors or globals. UniConvScript doesn't read #line at all, thus failing to report a meaningful input line. But at least it's a start.
ReportError() needed to account for terminal encodings that don't support the characters being printed. It was also reporting an inaccurate column number and its corresponding marker position, because the count was in bytes, not in characters, so that has been fixed.
Now EParse.__init__() calls a new function GetErrLineCol() that calculates the line and column corresponding to an error position.
The algorithm for finding the start of the line has also been changed in both ReportError() and EParse.__init__(); as a result, function fieldpos() has been removed.
The exception's lno and cno fields have been changed to be 1-based, rather than 0-based.
Thanks to @Jomik for the report. Fixes#5.
No other functional changes. This required quite some reorganization affecting many files. As a side effect, PythonType2LSL and LSLType2Python aren't duplicate anymore.
Adds a new EParseInvalidBrkContArg exception. Previously it raised EParseInvalidBreak or EParseInvalidCont, whose text was misleading for this type of error.
When a global list includes a reference to a global variable of type key, the corresponding list entry type is string, not key (SCR-295, possibly caused by SVC-1710 or SVC-4485).
This implementation is fishy, because it hard-codes the type in the node regardless of the child types. But in some quick experimenting, it seemed to work. And since the main purpose is to document LSO's behaviour, rather than actually being usable, it's OK like that.
The previous commit didn't work as expected. "from module import var" freezes the value at load time; changing it later has no effect. A reference to the module needs to be used.
Fix that and the similar problem with LSO. Also revert some "from lslcommon import *" introduced earlier.
That also revealed another bug about missing 'cond' in the import list of lslextrafuncs. This should fix all functions that return values on null key input.