Big update to prettier-extension-monkeyc

I've posted about prettier-extension-monkeyc before, but I've added a bunch of new features that developers will probably like (well, I've been missing them, so maybe you have too).

The new features it implements for VSCode include:

  • Goto Definition. Point at a symbol, Ctrl/Cmd click, and it will take you to the definition. Or F12
  • Goto References. Right click on a symbol and select "Goto References". It will show you all the references. Or Shift-F12
  • Peek Definition/Peek References. Same as above, but in a popup window so you don't lose your place in the original document.
  • Rename Symbol. Right click on a local, function, class or module name, and select "Rename Symbol". It will rename all the references. It doesn't yet work for class members/methods.
  • Goto Symbol. Type Ctrl/Cmd-Shift-O and pick a symbol from the drop down (which has a hierarchical view of all symbols in the current file). This also appears as an outline across the top of the file.
  • Open Symbol By Name. Type Ctrl/Cmd-T, then start typing letters from a symbol name. A drop down will be populated with all matching symbols from anywhere in your project.

Older features include a prettier based formatter for monkeyc, and a monkeyc optimizer that will build/run/export an optimized version of your project.

[edit: My last couple of replies seem to have just disappeared, and the whole conversation seems to be in a jumbled order, so tldr: there's a new test-release at https://github.com/markw65/prettier-extension-monkeyc/releases/tag/v2.0.9 which seems to work for me on linux. I'll do more verification tomorrow, and push a proper update to the vscode store once I'm sure everything is working]

  • I have now a warning that I can't understand

    Fixed in v2.0.23

  • My wish for this x-mas is that for every 10 bugs you fix in less than a day Garmin fix 1 bug that is known for at least a year :)

  • Personally I think Garmin should just fix their type system so that something like if (s != null) narrows types properly

    This one actually does work, I believe, if s is a local.

    But things like "s != null && foo(s)" (where foo expects a String) don't work in the currently released sdks (but do work in the Compiler 2 Beta). But there are plenty of places where the person writing the code knows that a variable is non null, but the compiler can't know that.

  • s != null && s.foo() works in Compiler 2 Beta, but if (s != null) {s.foo()} doesn't work as far as I remember, I also added some info about it on the existing bugreport.

  • but if (s != null) {s.foo()} doesn't work as far as I remember

    My experience is that really simple checks like the above do work, even for non-locals. But equivalent things which work for locals don't necessarily work for non-locals.

    eg I just tried:

        var mStr as String? = null;
        function bar(s as String?) as Void {
            if (s != null) {
                s.find("x"); // works
            }
            if (mStr != null) {
                mStr.find("x"); // works
            }
            if (s == null) {
                return;
            }
            s.find("x"); // works
            if (mStr == null) {
                return;
            }
            mStr.find("x"); // fails
        }
    

  • ok, here's one that doesn't:

    protected var mHeartRateZones as Array<Number>?;
    function x() {
        if (mHeartRateZones != null) {
            var z1 = mHeartRateZones[1];
        }
        // but this works:
        var heartRateZones = mHeartRateZones;
        if (heartRateZones != null) {
            var z1 = heartRateZones[1];
        }
    }
  • feature-request: add more optimizations related to evaluation of constants. For example I have code like:

    if (SPACE_BETWEEN_GAUGE_AND_LABEL != 0 && mShowZoneColor) // the const is intentionally on the left side

    and you could evaluate the expression (I know this won't be simple as being said, but I hope you can do it at least for the most typical use cases) and either remove "PACE_BETWEEN_GAUGE_AND_LABEL != 0 " (when PACE_BETWEEN_GAUGE_AND_LABEL is not 0) or remove the whole if bock (which I believe you would have done if I used a boolean const)
  • Yes, this is mostly just something I haven't got around to... I implemented all the binary operators that ended up being applied to two constants in *my* projects, and intended to come back and add the rest later. But I haven't got around to it yet.

    So right now, I'm not constant folding any comparisons (that should be fairly easy to implement), or logical && and ||. I deliberately left out the logical operators because of all the bizarre things that happen with different types. eg if SPACE_BETWEEN_GAUGE_AND_LABEL is 5, it looks like I can optimize this to "if (true && mShowZoneColor)", which then becomes "if (mShowZoneColor)". But if mShowZoneColor is null (or Float, or pretty much anything other Boolean, Number or Long), the original code crashes, while the optimized code does not (and remember that my optimizer currently knows nothing about types, so no matter how its declared, I have no idea what type mShowZoneColor is going to be.

    So yes - comparisons should be easy, I'd just forgotten about them. Logical operators maybe a bit harder, but there's definitely no problem when the first argument to && is false, or the first argument to || is true; so I should at least be able to handle those...

  • Version 2.0.24 is out.

    This release adds various features:

    •  All binary operators can now be constant folded (for suitable argument types)
    •  Functions that can be inlined in assignment scope, can now also be inlined in declarations.
    •  There’s a new size based partial redundancy elimination pass.
    •  There’s a new command “Clean Optimized Project” in the VSCode command palette that will remove the build artifacts; useful if you have a monkey.jungle that includes all .mc files, including ones in bin.

    I noticed that a reference to a non-local variable takes 8 bytes, plus 6 for each component (so gFoo takes 8, MyModule.gFoo takes 14), while a reference to a local variable only takes 2 bytes. So if there are two or more uses of.a global in a function, and the optimizer can prove the value doesn’t change, its worthwhile to load the value into a local (10 bytes) and replace each use with a use of the local (saving 6 bytes per use). So I wrote the PRE pass to do that. Currently, it assumes that all non-locals change at all function/method invocations. This is suboptimal, and analyzing each function to determine what it might change will be coming soon.

    While working on that, I noticed that even literals are quite expensive; each reference to a number or string adds 5 bytes, so if, for example, the number 0 is used 3 times in a function, thats 15 bytes. But if I read it into a local, and then use it 3 times thats 7 + 3*2 = 13 bytes - so again, I can save space if its used 3 or more times. And this time, I don’t have to worry about anything else changing the value…

    For my code, this takes about 150 bytes off of a program with a 10k code footprint, on top of the 1k it was already removing. I think once I fix it to be less conservative, it might get another 50-100 bytes.

    This pass was quite tricky to implement. Since I’m constrained to output monkeyc (rather than some lower level byte code), it can be hard to find a good place to insert the assignments to the new variables; and sometimes, it's simply not possible. I’ve spent a while ironing out all the bugs I could find, but I’m still concerned that it could do unexpected (and incorrect) things. So for now, it's off by default. There’s a new prettiermonkeyc.sizeBasedPRE setting (Cmd/Ctrl-, then type sizeBasedPRE into the search field), which can be overridden in tasks.json, or launch.json.

  • Can you double check the "Functions that can be inlined in assignment scope, can now also be inlined in declarations."? I think it has a bug.

    1 Downloaded the new version, and turned on the size based pass, and I saw the code shrinked. But when I changed the code from:

    var x;
    x = foo();

    to:
    var x = foo();

    then the code size grown. It's because an old bug is back:

    (:inline)
    function foo() {
       bar();
    }

    (:inline)
    function bar() {
     ...
    }

    both foo and bar are inlined, but the original bar is not deleted.