Haxe Macros: Code completion for everything

โ€” Haxe

This post is about Haxe macros, providing us smarter code-completion. In this post we create a class that gives completion automagically based on files on disk, using Haxe macro scripting. This gives us development power, less error-prone code and smaller JavaScript output.

Most developers will recognize this type of class:

class FileNames {
ย  public static inline var BACKGROUND:String = "background.jpg";
ย  public static inline var BALL:String = "ball.jpg";
ย  public static inline var CAR:String = "car.jpg";
}

Why are we using this?

It’s a common class with static references. There are a lot of variations on this class, think of FileNames, XMLNames, AssetNames, URLNames, LibraryNames, FontNames etc.. You name them. All these type of classes have mostly thing in common; they point to static things that refers mostly to info on disk or inside a XML- or JSON-file or something.

You use this type of class to have a reference to a thing on disk, but have it stored in one class to have code-completion on it, make refactoring/searching easier. It’s also inlined which basically means the values are pasted-in-place in the final output.

Why are we writing this manually?

Good question! To be honest, I dislike doing things manually. We are using a computer, so it should help us. I’d like to get warnings when I code something wrong or when I point to things that are removed. Could it possible to fill this class automatically with properties and get warnings when a file or node in XML/JSON-file does not exists anymore?
When you think about it, most information is already available, the content is mostly inside files.

Haxe Macros

The Haxe macro system allows powerful compile-time code-generation without modifying the Haxe syntax.

๐Ÿ˜ฎ What does this mean? In simple words, macros allows you to manipulate you classes when the actual build of your code happens (compile-time). Macros are really just Haxe programs which run while compiling. Haxe is very open. Macros can be used to add properties, to generate pieces of code or even to build complete new classes. Does this sounds helpful or confusing? Coming from an Actionscript background, this concept for me is new and somehow vague.

But.. Let’s try to learn something. Haxe compiler can access your harddisk. That means while the code is compiling, you can run your own code (macros), have ultimate control over classes and also access things like the file-system. Another fun fact, the code completion (in VSCode and HaxeDevelop) under the hood is also provided by Haxe, it is calling a Haxe build command. This is because of the high number of Haxe features and things like type inference; code editors cannot easily handle completion by just parsing the Haxe files.

Getting smart auto-completion in Haxe

So, the Haxe compiler can access our file-system and the auto-completion is using the compiler. With all this info, it should be possible that we write some rules to get smarter auto-completion, right? Imaging that we want the FileNames class (first code example) to be automatically filled with properties, which reflect the files from certain folder on disk. But without writing it manually ๐Ÿ˜ฎ

Well, to start, the class should be empty ๐Ÿ™‚ The computer is wise enough to provide you that info. Let’s clear the class of the first example!

@:build(FileNamesBuilder.build("assets/"))
class FileNames {
ย  // Ha! Nothing in here!
}

Okay, the class empty.

Second thing we need to do: something needs access to this class and provide the properties. You probably noticed the @:build() over there. That is a magic key to access macro scripting for this class, it’s a meta-tag. This meta-tag expects a reference static function build() from a class called FileNamesBuilder. I also provided ‘assets/’ as directory to get the information from.

Let’s see how this builder class could look like. I placed it in a separate file (FileNamesBuilder.hx), to avoid confusion between normal functions and macro functions. This differences is important to make, since they are for different times (compile-/runtime).

import haxe.macro.Context;
import haxe.macro.Expr;

class FileNamesBuilder {
  public static function build() {
    var fields = Context.getBuildFields();
    return fields;
  }
}

What happens over here? The haxe.macro.Context class knows things of your class. With ‘your class’ I mean ‘FileNames’ class, since that is the current context where it is doing things upon. At the moment it get the current fields (properties, variables) from the class, and returns them (This function has a given return type). In our case you and me know there are no fields, but lets keep it clear; if there were any or if we add them manually, they will be remained.

What is a field in Haxe?

