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 a question / feature-requests: thanks to the prettier optimizer I managed to squeeze many new features to my code - mainly to devices with only 16K memory - however now I reached a point when even the devices with 32K memory get out of memory when I compile them without the optimizer. However some of my code is debug related. So I though that I could maybe somehow differentiate when am I compiling without the optimizer (mainly to debug with breakpoints) when I could do without these logs and when I compile with the optimizer (mainly when I need it for side loading and thus I need the logs).

    Is there a convenient way to know when am I compiling with or without the optimizer? I mean something like there's an annotation or something like that.

    Let's say that the same way the optimizer has the "built-in" :inline it would also have :optimizer. This would enable me to do things like:

    (:no_optimizer) const OPTIMIZER = false;
    (:optimizer) const OPTIMIZER = true;


    (:optimizer) var msg = "";
    function x() {
        for (i = 1; i < 10; i++) {
            if (OPTIMIZER) {
                msg += "" + i + "." + blabla;
            }
        }
        if (OPTIMIZER) log(msg);
    }

    or

    (:no_optimizer)
    function y() {
        // foo
    }
    (:optimizer)
    function y() {
        // bar
    }

  • Actually I have a theory:

    So the way paths in jungles work, is that they're always relative to the jungle file itself - so if you have two jungles in different directories (I do this, btw; I have a short stub jungle, and a generated jungle in another directory), you will refer to the same physical paths with different relative paths. Also, all the default paths (resources, resources-round, resources-fenix3 etc) are relative to the manifest file (which is usually in the same directory as the jungle, but doesn't have to be).

    But something seems off about your theory - at least as I understand the way resources/jungles work together.

    If you put

    fenix3.lang.hun = memory16K/resources-hun

    in your jungle, then that should completely overwrite all the default paths for fenix3. The *only* place the compiler should look for fenix3 Hungarian language overrides is memory16K/resources-hun. If you wanted to *keep* the default paths, you should have put

    fenix3.lang.hun = $(fenix3.lang.hun);memory16K/resources-hun

    which (assuming you haven't changed any of the defaults for base, round, or round-218x218) ends up as 'resources-hun;resources-round-hun;resources-round-218x218-hun;resources-fenix3-hun;memory16K/resources-hun'

    So based on what you've said, even without my optimizer, the resource compiler shouldn't find anything in resources-hun when compiling for fenix3.

    That said, it looks like it actually does. But in that case, it should still find them after my rewrite because resources-hun is relative to the manifest file, not relative to the jungle.

    Anyway, I'll try to setup an example project and figure out exactly what is going on over the weekend.

  • This would enable me to do things like:

    (:no_optimizer) const OPTIMIZER = false;
    (:optimizer) const OPTIMIZER = true

    I don't see a direct way to do that. The problem is that when you're compiling without my optimizer, my optimizer can't really affect anything. So while I could ensure that no_optimizer is provided as an exclude annotation when compiling with the optimizer, I *can't* ensure that optimizer is provided when not using it.

    I suppose I could provide an option to remove a particular list of exclude annotations, and add a different list when compiling with the optimizer; that would mean you could then add optimizer as an exclude annotation in your jungle, and set the option to remove that, and add no_optimizer when optimizing... but thats getting pretty obscure.

    I guess a simpler thing I could do is just have a magic symbol that the optimizer pretends exists. So you could do:

    const OPTIMIZER = $ has :prettier_monkeyc_optimizer;

    Then the optimizer guarantees to evaluate that as true (at compile time), while Garmin's compiler would evaluate it as false (unless of course you actually declared it somewhere).

  • I guess a simpler thing I could do is just have a magic symbol that the optimizer pretends exists

    On reflection, I don't think that helps you, because although the expression will evaluate to false when you compile without my optimizer, Garmin's optimizer won't constant fold it, so all the code guarded by OPTIMIZER will remain in the app, and you don't gain anything.

    But what I could do is provide an option to override the value of a constant. So you could just write

    const OPTIMIZER = false;

    in your code, and then in the prettier-monkey options set the (to be defined) option to override OPTIMIZER to true.

    Also note that this will only help you when compiling at -O2, because at -O1 (or -O0) Garmin's compiler doesn't constant fold anything, and will leave all the code in regardless. So that may be less useful if the goal is to use the debugger...

  • If you put

    fenix3.lang.hun = memory16K/resources-hun

    So I wrote a small project that confirms what I wrote above. I have a resources-hun directory which defines a string that doesn't exist in the base definitions. As a result, if *that* resource file gets included in the build, it results in an error. I also have a fenix3-hun directory with a valid set of overrides. If I set

    fenix3.lang.hun = fenix3-hun

    then both the optimized and unoptimized builds complete successfully, but if I write

    fenix3.lang.hun = $(fenix3.lang.hun);fenix3-hun

    then they both fail.

    So now I have no idea what's going on in your project. It *sounds* like the optimized build is pulling in a resource from resources-hun, when it shouldn't be. But from what you've told me so far, I can't see how.

    Is there any chance you can create a cut down version of your project that repro's the issue? I suspect that if you just created a new project (say, a simple datafield), then added all the resource files to it but none of the sources, the problem would still reproduce. If thats the case you could then just remove resource files until you identified the source of the problem, and then remove resources from the remaining files until you had something you were willing to share with me...

    Meanwhile, I think I have a fairly straightforward way to fix the "heb" issue.

  • <property id="-" type="string"></property>

    After looking a bit more, the issue here is that "-" isn't allowed as the first character of an attribute name, and in particular in section 3.3.1, it says "Values of type ID must match the Name production".

    However, it looks like Garmin's parser allows it, and xmllint allows it. And there doesn't seem to be a downside to allowing it, so I'll just fix my parser...

  • I made the stupidest thing in the last 10 years... I deleted the whole source directory and wasn't able to recover it, so everything I did in the last 2 months is gone. So I'll try to re-write everything... I guess it'll take a week at least, and I'll get back to the POC later

  • when you inline a function can you add a comment with the function definition? i.e:

    (:foo, :inline) function x(s as String) as String {
        return "x: " + s;
    }

    var a = x("test");

    would become:

    var a;
    // (:foo, :inline) function x(s as String) as String
    {
        a = "x: " + "test";
    }

    (the point is to add the definition at or 1 line before the opening { of the block)

  • I'm using the optimized output from 3 days ago to recover my 2 months of work :) 

    I see some things that are pretty much edge cases but might be improved slightly:

    const SPACE_BETWEEN_GAUGE_AND_LABEL = 0;
    function f() {
       var
    spaceBetweenGaugeAndLabel = mShowZoneColor ? SPACE_BETWEEN_GAUGE_AND_LABEL : 0;
    }

    became:
    var spaceBetweenGaugeAndLabel = mShowZoneColor ? pre_0 : pre_0;

    which could be optimized to:
    var spaceBetweenGaugeAndLabel = pre_0;
  • Also can you give a warning if any "big" function is inlined ore than once? I just realized that I have a huge chunk of code twice in the optimized output, because whenever I decide if a function needs to be inlined I search for it's name and if I see that it's only called once I add :inline annotation. The problem (my bug :) is that this was the case:

    (:debug)
    const MOCK = true;
    (:release)
    const MOCK = false;

    function a() {
        c();
    }
    (:inline)
    function b() {
        if (MOCK) {
            c();
        }
    }
    (:inline)
    function c() {
        d();
    }
    (:inline)
    function d() {
        //... this big code block is inlined twice when I debug
    }
    function other() {
        b();
    }

    PS: maybe another solution is to add :inline_once as I asked a few months ago, though maybe the warning is even better (or both features could be added as well ;)