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]

  • Does inline mean that content function will put into call place?

    Does it mean that resignation from functions makes optimization?

  • Does inline mean that content function will put into call place?

    Yes.

    Does it mean that resignation from functions makes optimization?

    The answer to that is complex, and depends on what you're trying to optimize for- there's actually been quite a lot of discussion in this thread about that (although threads in this forum are really hard to follow, and there have been a lot of posts to this one).

    My goal (and I think thats common for monkeyc projects) is to optimize for size. Generally, inlining a single-use function will reduce code size (ie there's an overhead to breaking out a function if you're only going to use it once), and inlining really simple functions is likely to reduce code size - especially if doing so enables other optimizations. But generally, I've found that it's really hard to know in advance *when* inlining will be beneficial, so I was initially hesitant to implement it. 's suggestion was to use an (:inline) annotation, so that the programmer can experiment with it and decide which things to inline. So I did that, and both he and I (if no one else) have found that it works really well. You can just add the annotation, build and run, and look at the code size in the simulator.

    Its also useful for things like type-safe null removal. If I have a variable foo, and the type checker knows its String or Null, but I know its definitely non-null, I can write var as String, which tells the type checker its definitely a String. But now we've lost any info the type checker actually had. Suppose I made a mistake, and var is really Number or Null. The type checker will *still* just believe that its a String, even though it knew prior to the annotation that it wasn't one.

    So instead, I can write

    function nonNullStr(str as String?) as String { return str as String; }

    And now, when I call nonNullStr(var), the type checker will tell me if var might be a number. But generally, you wouldn't want to do that in your final app, because there's a size and speed overhead in doing so. It turns out this is one of the functions that is guaranteed to be smaller when inlined, so my optimizer will in fact inline it, no matter what, even without an inline annotation. So now you can write safer code, with no overhead.

  • Can you give an example how you use nonNullStr? Is it to simplify these:
    if (s != null) {
        var i = (str as String).find(".");
    }

  • I have now a warning that I can't understand:
    INFO: fenix6: source/Field.mc:992,24: While inlining convertToString: Failed to resolve 'query'

        hidden function searchVal2OtherVal(query as Object, id as String, keyPrefix as String, keyPostfix as String, maxSize as Number) as String? {
            var result = null;
            var queryStr = convertToString(query, null);
            if (queryStr != null) {
                //...
            }
        }
    
        (:inline) // inline == no_inline
        function convertToString(value as Object or Null, defaultValue as String?) as String? {
            // return value != null && value has :toString ? value.toString() : (value has :format ? value.format("%d") : defaultValue);
            return value != null ? "" + value : defaultValue;
        }
    

    I use convertToString in another place and there it works.

  • Can you give an example how you use nonNullStr?

    Seems like a safer alternative to an explicit cast, if I'm not mistaken.

    So in your example...

    if (s != null) {
        var i = (s as String).find(".");
    }

    ...if s was something like Number or null, the compiler would happily cast it to String and the code would fail at runtime.

    But with nonNullStr, we could safely write...

    if (s != null) {
        var i = nonNullStr(s).find(".");
    }

    ...which would produce a compile-time error for Number or null, but build successfully for String or null.

    Personally I think Garmin should just fix their type system so that something like if (s != null) narrows types properly (e.g. changes "String or Null" to "String", for the body of the if statement.)

    As nice as tool like this is, a lot of it is simply about filling the void in existing CIQ functionality.

  • It would also help if explicit casts between incompatible types would fail at compile-time. For example, in typescript you can't cast a string to a number (for example.) If you really need to cast between incompatible types, you have to cast to "any" first.

    For example, the following typescript code won't compile:

    let x: string = "42";

    let y = x as number;

    The following typescript code will compile (although ofc it will product incorrect results at runtime):

    let x: string = "42";
    let y = x as any as number;

  • I don't use extension, even  VC,  I was just wonder and have 10kb saving in memory :-)

    Nobody write function to use it only once, - do don't amylase this case. I think even one line func is better for size than inline - typically inline function are used to optimise speed.

    In my opinion inline was very important for const and enums to changing from var to literal.- and it's enough to save most of possible memory. If somebody says - I saved 40kB it means code was terrible :-)

    btw Gavriel  example

     (:inline)
    hidden function strfHrZone(format as String, extHr as AlphaNumeric, extZone as AlphaNumeric, intHr as AlphaNumeric /*,intZone as AlphaNumeric*/) as String {
    var name = mName != null ? mName : "";
    log("strf: " + format + ", " + extHr + ", " + intHr + ", " + name + ", " + extZone);
    return Lang.format(format, [extHr, intHr, name, extZone /*,intZone4*/]);
    }

    // to run you have to add additional
    //scope {} because of doubled var name
    //don't think you mangle var's name
    function testInline()
    {
    var x = strfHrZone(...),
    y = strfHrZone(...);
    }
  • Can you give an example how you use nonNullStr

    Its basically for the situations where you would use "!" (as in typescript) if the compiler supported it.

    Its where *you* know its non-null, but the compiler thinks its String or Null. You *could* say (s as String), but that throws away all type checking. With nonNullStr(s) you're telling the type checker "This thing that you already know is String or Null is not null". With (s as String) you're saying "I don't care what you think you know about s, it's actually a String".

  • Nobody write function to use it only once

    Actually, they do. It's remarkably common. But even if that were not the case in general, it can happen in monkey c because you have a cut down version of the program that runs on an fr235, and a full-power version that works on a fenix5xplus. The cut down version may only use the function once, while the full-power version uses it several times.

    btw Gavriel  example

    Not sure what you're getting at here? The inliner renames variables, adds scopes, and qualifies non-local names as necessary to make the code work. If you have an example where it doesn't, I'd like to see it.

    If I fix up your example (define AlphaNumeric, log, mName, and provide actual arguments to the two calls to strfHrZone), the compiler warns that it didn't inline the two calls because they need to be in assignment context (meaning the call has to be the RHS of an assignment; and a declaration isn't an assignment).

    If I fix that:

        typedef AlphaNumeric as Numeric or String;
        var mName as String? = null;
        function log(s as String) {}
        (:inline)
        hidden function strfHrZone(
            format as String,
            extHr as AlphaNumeric,
            extZone as AlphaNumeric,
            intHr as AlphaNumeric /*,intZone as AlphaNumeric*/
        ) as String {
            var name = mName != null ? mName : "";
            log(
                "strf: " +
                    format +
                    ", " +
                    extHr +
                    ", " +
                    intHr +
                    ", " +
                    name +
                    ", " +
                    extZone
            );
            return Lang.format(format, [extHr, intHr, name, extZone /*,intZone4*/]);
        }
    
        // to run you have to add additional
        //scope {} because of doubled var name
        //don't think you mangle var's name
        function testInline(
            format as String,
            extHr as AlphaNumeric,
            extZone as AlphaNumeric,
            intHr as AlphaNumeric /*,intZone as AlphaNumeric*/
        ) {
            var x, y;
            x = strfHrZone(format, extHr, extZone, intHr);
            y = strfHrZone(format, extHr, extZone, intHr);
        }
    

    I get:

        typedef AlphaNumeric as Numeric or String;
        var mName as String? = null;
        function log(s as String) {}
        function testInline(
            format as String,
            extHr as AlphaNumeric,
            extZone as AlphaNumeric,
            intHr as AlphaNumeric /*,intZone as AlphaNumeric*/
        ) {
            var x, y;
            {
                var name = mName != null ? mName : "";
                log(
                    "strf: " +
                        format +
                        ", " +
                        extHr +
                        ", " +
                        intHr +
                        ", " +
                        name +
                        ", " +
                        extZone
                );
                x = Lang.format(format, [extHr, intHr, name, extZone]);
            }
            {
                var name = mName != null ? mName : "";
                log(
                    "strf: " +
                        format +
                        ", " +
                        extHr +
                        ", " +
                        intHr +
                        ", " +
                        name +
                        ", " +
                        extZone
                );
                y = Lang.format(format, [extHr, intHr, name, extZone]);
            }
        }
    

  • It would also help if explicit casts between incompatible types would fail at compile-time

    I made that suggestion here