The docs are not very clear on what a Field is, but once you know how to use it, it’s ok I guess. Basically a field is what any variable is of a class. You can create them using this class. Not to go into deep programming philosophy, but in Haxe everything in the end is an expression, and a Field is a specific expression to define properties.

In my FileNamesBuilder class, I used this to create a Field:

fields.push({
  name: fileRef.name,
  doc: fileRef.documentation,
  access: [Access.APublic, Access.AStatic, Access.AInline],
  kind: FieldType.FVar(macro:String, macro $v{fileRef.value}),
  pos: Context.currentPos()
});
  • The name property is the variable name. Note, this may not contain all type of chars, so in my builder I replaced some chars like a dot or minus sign to underscores.
  • The doc property (optional) is to provide documentation. IDE’s like FlashDevelop use this.
  • The access property (optional) is to set the access of the new field. Options are (Public,APrivate,AStatic,AOverride,ADynamic,AInline or AMacro) as found in found in haxe.macro.Expr.Access. I wanted to have a public static inline variable, so should be pretty clear what is in the list.
  • The pos property actually defines the position, which is displayed in error messages. Context.currentPos() mostly does the trick.
  • The kind property is to set the field type but also to set the actual value, since both go hand in hand. I set this to String, with as value the name of the file as String. The syntax is kinda weird on this, it’s called reification escaping. More on this topic can be found over here. I am also not completely familiar with the syntax, but try to understand what’s happening. FVar (is an Haxe enumerator) defines a variable field, whereas FFun defines a method and FProp defines a property. With real syntax you can define fields which are variables, properties or methods, so naturally the kind can be one of the three.

To sum it all up: I came up with this FileNamesBuilder class:

/**
 * @author Mark Knol [blog.stroep.nl]
 */
import haxe.macro.Context;
import haxe.macro.Expr;
import sys.FileSystem;

class FileNamesBuilder {
  public static function build(directory:String):Array {
    var fileReferences:Array = [];
    var fileNames = FileSystem.readDirectory(directory);
    for (fileName in fileNames) {
      if (!FileSystem.isDirectory(directory + fileName)) {
        // push filenames in list.
        fileReferences.push(new FileRef(fileName));
      }
    }

    var fields:Array = Context.getBuildFields();
    for (fileRef in fileReferences) {
      // create new fields based on file references!
      fields.push({
        name: fileRef.name,
        doc: fileRef.documentation,
        access: [Access.APublic, Access.AStatic, Access.AInline],
        kind: FieldType.FVar(macro:String, macro $v{fileRef.value}),
        pos: Context.currentPos()
      });
    }

    return fields;
  }
}

// internal class
class FileRef {
  public var name:String;
  public var value:String;
  public var documentation:String;

  public function new(value:String) {
    this.value = value;

    // replace forbidden characters to underscores, since variables cannot use these symbols.
    this.name = value.split("-").join("_").split(".").join("__");

    // generate documentation
    this.documentation = "Reference to file on disk \"" + value + "\". (auto generated)";
  }
}
haxe3 macro filenames autocompletion

Thanks to inlining, the Javascript output is very small, the FileNames class does not even exists as actual class in the final output ๐Ÿ™‚

(function () { "use strict";
  var Main = function() { }
  Main.main = function() {
    console.log("background.jpg");
  }
  Main.main();
})();

Note; I use -D js-flatten -dce full in my compiler arguments.
In HaxeDevelop goto ‘Project’ > ‘additional compiler arguments’ to have the inlining and removal of dead code enabled. This gives very small JavaScript output.

Conclusion

Haxe is a very powerful language since it is open and you can access everything, and even do things while building.

  • Macro scripts are a bit hard to understand at first sight, but when you slowly understand, it gives a lot of extra development power.
  • With smart use of macro scripting, you can get auto-completion on almost anything. In our example we only read a directory, but we can even parse XML/JSON-files and grab information out of that.
  • Having completion on it also means that when a thing is removed from disk, you get a compile error.
  • Of course, this is only a usage for code completion, but you can use the same trick to add fields/properties to any class or type.
  • Using inlining, you have no static instances in a separate class in the final output, which is extreme useful for mobile development.

