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]

  • or maybe we found another compiler bug

  • or maybe we found another compiler bug

    I'm actually starting to think thats it.

    You know that Garmin recommends using full names "for speed"? ie $.Rez.Strings.string, rather than Rez.Strings.string.

    I think whoever wrote that assumed the compiler would generate

     

      spush globals/Rez/Strings
      mget
      spush string
      getv

    which would definitely be both small and fast.

    But it actually generates

      spush globals
      mget
      spush Rez
      getv
      spush Strings
      getv
      spush string
      getv

    which is exactly what Rez.Strings.string would have generated (actually, I think older compilers may have generated something very slightly worse without the $).

    So I think what's going on is that the data structures I was worried about, are generated when you declare the module. So they exist whether or not you use import/using. But they're only *used* if you use import/using.

    But this means that even for a module declared in the global scope, its worth importing it.

    // file1.mc
    module MyModule {
      function foo() {}
    }
    
    // file2.mc
    function bar() {
      MyModule.foo();
    }

    If you add "import MyModule;" to file2 (but otherwise leave it unchanged), the code for MyModule.foo gets smaller, (and probably faster) because it does

      spush globals/MyModule
      mget
      spush foo
      getv

    instead of

      lgetv 0  // "self"
      spush MyModule
      getv
      spush foo
      getv

    Note that exactly how much faster will depend on where you're calling it from. In my example, bar is in the global scope, so self is already "globals", and getv should be almost as fast as mget. But if you called MyModule.bar from inside a class, it would have to lookup MyModule in the class (and fail), lookup MyModule in all the super classes (and fail) and then follow the module chain outwards, looking it up in each until it got to the global scope.

    So I'm revising my previous statement; I think this optimization could be very helpful...

  • OK, so if this is of the rare optimization when it is both faster and smaller code, then I guess we'll soon see it in 2.0.50 Slight smile

    I'll vote on your bugreport if you decide to bother opening one.

  • For my app:

    Before: text: 11336 data: 4453

    After: text: 11212 data: 4453

    But I've found couple of issues. One of my tests failed. It has code like

    module A {
      module B {
        var x as Number = 0;
      }
      const K = B.x;
    }
    
    function foo() {
      System.println(A.B.x);
      A.B.x++;
      System.println(A.K);
    }

    If I don't "import A.B" this prints 0, then 0. If I do import it (and change A.B to B), it prints 0 then 1.

    As far as I can see, K gets initialized the first time you "mention" A.

    So in the original, at the call to System.println(A.B.x) A's init method gets called, and K gets initialized to 0.

    After adding the import, it becomes "System.println(B.x); B.x++", neither of which mention A, so K doesn't get initialized until System.println(A.K); at which point x is 1, and K gets set to 1.

    So there are potential (if rather subtle) behavior changes.

    I can't quite see where this behavior comes from though. There's nothing obvious in the byte code. But I think it must be a side effect of getm/getv. When either fetches a module, if the init method hasn't been called yet, they call it.

    There's one other problem I've not got to the bottom of yet, so not sure if there's a bug in my implementation, and its doing something incorrect, or if its another issue like this one (or maybe even the same thing).

    [EDIT: It turns out this is a pretty serious bug. I'm going to have to restrict this pretty severely to avoid that] 

  • It's not as bad as I thought. None of the Toybox modules have this problem, and it's never a problem for "top level" modules (ie modules declared in the global scope). For nested modules, I can check whether there might be an init method in any of its parent modules, and simply give up.

    This means that I can still "import Rez", I just can't import Rez.*. And my project declares a couple of modules, but they're both top level - and they don't declare any variables that would need an init method anyway (so if I did add any nested modules, the optimization would still work). So now I get:

    Before: text: 11336 data: 4453

    After: text: 11224 data: 4453

    So avoiding the bug cost me 12 bytes.

  • then I guess we'll soon see it in 2.0.50

    Its now in v2.0.50 :-)

  • yes! 122 bytes saved

  • FYI: Usually I use the latest prod sdk (4.1.7), but I was checking out something else in 4.2.0 beta2, and I noticed that in 4.2.0 the data size is 6601 while in 4.1.7 it's only 6523 (code size is identical)

  • in 4.2.0 the data size is 6601 while in 4.1.7 it's only 6523

    Is that a debug or release build? If its debug, it doesn't matter (much)...

  • Debug. I always look at the code/data that you print when I build & run in the simulator.
    Why doesn't it matter? Won't it leave less memory for the app to use?