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]

  • Garmin's compiler now does the same thing

    Could you, please, add an option to disable this (or disable automatically based on compiler version)?

    I have lots of warnings for such cases:

    enum Values {
        VALUE_1 = false;
        VALUE_2 = 0;
    }
    
    function processBooleanValue(value as Boolean) as Void {}
    function processNumberValue(value as Number) as Void {}
    
    function doThings() as Void {
        processBooleanValue(VALUE_1);
        processNumberValue(VALUE_2);
    }

    as optimizer does the following:

    typedef Values as Boolean or Number;
    
    function processBooleanValue(value as Boolean) as Void {}
    function processNumberValue(value as Number) as Void {}
    
    function doThings() as Void {
        processBooleanValue(false as Values);
        processNumberValue(0 as Values);
    }
    

    that causes:

    Passing 'PolyType<$.Toybox.Lang.Boolean or $.Toybox.Lang.Number>' as parameter 1 of non-poly type '$.Toybox.Lang.Boolean'.
    Passing 'PolyType<$.Toybox.Lang.Boolean or $.Toybox.Lang.Number>' as parameter 1 of non-poly type '$.Toybox.Lang.Number'.

  • Hmmm. To me it looks a bit strange what you do. Why do you mix types in an enum?

  • I know it looks weird, but I want these values to be grouped to a named set of constants. In other functions I use this values in a more natural way - I have "value as Values" function argument and process them using switch.

    I have about 5 such enums, and most of them are from my early days with Monkey C. At first there were only numbers in these enums, but later I added booleans there. Typically, these enums represent something similar to "don't do this", "do this 1 time", "do this 2 times", "...", "do this as many times as necessary". I could've used negative values for first and last cases, but booleans look more natural to me here. Probably, I should change this, but "What do we say to the god of refactoring?" "Not today".

  • But you realize that you're asking for a tool used by many to be refactored to acomodate your lazyness?

  • If you phrase it this way, then my answer is "yes, probably, you're right".

    I see this as a series of facts: "Monkey C allows you to create such enums", "Monkey C allows you to pass enum values to functions with enum-typed arguments", "Monkey C allows you to use enum values as basic-typed constant values and pass them to functions with basic-typed arguments", and I use all of these in my code, that is fully valid for "-l 3" compiler option.

    If markw65 thinks, that optimizer shouldn't or can't process such cases (or give a possibility to leave things as they are), then I can live with this as I do now. I've asked only because the "replace enums with typedefs" functionality was mentioned in the discussion, and this optimization (that is obsolete now) sometimes modifies code in the way it cannot be compiled without warnings anymore.

  • Ok, you're right, Somehow I missed the part that the unoptimized code works Slight smile

  • If markw65 thinks, that optimizer shouldn't or can't process such cases

    Sorry, I've not been getting notifications for some reason, and didn't see this until now.

    This really comes down to issues in Garmin's type checker, such as this one.

    In both that case, and yours, Garmin's type checker is happy to accept 'enumValue', but rejects 'enumValue as enumType'

    But most of the problems I was trying to work around by inserting those type assertions are for cases when the enum type doesn't actually get removed from the program (eg when its a Toybox type), and when variables or arguments are declared as taking that enum type. In that case, the substituted enum values have to be cast back to the enum type.

    But I think in the case that the whole enum is removed, it's always possible to just substitute the values without a cast. So there's probably an easy fix for your case.

    But note that you can always just set prettierMonkeyC.typeCheckLevel="Off". All that does is turn off Garmin's type checker when it's building the optimized source. It doesn't affect what my extension reports in the live analysis, or at build time, and it (obviously) doesn't affect what Garmin's type checker reports when it's building the unoptimized source. So as long as you sometimes build the unoptimized project, you really don't lose much...

  • I see why I wasn't getting notifications. I created a test garmin connect account so I could upload .FIT files generated by a project I'm working on without them getting mixed up with my actual workouts, but that meant I was logged in to the forums with that account too. "3647548" is me.

  • This really comes down to issues in Garmin's type checker, such as this one.

    Yes, thanks, I've seen it before in plugin's readme, and at first thought this was it. But later I saw yours

        foo(Zero as Foo); // Works, as expected
        foo(0 as Foo);    // Works, as expected

    and considered that my case is something different, as I have warnings for arguments, while you haven't. If this is all the same, then OK.

    cases when the enum type doesn't actually get removed from the program (eg when its a Toybox type)

    Have you seen them in modern SDK versions? I've inspected assembler.txt from "--debug-log-level 3" for unoptimized source recently and haven't found any mentions of both custom and Toybox enums, as all of them were replaced with their values in the bytecode, including FontDefinition, ColorValue and TextJustification enums. Are there any others, that stay in bytecode as references to values and not as values themselves? I use "-O3" optimization level, btw (and don't know, why would anyone ever want to use other values, as there, probably, wouldn't be any speed optimizations due to the larger amount of bytecode is being generated, that contains the same set of opcodes as "optimized for size" opcodes, and speed-optimized code is executed longer, as I think, then size-optimized basically due to the longer code, but this is the topic for separate discussion).

    prettierMonkeyC.typeCheckLevel="Off"

    Thanks, I use strict level for unoptimized builds and informative (due to the issue above) for optimized ones already. I know, this is only a compile time thing, and PRG files will be the same for all of these levels, but I tend to keep the level as strict as possible to have some certainty that both I and the optimizer produce the safest possible code.

  • I would have some other question:

    Simple Classes

    In the past I avoided to create "simple" classes and used Arrays + static functions instead as this decreased the final watchface size. I'll give you an example:

    class Point {
    
        var x as Numeric;
        var y as Numeric;
    
        function initialize(x as Numeric, y as Numeric) {
            self.x = x;
            self.y = y;
        }
    
        function rotate(center as Point, angle as Numeric) as Void {
    	
    		var a = Math.toRadians(angle);
    	
    		var s = Math.sin(a);
    		var c = Math.cos(a);
    
    		// translate point back to origin
    		var x2 = x - center.x;
    		var y2 = y - center.y;
    
    		// rotate point + translate point back
    		self.x = (x2 * c - y2 * s) + center.x;
    		self.y = (x2 * s + y2 * c) + center.y;
    	}
    }

    I would use following alternative instead:

    module Point {
    
        function rotate(center as [Numeric, Numeric], angle as Numeric) as [Numeric, Numeric] {
    	
    		var a = Math.toRadians(angle);
    	
    		var s = Math.sin(a);
    		var c = Math.cos(a);
    
    		// translate point back to origin
    		var x2 = x - center[0];
    		var y2 = y - center[1];
    
    		// rotate point + translate point back
    		var x = (x2 * c - y2 * s) + center[0];
    		var y = (x2 * s + y2 * c) + center[1];
    
            return [x, y];
    	}
    }

    Do you think it would be feasible and possible to also optimize such classes, maybe with the help of manual defined annotations?

    Utility Files

    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 and the difference was quite large if I remember correctly (~2kB in my real life example back then). Files only containing static functions or containing a module could be summarized in a single file - is this something you consider? Any thoughts on this idea?