I hope you enjoyed this post!

Read more

Flash productivity tools: Random instance swapper

โ€” JSFL

In some cases it is nice to have a tool, that just gives that helping hand for boring tasks.

While approaching the deadline of House of Anubis (game), MediaMonks needed a tool that helps with randomizing tiles. The levels has a huge amount of walls and tiles in it’s library, since it is build in Flash with MovieClips on stage. For the quick win, this simple tool allowed to randomize selected clips on stage with a selection from our library. Of course; to refine it, you’ll need more than just a MovieClip swapping tool, but we love to share this tool with you anyway in case you need it.

How to: Select multiple library symbols, select multiple items on stage. Run command. Randomize.

ยป Download : Random instance swapper.JSFL

Find all my JSFL tools on Github.

Read more

Flash productivity tools: Transform selection manager

โ€” JSFL

When you have a selection with multiple MovieClips, you sometimes want to transform (rotate, skew, move, scale) them all, but individually. By default, Flash will transform them like it is one object. I created a tool for that. Just select some items on stage, call the command and set the adjustments. It is also possible to transform them relative from its current transformation.

ยป Download : Transform selection manager.JSFL

Transform selection manager explanation

Find all my JSFL tools on Github.

Read more

Flash productivity tools: Organize layers

โ€” JSFL

It is important to have very clean and well-organized .fla files when you work in a team. At MediaMonks, we have naming guidelines which should be used by all monks. I have created some productivity tools which helps with these rules, let us work better and highly efficient.

This tool automatically renames layers and cleans empty layers. This tool should be used as neurotic as you would use the save-button, and you will have perfectly named layers. We have included a free nazi-tool which detects if you’re using txt or mc as prefix for your instance names, according our MediaMonks coding standards.

ยป Download : Organize layers.JSFL

Organize layers explanation

Find all my JSFL tools on Github.

Read more

Flash productivity tools : advanced library symbol duplicate

โ€” JSFL

duplicate symbolWorking fast and productive is the best thing you can offer your boss ๐Ÿ™‚ But to work fast, you need to take shortcuts. That’s why I love to share this tool with you.

Ever wanted to duplicate a symbol timeline with all of its instances? In Flash, if you do a duplicate of a library item, it does not offer to duplicate its children too, you have to do it manually. Another annoying thing; it does not copy the class references. That’s why I created a tool that does it all. It asks you to give a pre- or postfix for the new names, or you can choose to replace parts of the names.

How to:
Lets say you have a button called RedButton with a RedButtonLabel and a RedButtonBackground and a ButtonArrowIcon as children. You want to create a orange button, which should have the same timeline as the red button, but with another text and maybe another background. (normally, you have to swap them by hand)

Select RedButton in the library. Open the duplicate tool from the commands-menu, add Red in the left ‘replace values’ field and Orange in the right field next to it. Check all instances with Red in the name, and press Ok.

You will see a few new instances in your library. You don’t have to swap instances in the new timelines, they are already swapped over all timelines! Enjoy the new button.

ยป Download : Duplicate advanced.JSFL

Duplicate library item with all children

Find all my JSFL tools on Github.

Read more

Flash productivity tools: Name it right

โ€” JSFL

flash commandsIt is important to have very clean and well-organized .fla files when you work in a team. At MediaMonks, we have naming guidelines which should be used by all monks.

This tool is made to easily assign or change the class of a MovieClip, and uses our naming standards for the linkage names (which is filename dot SymbolName). It also gives you the option to change the instance name over the full timeline and fixes the layer name. The tool has a class-dropdown, pre-filled with Temple-classes, and the classes that are already linked to library symbols in your current document.

ยป Download : Name it right.JSFL

Name it right explanation

I use this panel as complete replacement of the symbol properties window, since it is smarter and less annoying.

Find all my JSFL tools on Github.

Read more