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]

  • Why do you use modules? I have many classes with global functions in it and I haven't noticed that it matters whether I have more or less files. IMHO your "problem" isn't the number of files, but the number of modules.

  • I had classes in the past and did observe the same... Modules seem more clean if I use a class for "namespaces" only. More classes add up as well...

  • Have you seen them in modern SDK versions?

    What I meant was, when the enum type itself isn't removed. eg something like:

    function foo(font as Graphics.FontDefinition) as Void {
     // whatever
    }
    
    function bar() {
      foo(Graphics.FONT_TINY);
    }
    

    Even after optimization, foo still takes an enum type. So rewriting bar to call foo(1) would fail to compile, so my optimizer changes it to foo(1 as Graphics.FontDefinition), and the compiler accepts it.

    But in your case, the enum was fully removed, so I change the enum to a typedef, and with the typedef, there's never any need for the cast - it can break code like yours, but it's never needed to fix code like the above.

    but I tend to keep the level as strict as possible

    I agree that its better to do that, and I do plan to fix this...

  • In the past I found out that it makes a difference if you have 1 file containing 100 functions vs 10 files containing 10 functions

    I think is correct on this one. More files don't increase code size, but more modules do, so merging files wouldn't help (but merging modules, or dropping modules would).

    That said, modules are generally better than classes (if you don't actually need it to be a class), and much better than classes with statics (such classes introduce a "shadow" class for the static methods).

  • I do plan to fix this

    Thanks.

    I just wanted you to know (if you don't know this already), that if it is difficult or even impossible to fix this due to whatever reason (including bugs in Garmin's compiler), "replace enums with typedefs" optimization (as well as constants inlining and most of compile time arithmetic optimizations, like replacing '2+2' with '4') can be completely disabled without having negative impact on code size as compiler with "-O 3" does the same thing now on bytecode level.

    such classes introduce a "shadow" class for the static methods

    Is there a documentation for this? I don't use modules, but static classes, as they allow to declare private members. The only downside I know about is that every static class I call stay in memory under "<Global>" section in simulator memory view as 36 bytes object (or something similar).

  • like replacing '2+2' with '4') can be completely disabled without having negative impact on code size as compiler with "-O 3" does the same thing now on bytecode level

    Not quite. Garmin's compiler doesn't do the equivalent of sizeBasedPRE - and trying to do sizeBasedPRE without being aware of all the in-scope constants is kind of pointless.

    That said, for constants, the postBuildOptimizer does a better job for sizeBasedPRE anyway; but I'm reluctant to kill off the source-to-source optimization because Garmin can, and recently did, make changes that break the post build optimizer, and next time it might be much harder to fix.

    Anyway, I have a change that I think will fix this particular issue... it should be out in the next couple of days.

    Is there a documentation for this?

    No, but you can see it in the "-g" output from garmin's compiler.

    eg, given these class declarations:

    class NoStatic {
      function foo() as Void {}
    }
    
    class Static {
      static function foo() as Void {}
    }
    

    you get these CLASSDEFs. Note that NoStatic only has one, but Static has two, with the static methods in the second one.

    globals/NoStatic:
      CLASSDEF
        APPTYPE 127
        EXTENDS @Toybox_Lang_Object
        PARENT globals
          Object 7 CLASS @Toybox_Lang_Object;
          <init> 3 METHOD @<initReturnFunction>;
          foo 1 METHOD @<returnFunction>;
          initialize 1 METHOD @<returnFunction>;
      END
    globals/Static:
      CLASSDEF
        APPTYPE 127
        EXTENDS @Toybox_Lang_Object
        STATICENTRY globals/Static/<statics>
        PARENT globals
          Object 7 CLASS @Toybox_Lang_Object;
          <init> 3 METHOD @<initReturnFunction>;
          initialize 1 METHOD @<returnFunction>;
      END
    globals/Static/<statics>:
      CLASSDEF
        APPTYPE 127
        PARENT globals
          <init> 3 METHOD @<initReturnFunction>;
          foo 5 METHOD @<returnFunction>;
      END

    There is one other issue that I forgot to mention though. Every module-scope method starts by setting "self" to the module that contains it (or '$' if it's at global scope). This takes 7 bytes (one of the optimizations the post-build-optimizer used to do was remove this when it's not needed - but the app store recently started rejecting apps with that optimization). Class methods (static or not) don't do this. So if you have a lot of static methods, its going to be better to use a class, but if its only a few, its better to use a module.

  • Garmin's compiler doesn't do the equivalent of sizeBasedPRE - and trying to do sizeBasedPRE without being aware of all the in-scope constants is kind of pointless.

    Oh, right. My idea was that cases like this:

    import Toybox.Application;
    import Toybox.Lang;
    import Toybox.WatchUi;
    
    class SimpleApp extends AppBase {
        private var value as Number;
    
        enum SomeEnum {
            ENUM_VALUE = 1,
        }
    
        const CONST_VALUE = 2;
    
        function initialize() {
            value = (ENUM_VALUE as Number) + CONST_VALUE + 3 + 4 + (WatchUi.KEY_ESC /* KEY_ESC is 5 */ as Number);
        }
    
        function getInitialView() as [Views] or [Views, InputDelegates] {
            return [new View()];
        }
    }

    are now compiled to:

    globals/SimpleApp/initialize:
        argc 1
        getself
        spush <globals/SimpleApp/<>value>
        ipush1 15
        putv
        return
    globals/SimpleApp/initialize_func_end:
    

    without the help of PMC. There are no signs of "SomeEnum" and "CONST_VALUE", as Garmin can perform OPTIMIZATION_CONSTANT_FOLDING and OPTIMIZATION_LEXICAL_ONLY_CONSTANTS. But I haven't thought that disabling these optimizations in PMC will affect source-to-source PRE (and it is obvious to me now), sorry.

    you can see it in the "-g" output from garmin's compiler

    Thanks. I haven't noticed this before.

    if you have a lot of static methods, its going to be better to use a class

    Yep, that's my case.

  • v2.0.96 is out

    • Match Garmin's handling of add and addAll for tuples (fixes #38)
    • Add preSkipLiterals option (workaround for part of #38)
    • Remove casts when an enum is replaced by a typedef (Fixes #44)
  • disabling these optimizations in PMC will affect source-to-source PRE

    I realized there's another case too. Garmin's compiler doesn't do any kind of copy propagation, so that

    enum {
      ONE = 1,
      TWO = 2,
    }
    
    function test() as Number {
      var x = ONE;
      return x + TWO;
    }
    

    doesn't get constant folded at all. Garmin's compiler produces

        ipush1 1
        lputv 1
        lgetv 1
        ipush1 2
        addv
        return

    so although the enums have gone, it leaves both the variable x, and the addition in the code. With my optimizer you get:

        ipush1 3
        return

    So again, not doing constant folding in the source to source optimizer can lead to missed opportunities.

  • Remove casts when an enum is replaced by a typedef

    Thanks, I confirm it is fixed for "build optimized project".

    However, if I raise "prettierMonkeyC.typeCheckLevel" from "Informative", that I had because of this issue, to "Strict", I see warnings by live code analysis for slightly different case:

    import Toybox.Lang;
    
    const CONST = 0;
    
    enum Values {
        VALUE_1 = false,
        VALUE_2 = CONST,
    }
    
    function processNumberValue(value as Number) as Void {}
    
    function doThings() as Void {
        // Argument 1 to $.processNumberValue expected to be Number but got False or Number as Values
        processNumberValue(VALUE_2);
    }
    

    Is there a way to use different type check levels for pmc-build and pmc-analysis?

    Garmin's compiler doesn't do any kind of copy propagation, so that
    doesn't get constant folded at all

    One more reason to use PMC. Thank you again for the great tool!