WinDart - an Experiment

Compiling Dart to native Windows binaries???

Hello, world!

To say the least, 2016 has been an interesting year. I’ve really used Dart a lot more, and at this point, it is by far my favorite programming language. And if you’re following this blog, eventually you will notice that I am fascinated with languages, whether spoken languages or programming languages.

So to enhance my understanding of Dart as a language itself, I read the ECMA specification and built an ANTLR parser for Dart. I’m not sure if there are any bugs; eventually they will reveal themselves, if any remain.

Originally, I wanted to use this new parser to compile to x86 Assembly, so that I could, say, build an operating system with Dart. Unfortunately, I am not familiar enough with OS development to properly do this. I settled on the following:

  1. Build a transformer that transpiles Dart to http://masm32.com.
  2. Build a transformer that runs ML and LINK from MASM, to build native Windows binaries.
  3. Come up with some reliable to unit test this flow (not yet done).

And thus began my experiment, “WinDart”. As you can imagine, it is still on. I plan to keep you all updated.

Transformer

WinDart is a barback transformer that overrides dart2js. It looks like this:

import 'package:barback/barback.dart';
import 'src/transpiler.dart';

class WindartTransformer extends Transformer {
  @override
  String get allowedExtensions => ".dart";

  WindartTransformer.asPlugin();

  @override
  apply(Transform transform) async {
    transform.consumePrimary();
    Asset asset = transform.primaryInput;
    Transpiler transpiler = parse(await asset.readAsString());
    transform.addOutput(new Asset.fromString(asset.id, "// :)"));
    transform.addOutput(new Asset.fromString(
        asset.id.changeExtension(".asm"), transpiler.output));
  }
}

So, as you can imagine, all the logic is in the Transpiler class, whatever that is.

class Transpiler extends DartlangBaseVisitor {
    // ...
}

If you are familiar with ANTLR, then you will notice that I opted for a Visitor, instead of a Listener. If not, then I should point out that ANTLR lets you optionally generate a class called a Visitor that lets you manually determine when to enter a ParseTree. Lexically speaking, Dart is freaking complicated (specifically expressions!), so this is absolutely necessary.

Currently, the Transpiler class is kinda messy, and poorly commented, so if you look at it, you’ll probably get lost, or judge me a lot, or even both. Who knows?

Tree View

To get anywhere using dart_parser, you need some way to see the parse trees it generates before coding logic to handle them. ANTLR includes a tool called grun that does this for you. It also has a JetBrains plugin available that lets you preview ANTLR rules. I! CAN! NOT! STRESS! HOW! USEFUL! THIS! IS!

This is the tree built for this simple script:

main() {
    print("Hello, world!");
}

Tree #1

I had to zoom out a lot to squeeze it all in. And if you didn’t believe me about the expression complexity, here is the section just for the expression "Hello, world!"

Tree #2

Perhaps this complexity is the beauty of Dart.

The Theory

So, once you can parse Dart, you need to actually do something with it. WinDart is lacking the overwhelming majority of Dart features, as it is in fact a new compiler.

Assuming you know MASM, it helps to be able to represent MASM values in Dart.

class MasmValue {
  ExpressionContext source;
  String before = "";
  var _value;
  String masmType;

  get value {
    if (requiresAddr)
      return "addr $_value";
    return _value;
  }

  set value(val) {
    _value = val;
  }

  bool requiresAddr = true;

  MasmValue(
      {ExpressionContext this.source, value: "NULL", bool this.requiresAddr: false, String this.masmType: "db"}) {
    this.value = value;
  }
}

Some logic was included to prefix pointers with "addr".

The most complicated part of the experiment is transforming Dart expression contexts into MasmValue instances. My current implementation is called MasmResolver:

MasmResolver

This will be covered further in-depth in the next part of this series. By then, it will be looking quite a bit better.

In the Next Part

We’ll compile this:

import 'package:windart/defs.dart';

@WinApi()
external int MessageBox(int hWnd, String lpText, String lpCaption, int uType);

@WinApi() external void ExitProcess(int uExitCode);

@WinMain()
main(List<String> args) {
  MessageBox(null, "Hello, world!", "via WinDart", 0);
  ExitProcess(0);
}