Skip to content
This repository has been archived by the owner on Jun 5, 2020. It is now read-only.
Daniel Wirtz edited this page Jan 20, 2014 · 33 revisions

Welcome to the MetaScript wiki! For now, the wiki contains a few non-trivial insights on writing meta.

Definitions

  • A source is the source that you are working with, containing your custom meta.
  • A meta program is what your source becomes when compiled through MetaScript.
  • A scope contains the compile time options that your custom meta uses.
  • A transformed source is what your source becomes through executing its meta program with a specific scope.

Short statements

//? if (NODE)
buffer.writeInt8(offset, value);
//? else
view.setInt8(value, offset);

This will actually work without using curly braces as long as there is just one line following, because each source line is wrapped by a dedicated write(...) call, resulting in the following meta program:

if (NODE)
  write('buffer.writeInt8(offset, value);\n');
else
  write('view.setInt8(value, offset);\n');

Local vs. global variables, functions or macros

In default notation, variables and functions are local to the file they have been declared in. So, if you intend to declare a global variable or function, just do it explicitly:

Variables:

//? var NODE = true;     // Local
//? NODE = true;         // Global

Functions:

//? function doit() {}   // Local
//? doit = function() {} // Global

White-spaces and line breaks

The MetaScript compiler makes sure to retain any white spaces and line breaks with one important exception: If a meta line or block, which is not a ?= expression, is the only contents of a line, the entire line will be skipped. Let's modify the example from above to demonstrate why this is useful:

if (true) {
    //? if (NODE)
    buffer.writeInt8(offset, value);
    //? else
    view.setInt8(value, offset);
}

The result of the above will not contain any white spaces or new lines where the meta statements are, like for NODE=true:

if (true) {
    buffer.writeInt8(offset, value);
}

Which looks much cleaner.

If you explicitly require indentation, you may use either a ?= expression, the __ variable or even call the indent(str:string, indent:string|number):string utility manually. Example:

// This will be indented (it's a ?= expression):
    //?= 'var i=0;'
// Just like this (it uses manual indentation):
    //? write(indent('var j=0;\n', 4));
// Or this (it prepends __):
    //? write(__+'var k=0;\n');
// But this will not:
    //? write('var k=0;\n');

Results in:

// This will be indented (it's a ?= expression):
    var i=0;
// Just like this (it uses manual indentation):
    var j=0;
// Or this (it prepends __):
    var k=0;
// But this will not:
var k=0;

The __ variable

When inspecting the generated meta program, you will notice that the variable __ is used quite frequently. It stores the indentation level of the last meta block processed. For example ...

//? if (NODE)
    //? include("node-stuff.js");
//? else
    //? include("browser-stuff.js");

... will indent the contents of the included files by '    ' (four spaces), just like before //?, while ...

/*? if (NODE)
    include("node-stuff.js");
else
    include("browser-stuff.js"); */

will not, just like before /*?. When creating custom utility functions, it's of course safe to use this variable on your own.

Other __ prefixed variables

  • __out
    Contains the so far generated output as an expanding array.
  • __source
    Source code of the currently processed source file.
  • __program
    Source code of the currently running meta program.
  • __filename
    File name of the currently processed source file.
  • __dirname
    Directory of the currently processed source file.
  • __version
    MetaScript version used to compile the currently running meta program.

JavaScript meta programming at compile time vs. runtime

JavaScripts surely allows a developer to do lots, if not all, of the work at runtime, switching to different fallbacks in varying environments. We all know this from making stuff work in multiple browsers and in lots of cases this is absolutely legit because there are simply no alternatives.

However, since technology like node.js came up, what we understand by different environments has changed a bit: Imagine you have a library that, for maximum performance, uses ArrayBuffers in the browser and Buffers, which are much faster than ArrayBuffers but not available in the browser, on node. Or node modules vs. polyfills and stuff like that. At worst, the result would be a heavily bloated library that implements lots of the functionality multiple times for the different APIs or at best multiple layers of wrappers around the fundamental types. Now imagine that this library is also meant to be used in the browser and that you do not want the redundant node-functionality to be downloaded every time a user visits it. Or that every microsecond counts because you are writing a realtime game. You see, it will work but is not optimal and you might consider writing some meta that produces a node and a browser version of your library from a single source tree.

Using a traditional preprocessor vs. MetaScript

This is mostly a question of style and a bit of what toolset you require at compile time. Usually, once you require more tools, using MetaScript (on node.js) will provide you with a programmer's paradise where a preprocessor will let you hanging.