Archive for February, 2009

Optimizer updated

Saturday, February 28th, 2009

Some kind users forwarded me some bugs that they found when using the optimizer/code generator.  A few were particularly egregious (causing problems whether the optimizer was turned on or off).  One made the optimizer go into an infinite loop.   Some just added unnecessary cruft to the optimized output.  In the course of correcting these issues (part of bugfix release 0.10.2), I added an additional optimization:

if (1) llSay(0,"Hello!");

Becomes:

llSay(0,"Hello");

And:

if (0) llSay(0,"Hello!");

Becomes:

(nothing)

Combined with constant folding, you can scatter debug statements throughout the code,  controlled by a DEBUG flag at the top of your script:

integer DEBUG = 0;
...
    state_entry() {
        if (DEBUG) llOwnerSay("entered foo state");
    }

and the debug code will be removed in the compiled output.

Parser error recovery for LSL Plus

Tuesday, February 24th, 2009

A first cut at parser error recovery is done.  The basic strategy I took was to try to resync the handler at global declaration, state, and handler boundaries.  The parser will parse as many global declarations as it can; when it encounters a syntax error it will skip forward to the next possible parseable top level element.  In a state definition, the parser will parse the beginning of the state (the state keyword and state identifier, or ‘default’) and as many handlers as possible, and then try to resume after a syntax error and parse to the end of the state, or until a new state definition is found.  The following is how I visualize how error recovery works – the items in green bold are successfully parsed elements, the items in italic (not green) are the elements ‘rejected’ by the parser, and the items in red are the tokens that caused the elements (handlers or global declarations) to be rejected.

integer foo() {
    return 42;
}

integer bar()
    return 0;
}

default {
    state_entry() {
        llOwnerSay("Hello World");
        state other;
    }
}

state other {
    state_entry() {
        llOwnerSay("Hello from other");
    }

    listen(integer chan, string who, key id, string msg) {
        llOwnerSay("heard: " msg);
    }

    state_exit() {
        llOwnerSay("Goodbye");
    }
}

Error Recovery and LL(k) Parsing

Monday, February 23rd, 2009

For LSL Plus I’m using the Parsec parser combinator library, which is an LL(k) parser.  It has facilities for error reporting (when a parse failure occurs, it reports the position and what tokens were expected) but not error recovery.  I believe I’ve found a low-tech way to get simple error recovery implemented, but I’m curious about more general techniques.  A parser combinator library which supports error recovery is desribed in this paper, which I’m on the verge of reading… but before I educate myself on formal techniques for error recovery, I’m curious about how ‘error recovery’ can be defined.  I think you perhaps could define it in terms of ‘edit distance’.

The ‘edit distance’ between two strings is the number of insertions/deletions/substitutions that it would take to transform one string into another.  The following is a Haskell function that computes the edit distance between two strings.

> ed :: Eq a => [a] -> [a] -> Int
> ed s [] = length s
> ed [] t = length t
> ed (s:ss) (t:ts) =
>   minimum [(if s == t then 0 else 1)
>            + ed ss ts,
>            1 + ed ss (t:ts),
>            1 + ed (s:ss) ts]

A parser that does error recovery would try to find a correct string (one that parses) that is the least distance from the actual string being parsed.  There will be a bound on the search path, since the grammar will have shortest string that parses correctly; the edit distance must be less than or equal to the length of the input string plus the length of the shortest possible correct string.  (I.e. you can always delete the entire string and insert a correct string, which is the worst case for error recovery).  Searching all strings from edit distance 1 to the maximum edit distance is of course not feasible (except for very tiny strings), but perhaps there are approaches much better than brute force, which is something like O(tn), where t is the number of possible tokens.

Nevertheless, I’ll probably go with an error recovery approach using Parsec that is heavily tuned to my grammar and existing parser.

For release 0.11.0

Saturday, February 21st, 2009

The next major release will be 0.11.0. I’m planning features to include, which I think will be focused around UI/editor improvements, as well as some improvements to how rebuilds work.

Right now, everytime any file changes in your project, all the LSL is ‘recompiled’.  Generally this is quick, but people with large projects will suffer due to redundant rebuilds.  The next release at the very least will be smart enough to only rebuild the script that changed, for script changes, and ideally would be smart enough to only build affected scripts when a module changes (this is a bit more work, requires keeping a dependency graph around).  This is a sort of ‘under-the-covers’ change that affects only performance, but is nevertheless tied in to the overall architecture of the plugin.

UI changes that I’m considering is adding an outline view (this has been requested).  I’d like to do a ‘popup’ outline (such as exists in JDT Java Editor) as well as an outline view in a separate frame.  I’d like to do better code folds in the editor.  And I’d like to do symbol hyperlinking (Ctrl-click on a variable or function reference, and you will navigate to the declaration).  All these features are tied to better interaction with the compiler (which understands the structure of the code).  All also require dealing with ‘broken’ source code (source code that doesn’t parse correctly should still for the most part be outline-able and foldable).  Dealing with broken source code requires error recovery in the parser, which is not completely trivial (but interesting!).

We’ll see how far we get… I try to make one ‘major’ release a month,  but I have no target release date for 0.11.0 yet.

LSL Plus 0.10.1 Released!

Wednesday, February 18th, 2009

The bug fix release alluded to earlier is now out, via both the update sites and as one big download. See the home page for details.

Performance issues resolved

Tuesday, February 17th, 2009

The performance issues reported for Mac users (and also subsequently reported for 64bit Linux) proved to exist on all platforms. The optimizer efficiency has been improved (by an order of magnitude or so) and will be released shortly, in release 0.10.1.

Possible peformance problem on Mac

Thursday, February 12th, 2009

I have reports of a possible severe performance problem with the 0.10.0 release on a Mac. Any Mac users with (or without) similar problems, please report your findings here, or in the forums!

For the next release…

Sunday, February 8th, 2009

The next release is likely to be a minor release (e.g. 0.10.1) which will enhance, and in some cases correct, the optimizations done. As far as I know there are no optimizations that change the semantics of a script, but there are a couple of cases where constants get inlined where they shouldn’t be. For example, list constants defined as global variables get inlined in 0.10.0, but should not, as this is likely to increase the memory footprint of the script.

Another possibility is updating the Haskell core to make it more useful from the command line (allowing, for example to be used as part of a Makefile).

Mac Release for 0.10.0 now out

Saturday, February 7th, 2009

The Mac (OSX/x86/32bit) release for version 0.10.0 has been released.

Welcome

Saturday, February 7th, 2009

This is the blog for the LSLPlus project. Expect news and information about ongoing work on the project to appear here.