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've updated the infinite-recursion branch with a fix that prevents the crash, and reports what it thinks is wrong. Although that doesn't actually fix your problem, I plan to include that with the "bin" fix so that when cycles do occur (either because of a bug in the code, or yet-another bug in the optimizer), the optimizer will at least report what its unhappy about, and give the end user some idea of what is going wrong.

  • Congratulations on 1000th post in this thread!

    I was playing with A and B classes from your last example, and I see different results.

    First, I slightly modified code to comply strict checking and replaced "A.foo()" with "(new A()).foo()":

    class A {
        function foo() as Boolean {
            return self instanceof A;
        }
    }
    class B {
        function bar() as Void {
            System.println((new A()).foo());
        }
    }

    When I call "(new B()).bar()", I see "true" in stdout.

    If "foo" and "bar" are meant to be static:

    class A {
        static function foo() as Boolean {
            return self instanceof A;
        }
    }
    class B {
        static function bar() as Void {
            System.println(A.foo());
        }
    }

    then I see "false" for "B.bar()", but I don't understand what is "self" in "foo" exactly and why compiler accepts that.

    If "B" should extend "A":

    class A {
        function foo() as Boolean {
            return self instanceof A;
        }
    }
    class B extends A {
        function bar() as Void {
            System.println(A.foo());
        }
    }

    then it is still "true" for "(new B()).bar()".

    I've updated the infinite-recursion branch with a fix that prevents the crash, and reports what it thinks is wrong.

    It now reports about the cycle for my real class, that I've found during debugging session, and for 5 or 6 more from "bin", where I use the same pattern "class ClassName extends BarrelName.ClassName", and the command line execution finishes successfully with expected "'ERROR' level diagnostics were reported" message. Thank you!

    it doesn't even see the file that declares TestBarrel.TestClass, and so TestBarrel.TestClass does (correctly) resolve to $.TestClass

    Could you, please, explain why is it correct? I'd expect "TestBarrel.TestClass doesn't exist" error message. Or is this something about optimizer/analyzer internal implementation that makes it work exactly this way?

  • First, I slightly modified code to comply strict checking and replaced "A.foo()" with "(new A()).foo()"

    Which is completely different.

    My example was correct. Garmin's type checker doesn't like it, but the runtime behavior is as I described.

    The point is that when you say "Foo.bar()" inside an instance method, and Foo is a class, then bar will be invoked with the current value of self. This is exactly what you want when Foo is a super class of the current class (eg in AppBase.initialize()). But it also allows you to call any class method with an invalid self argument.

    Could you, please, explain why is it correct? I'd expect "TestBarrel.TestClass doesn't exist" error message

    Because if Foo is a module, Foo.bar means lookup bar first in Foo, and then in the module containing Foo, and so on until you reach the global scope. So if Foo.bar doesn't exist, but $.bar does exist, then Foo.bar refers to $.bar.

  • Garmin's type checker doesn't like it, but the runtime behavior is as I described.

    With disabled check I see it, thanks. Glad I'm using strict to protect myself from this madness. Slight smile

    Because if Foo is a module, Foo.bar means lookup bar first in Foo, and then in the module containing Foo, and so on until you reach the global scope. So if Foo.bar doesn't exist, but $.bar does exist, then Foo.bar refers to $.bar.

    OMG, I was sure, that prefixed method call can't just ignore class or module name in some cases, but I've just reproduced it. Documentation https://developer.garmin.com/connect-iq/monkey-c/objects-and-memory/#scoping only mentions more or less obvious lookup rules for plain "bar()" calls. Thank you for pointing this out.

    And am I right, that compiler does all these resolutions at compile time and puts full and exact existing methods/classes/modules names into bytecode, and it is not the device (and its firmware, that may change over time) or simulator who decides what is to call? If so (and I think it is after looking through bytecode), then it contradicts in my optinion with the phrase "If you are referring to a global variable, using bling can improve runtime performance", that assumes the device and not the compiler will do these lookups. No, bytecode has "getmv globals/SomeModule func", while "SomeModule" doesn't declare "func".

    I guess, I should carefully look through your "OptimizerTestsLookup.mc" to learn more about this topic. Thanks again for documenting and supporting these bizarre rules.

  • No, bytecode has "getmv globals/SomeModule func", while "SomeModule" doesn't declare "func".

    Yes. There's a potential speed-optimization here that my optimizer could do. It does do it in a few cases, but not in general.

    OMG, I was sure, that prefixed method call can't just ignore class or module name

    It really doesn't ignore it. Normally, when you say "foo", and foo isn't a local variable, it actually means self.foo. If you're inside a module function, self will be the function's module, so it's really identical to $.path.to.module.foo. Since scoped lookup has to work for self.foo, it also has to work for $.path.to.module.foo, because the runtime can't tell the difference.

  • self.foo
    self will be the function's module, so it's really identical to $.path.to.module.foo

    Now I finally get it. Thanks once again.

  • v2.0.98 is out

    The optimizer no longer falls over if presented with a cyclic class graph. It will delete edges to remove all the cycles, report where it found the problems, and then continue to do the usual live analysis.

    The optimizer is less aggressive about removing paths specified by the jungle that refer to "bin". Now it only cares about bin/optimized (or whatever prettierMonkeyC.outputPath is set to), and it only deletes paths generated by "**.mc", the default jungle sourcePath.

    For barrel projects, the extension keeps track of compiled barrels, and will re-analyze the project whenever they are modified, or created, or deleted.

  • It works great, thank you! All problems due to my workspace structure are gone.

    There's something new. Strings references in XML seem to be broken: "Undefined symbol TestBarrel.Strings...." both for XMLs in barrel and in project, that depends on this barrel.

    I see it even here https://github.com/markw65/Barrels/blob/main/BarrelTest/resources/resources.xml with 2.0.98 installed.

  • There's something new. Strings references in XML seem to be broken

    This doesn't seem to be new (I tested back to 2.0.94), but it is a bug.

    The extension isn't supposed to analyze or optimize barrel projects. If you open an application which uses a barrel, then the barrel's sources/resources should be analyzed as part of that, so you should get the usual links and refs and defs for everything in the application and the barrel. But if you open the barrel itself, the extension should ignore it.

    What's going on here is that the extension is treating the barrel project as a normal project, and things predictably go wrong.

    If you open my Barrels project via the code-workspace, it opens both DataFieldWithBarrels (an application), and BarrelTest (a barrel project), and the error appears when analyzing BarrelTest (and the bug is that it shouldn't be running analysis on BarrelTest at all). If you only open DataFieldWithBarrels, everything works, including @BarrelTest.Strings.TestString that you mentioned.

    I'm sure the extension used to filter out barrels - but I must have broken that at some point. I'll fix it in the next release.

  • The extension isn't supposed to analyze or optimize barrel projects.

    While I understand there's nothing to run and optimize in barrel, I'm curious, why barrel shouldn't be analyzed. For me barrel is a self-sufficient piece of code (unlike the other concept of having some "lib" or "common" directory outside the project root and adding it to "sourcePath" of regular project), since "barrelbuild" as far as I understand can guarantee that. Is this because barrel can have no devices in manifest and then will be built for every (I think) downloaded device, that supports given "minApiLevel"?

    If you only open DataFieldWithBarrels, everything works, including @BarrelTest.Strings.TestString that you mentioned.

    If I apply the following patch:

    diff --git a/DataFieldWithBarrels/barrels.jungle b/DataFieldWithBarrels/barrels.jungle
    index 1b26ba5b..5a4b750c 100644
    --- a/DataFieldWithBarrels/barrels.jungle
    +++ b/DataFieldWithBarrels/barrels.jungle
    @@ -4,5 +4,5 @@
     BarrelTest = [../BarrelTest/monkey.jungle]
     base.barrelPath = $(base.barrelPath);$(BarrelTest)
     
    -CompiledBarrel = "/Users/mwilliams/www/git/Barrels/CompiledBarrel/bin/CompiledBarrel-0.0.0.barrel"
    +CompiledBarrel = "../CompiledBarrel/bin/CompiledBarrel-0.0.0.barrel"
     base.barrelPath = $(base.barrelPath);$(CompiledBarrel)
    diff --git a/DataFieldWithBarrels/resources/layouts/layouts.xml b/DataFieldWithBarrels/resources/layouts/layouts.xml
    index 3935a823..472d9089 100644
    --- a/DataFieldWithBarrels/resources/layouts/layouts.xml
    +++ b/DataFieldWithBarrels/resources/layouts/layouts.xml
    @@ -4,6 +4,7 @@
             <drawable class="Background" />
             <label id="label" x="center" y="center" color="Graphics.COLOR_LT_GRAY" justification="Graphics.TEXT_JUSTIFY_CENTER" font="Graphics.FONT_TINY" />
             <label id="value" x="center" y="center" color="Graphics.COLOR_WHITE" justification="Graphics.TEXT_JUSTIFY_CENTER" font="Graphics.FONT_LARGE" />
    +        <label id="BarrelTest" text="@BarrelTest.Strings.TestString"/>
         </layout>
     
         <!-- Layouts used for the for the four quadrants. -->
    

    and run "node test/test.js --analyze-only --jungle .../Barrels/DataFieldWithBarrels/monkey.jungle", is

    ERROR: Undefined symbol BarrelTest.Strings.TestString at .../Barrels/DataFieldWithBarrels/resources/layouts/layouts.xml:7
    While building '.../Barrels/DataFieldWithBarrels/monkey.jungle
    Error: 'ERROR' level diagnostics were reported
    ...

    what I should expect to see now?