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]

  • Looks good!

    How this plays with Strict typecheck? Or you do it before this and run the compiler with typecheck off?

    Also: even though I am a fan of not changing zero = 0 to pre_0 ;) but in this case i think it's more confusing to keep the name of a variable that is reused, so probably better name it var_1. And if you can add the real name as comment it's even better.

  • How this plays with Strict typecheck

    No issues. At least, in theory there shouldn't be any, and so far I've not had any.

    Note that when you declare the type of a function parameter, it's not a constraint; it just tells the type checker what the type of the passed parameter will be.

    After that, you can assign whatever you like to it, and the type checker (both Garmin's and mine) will track it like any other local.

    This is very different from Module or Class variables, where the type declaration is also a constraint on what you can assign to the variable.

    Also: even though I am a fan of not changing zero

    For declared locals, it's not a big deal whether I just generate a random name, or pick one of the locals as the "canonical" one (currently I do pick one - although you can't see that from the example).

    But for parameters, I don't think I should change the name. From the caller's point of view, the name of the parameter still makes sense.

  • yeah, but if you'll reuse it then it might make also sense to do it with the comments:
    function x(meaningful as Number, var_1 /*> original_name <*/ as String)

  • v2.0.46 is out. Fixes the infinite recursion bug, and adds the minimize locals pass described above.

  • It works very good! I can see that the memory usage is down by about 0.1k. Now I am almost able to release a new feature added. It still needs a few less bytes though... It works, but it depends on the user input strings' length in the settings. If I use "normal" values then it's kind of OK, it gets to 28.5kB/28.5kB so it's really on the edge... But if I intentionally fill in every string setting with it's longest value then it's too much.

    So the things I thought about:

    - not related to prettier: I might shorten some strings (especially the fit contribution field's name. Currently they're long, meaningful strings, but in theory I could shorten them (almost as much as the preferences, to have 2-3 letter names)

    - I was thinking if this is a possibility: when the optimizer "gives up" / "chooses the strictest option" / etc... when it is in a state where probably there could be a better option but because it doesn't know something or because it needs to be 100% sure (for example: if (foo has :bar) can't be eliminated to future-proof the code) then it could give some indication how to improve manually (this option most probably should be enabled in the settings). An example to such a useful message is when it can't inline a function. In that case with a small manual refactoring it can be improved. From the explanations in this thread I think there are lot more small things like that. So if there's a possible way to communicate it when compiling with "Optimization hints" enabled then the developer could refactor the code to give the optimizer one more chance.

    - Additionally there could be some annotations to help the optimizer in some of these tricky cases. (And the above hints could tell us what annotation to use to direct the optimizer, like :foo(true) or :enable_bar) 

  • So the things I thought about

    Somewhat unrelated to my extension, but I noticed in some of the code you posted earlier in the thread that you had an array that looked like heart rate zone colors? I had a similar array, and found I could save quite a lot by converting it to bit fields stored in a Number.

    Instead of

    const mColors = [
      Graphics.COLOR_LT_GRAY,
      Graphics.COLOR_BLUE,
      Graphics.COLOR_GREEN,
      Graphics.COLOR_YELLOW,
      Graphics.COLOR_RED
    ] as Array<Number>;

    I have

        function rgb6(color as Graphics.ColorType) as Number {
            return (
                ((color & 0xc00000) >> 18) +
                ((color & 0xc000) >> 12) +
                ((color & 0xc0) >> 6)
            );
        }
        private const mColors as Number =
            (rgb6(Graphics.COLOR_LT_GRAY) << 0) +
            (rgb6(Graphics.COLOR_BLUE) << 6) +
            (rgb6(Graphics.COLOR_GREEN) << 12) +
            (rgb6(Graphics.COLOR_YELLOW) << 18) +
            (rgb6(Graphics.COLOR_RED) << 24);
    

    Basically, I'm encoding every color into 6 bits; the top two bits from red, the top two bits from green and the top two bits from blue. Most devices only have 6 bit color anyway, and the standard heart-rate-zone colors are all taken from the 6-bit palette. So 5 zones conveniently fits into a Number. If you needed more that 5, you could use a Long.

    The above code relies on the optimizer's automatic constant function folding. Since rgb6 consists of a single return statement, any time you call it with all constant arguments, it tentatively inlines it, and if the result is a constant, it actually inlines it.

    Under this mapping,

    • LT_GREY = 0xaaaaaa -> 0x2a
    • BLUE = 0x00aaff -> 0x0b
    • GREEN = 0x00ff00 -> 0x0c
    • YELLOW = 0xffaa00 -> 0x38
    • RED = 0xff0000 -> 0x30

    This means that `mColors` ends up as 0x30e0b32a. To actually use it, you just do:

    // extract the 6-bit field based on the zone
    var field = mColors >> (zone * 6);
    // convert the 6-bit color back to 24-bits
    var color =
        (field & 0x03) * 0x55 +
        (field & 0x0c) * 0x1540 +
        (field & 0x30) * 0x55000;
    

    Anyway, just a thought. Maybe you can do something like this and get a few more of the bytes you need...

  • Indeed I have, but 7 colors (5 + transparent for zone 0 and dark red for 6).
    You wrote about Number vs Long that Long is a pointer to an object (pointer in the stack + 8 bytes in the heap) while Number is a 32 bit literal. So that makes it less appealing, on the other hand then I have 3 more colors I can pack there.

    However it seems that there are 2 bugs or there's something I do wrong:

        private const ZONE_COLORS as Long =
            (rgb6(Graphics.COLOR_TRANSPARENT) << 0) +
            (rgb6(Graphics.COLOR_LT_GRAY) << 6) +
            (rgb6(Graphics.COLOR_BLUE) << 12) +
            (rgb6(Graphics.COLOR_GREEN) << 18) +
            (rgb6(Graphics.COLOR_ORANGE) << 24) +
            ((0L+rgb6(Graphics.COLOR_RED)) << 30) +
            ((0L+rgb6(Graphics.COLOR_DK_RED)) << 36);
    

    became:

      private const ZONE_COLORS as Long =
        (63 << 0) +
        (42 << 6) +
        (11 << 12) +
        (12 << 18) +
        (52 << 24) +
        ((48 + 0l) << 30) +
        ((32 + 0l) << 36);
    

    So IMHO there are some bugs:

    1. It doesn't matter in this case (when the #2 will be fixed) but in general just as you eliminate the 0 in "n + 0" => "n" (where n is Number variable) you could eliminate shift by 0-s: "n << 0" or "n >> 0" can be "n"

    2. 42 << 6 should be 128

    3. "48 + 0L" should be "48L"

    4. all the above  +-s shouldn't be there, it should be just one Long.

  • The below things are not bugs, just make the generated code harder to read.

    I saw som funny local variable comments:
    label /*>pmcr_zoneColor_0<*/ = ...

    I think it's confusing, in the comment it should only use variable names / values from the original code.
    Also: probably no point using long pre_... variable names, especially when it's value is not going to be really what it is called, and that anyway next to each variable there's the name it acts as at that point. So IMHO you can use short names: instead of pmcr_zoneColor_0 /*>heartRateZones<*/ it could be just: v1 /*>heartRateZones<*/
    And for pre_X in the comment: instead of foo /*>pre__1<*/ it could be: foo /*>-1<*/ (again: use the value in the original code)
    Also, if you already add comments with original names, can you add the function names next to their inlined block?

    (:inline) function foo(a as Number) as Float {...}
    f = foo(3);
    Either:
    { /*>foo<*/
    Or:
    { /*>foo()<*/
    Or:
    { /*>foo(a as Number) as Float<*/
    Or:
    { /*>foo(3)<*/ - probably this makes the most sense
  • Unfortunately it doesn't seem to shrink the size for me. I guess it's because of the extra bits of code for the Long. The best version was 16 bytes bigger (+24b code, -8b data) - This is of course after replacing the const value with the calculated Long value.

  • So IMHO there are some bugs

    1. Yes, I think I did miss the x << 0 optimization; but when both operands are constants, it should be folded anyway.

    2. Yes it should, and when I try your example:

        private const ZONE_COLORS as Long =
            (rgb6(Graphics.COLOR_TRANSPARENT) << 0) +
            (rgb6(Graphics.COLOR_LT_GRAY) << 6) +
            (rgb6(Graphics.COLOR_BLUE) << 12) +
            (rgb6(Graphics.COLOR_GREEN) << 18) +
            (rgb6(Graphics.COLOR_ORANGE) << 24) +
            ((0l + rgb6(Graphics.COLOR_RED)) << 30) +
            ((0l + rgb6(Graphics.COLOR_DK_RED)) << 36);
    
        function initialize() {
            System.println(ZONE_COLORS);
        }
    

    I get:

      function initialize() {
        System.println(2251438471871l);
      }

    so the const has been eliminated entirely, and its uses have been replaced with the value.

    So I'm not sure what's going on in your case...

    Similarly 3. and 4. don't seem to happen for me (which is what I would expect).