Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Setting up the Host Application

MiniD is meant primarily to be an extension language -- a language used to extend the functionality of a larger program. To that end, the reference MiniD implementation is designed as a library meant to be used within another application. The controlling application is called the "host." Through the library, the host application sets up an environment in which MiniD code can execute, then loads code (either in source code form or from a precompiled binary file) and runs it.

As an example, consider MDCL, the MiniD command-line interpreter. When run in non-interactive mode, it's an extremely simple application. Basically it sets up an extremely minimal environment, then loads and runs a single script, after which it exits. We will create a similar application as an example.

The Context and States

There are two important things which make up the MiniD execution environment. First is the context, represented by the MDContext class. The context holds global state, most importantly the global namespace, as well as the global metatables for the built-in types. Second are the states, represented by the MDState class. States represent a thread of execution, including a call stack and stack of local variables. These also correspond to MiniD's coroutines. Each state is associated upon creation with a single context. In this way, you can think of the relationship between contexts and states as very similar to that of processes and threads in most operating systems -- a process holds global state which is shared amongst the one or more threads which execute the program's code. One important difference is that in MiniD, multithreading is collaborative, meaning only one thread is executing per context at any given time, making synchronization and all the headaches that go along with it unnecessary.

Your app can actually have as many contexts as it wants. Each one is completely independent of all others. They all have different global state and can be running different pieces of code. This situation probably isn't common, but is useful for things like having a sandbox to run untrusted code (into which a "safe" set of libraries has been loaded), or to have multiple native threads running different scripts (since the contexts are independent, no synchronization is necessary).

The two parts of the context -- the global namespace and the global metatables -- deserve mention.

The Global Namespace

MiniD's functions of course have local variables, but global variables are where all kinds of persistent data is kept. When you declare a global in a MiniD module, the variable is actually inserted into the module's namespace, after which it can be accessed from other modules through the module's name. Module (and package) namespaces form a tree, the root of which is the global namespace. This is a nameless, parentless namespace, one of which is created automatically for each context you create. By default it has nothing in it except for a single name, _G, which refers to itself. This is useful for getting a reference to the global namespace in your scripts.

Of course an empty namespace isn't that useful, so any desired standard libraries and user libraries are subsequently loaded into it. The minid.minid module has some useful utility functions for setting up a context and loading the standard libraries into it.

The Global Metatables

The Metamethods section of the spec shows how to define and use the various metamethods available to MiniD objects to customize their behavior. In fact all MiniD types have metamethods, but only tables, classes, and instances can be customized from within the language. Other types have metamethods defined for them by the host application. For example, tables, arrays, namespaces, and threads all have opApply metamethods defined for them by the various standard libraries, without which you wouldn't be able to foreach over them.

A Simple Example

So we want to run some MiniD code. The first thing we have to do is create a context in which to run it. The easiest (and recommended) way of doing this is to use the NewContext function in minid.minid. NewContext will create a context, and based on an optional parameter, will load the various standard libraries into the new context's global namespace and metatables.

Let's create a context that loads all the standard libraries:

module example;

import minid.types;
import minid.minid;

void main(char[][] args)
{
    auto ctx = NewContext();
}

That was easy! By default, all the standard libraries are loaded into the context, so we don't have to pass any parameters.

Next we need a state. Actually, one was created for us -- when you create a context, a state called the "main thread" is created along with it. The main thread is special because it never goes away as long as the context is around, unlike other states which will be collected when they are no longer referenced. To get the main thread, just use the mainThread method of MDContext.

Finally we need to load some code and run it. Let's just assume the first parameter to our program is the name of the script to load, without any extension.

module example;

import minid.types;
import minid.minid;

import tango.text.convert.Utf;

void main(char[][] args)
{
    auto ctx = NewContext();
    ctx.importModule(toUtf32(args[1]));
}

That's it. importModule will search for a module (in either source code or binary form, picking whichever is most recently modified), load it, and then run it. It will also create an entry for the module in the global namespace hierarchy, into which any globals that the module declares are inserted.

We can then compile this program and create a MiniD script like so:

module testscript;

writefln("Hi, I'm a script!");

in a file "testscript.md". Then we can run our example program with "example testscript" and we'll get the output:

Hi, I'm a script!

That's all there is to it.

A Simple Interactive Interpreter

Next we'll make an interpreter that you can use to enter code and have it executed immediately. It will be very spartan, and will probably be kind of a pain to use, but it'll demonstrate some useful stuff.

module example;

import minid.types;
import minid.minid;

import tango.io.Console;
import tango.io.Stdout;
import tango.text.convert.Utf;
import tango.text.stream.LineIterator;

void main()
{
    auto ctx = NewContext();
    auto input = new LineIterator!(char)(Cin.stream);

    while(true)
    {
        // Output prompt
        Stdout("> ").flush;

        // Get next line of text
        char[] line = input.next();

        // Check for end-of-flow
        if(line.ptr is null)
            break;
            
        // Run the code and check for errors
        try
            loadStatementString(ctx.mainThread, toUtf32(line));
        catch(MDException e)
            Stdout("Error: {}", e);
    }
}

First we create a context, like in the last example. Then we set up our input and go into a loop. We print the prompt ("> ") and get a line of text. We check for end-of-flow so that we can exit. The most interesting part is the call to loadStatementString. This function takes at least two parameters, the first of which is a state in which to execute the code, and the second is a string containing the code to compile and execute. This is a "statement string" which differs from a module in that there need not be a module declaration at the top. You can have any number of statements in the statement string.

We check for errors using a try-catch statement. This is certainly much easier than in, say, Lua, where you have to use "protected calls" and check error codes and pop messages off the stack and such. If we wanted, we could skip the error checking altogether and the program would just quit if an error occurred.

This is a pretty crappy interactive interpreter. Thankfully, we don't actually have to write one ourselves. We have MDCL after all, but actually, all of the functionality of MDCL is contained within a library inside MiniD. MDCL itself is one line long. Have a look:

module mdcl;

import minid.commandline;

import tango.io.Stdout;
import tango.io.Console;

void main(char[][] args)
{
    (new CommandLine(Stdout, Cin.stream)).run(args);
}

The minid.commandline module contains a class, CommandLine, which allows you to add an interactive MiniD console to just about any place in your app. All you have to do is give it input and output streams and run it.