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 just tried making up foo and bar, and for me it worked whether in assignment or declaration context. I did find (and have a fix for) an old bug: if you did something like

    (:inline)
    function foo(x as Number) as Number {
        x++;
        return bar(x);
    }
    
    (:inline)
    function bar(x as Number) as Number {
        var tmp = x * 2;
        return tmp;
    }

    Then foo won't be inlined into any later functions, because by the time we've optimized foo it looks like

    (:inline)
    function foo(x as Number) as Number {
        x++;
        {
            var tmp = x * 2;
            return tmp;
        }
    }

    and the current heuristic is to require that the last top level statement be a return (and now the last top level statement is a block).

    I don't think thats related though, because as I said, foo simply doesn't get inlined.

    In any case, do you have an example that you can post that actually fails to delete the inlined functions after inlining?

  • import Toybox.Activity;
    import Toybox.Lang;
    
    class Foo {
        var mHR as Number = 123;
        var mB as Number = 0;
    
        public function compute(info as Activity.Info) as Void {
            var extHr = foo();
        }
    
        (:inline)
        hidden function foo() as String or Number {
            var result = "?";
            var heartRate = mHR;
            var isValidHR = true;
            if (isValidHR) {
                bar(heartRate);
            }
            return result;
        }
    
        (:inline)
        hidden function bar(heartRate as Number) as Void {
            mB = 1;
        }
    }
    

  • Thanks - that does it.

    Not sure whats going on yet, but I did notice its order dependent. If you put compute last, foo and bar both get removed. In fact compute does too - but thats because nothing calls it (normally, it would override the builtin DataField.compute, and the optimizer knows that builtin methods can get called by "magic").

  • I know you're not looking at public/private yet, but in general public is not supposed to be removed (certainly if the analysis is being done based on the class itself only). It could be maybe removed if it's based on all the source files, but then you need to know/guess what public method overrides something (you're right that in my real class Foo extends DataField). But there is a slight chance of removing something that's used. i.e: when your class overrides Ant.GenericChannel, then your initialize looks something like:

    class MySensor extends Ant.GenericChannel {
        public function initialize() {
             GenericChannel.initialize(method(:onMessage), ...);
             ...
        }
        public function onMessage(msg as Message) as Void {...}
    }

    I have public on onMessage, but I guess it would probably work with other modifiers as well (I did not try it)
  • but in general public is not supposed to be removed

    Ok, I'll probably regret this, but here's a challenge: produce an example where the optimizer removes a function that was actually used by the program.

    It could be maybe removed if it's based on all the source files

    The optimizer *has* all the source files.

    i.e: when your class overrides Ant.GenericChannel

    except that the optimizer knows that once it sees :Foo, it can't remove anything named Foo. That includes constants and enums that would otherwise be removed (because all references to them have been replaced by their values), and inlined and non-inlined functions.

  • I'm not saying it happens, just reflected on what you wrote about why Foo::compute was removed.

  • Can you double check the "Functions that can be inlined in assignment scope, can now also be inlined in declarations."? I think it has a bug

    Fixed in v2.0.25

  • I don't see 2.0.25 yet, is there anything I can do? Restarting VSC didn't help

  • Sorry, it sometimes takes a while to show up after I upload it. I usually wait until I see it before posting, but didn't bother this time. Its definitely there now...

  • Hey markw65, previously you've mentioned using a pair of build exclusions.  I'm wondering if there's a way I can use build exclusions if I don't have a pair though. For example:

    (:highmem)
    const highmem = true;

    if (highmem) {
        highMemFunction();
    }
    
    (:highmem)
    highMemFunction() {
        //dostuff
    }

    If I'm only excluding :highmem from 4 devices, I really don't want to define :lowmem and exclude it from the other 50. Is there a way to do this?

    Thanks

    Lance