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

API Introduction

Note: this part of the specification is not implementation binding -- that is, this only describes how the official implementation's API works. Other implementations are free to design their APIs in any way.

MiniD is not really designed to function entirely as a standalone language. In fact, its main purpose is, like Lua, to be an "extensible extension" language. It's "extensible" in that it's possible to add new functions and objects to the language to give it more capabilities than the standard libraries provide. It's an "extension" language in that it makes it possible to add programmability to a native host program, so that new code and behaviors can be programmed into an application which has already been written and compiled. Both of these behaviors imply a close relationship between the host application and the MiniD runtime.

The native API provides this relationship. When you want to add scriptability to your native application, you use the native API to load and run scripts and to provide application-specific types and functions to MiniD.

This page documents some of the fundamental ideas of and overall organization of the native MiniD API.

API Layers

The native API is designed in layers, with the lowest level being the only one that directly communicates with the internal state of the library, and the others built on top of it and on each other.

The lowest-level is the "raw" API and generally consists of small atomic operations, such as moving a value from one place to another or creating an object. This mostly corresponds to the minid.interpreter module.

The next level is the "extended" API, held in minid.ex, which is built entirely upon the raw API, and does not communicate with the library internals at all. The extended API contains functions that automate a lot of repetitive native API tasks, such as checking parameter types, looking up names, and building strings piecewise. All the MiniD standard libraries are written using just the raw and extended APIs. (That's kind of a lie; some of them dip into some library internals to get better performance. But they could, theoretically, be written entirely using the public API.)

Finally the top level is the binding library. This is an almost-automatic way of connecting native and script code, using compile-time reflection to automatically generate shim functions and types so that MiniD can call D functions and instantiate D objects. This requires little to no a priori design decisions to hook MiniD up to the host application, and in fact third-party libraries that were not designed for use with MiniD at all can be bound to it. You may have to pay a price of speed and memory for this convenience, however. Designing a library to work directly with MiniD will often be faster than binding it after the fact. But the binding library still remains a valuable tool to quickly expose native code (especially that which isn't necessarily speed-critical) to the language.

The Stack

MiniD, for the most part*, has its own memory management separate from the D garbage collector. Because of this, it would not be safe to hand off pointers to internal MiniD memory to D, as the pointers could be stored away somewhere where the MiniD GC would not be able to see it and would accidentally collect it. To solve this problem, all interaction with MiniD is performed on a stack of values visible to the library user. Objects can then be moved around and manipulated without ever actually getting a pointer or reference to them. This is the same API design as used in Lua and Squirrel.

The stack is not a "pure" stack; rather it's more like a resizable array. You can push and pop items, but most API calls also allow you to access any item on the stack directly. Each function has its own stack space and cannot access the stack space of any other currently-executing function. Therefore all accesses to the stack will be relative to the currently-executing function's stack frame.

The MiniD stack grows upwards, meaning that as you push items, their indices will be increasing positive integers. In general, API calls that push items onto the stack will return the index of the items they push. When you access stack items directly, though, you can use either positive or negative indices. Positive indices are absolute and always refer to the nth item on the stack. Negative indices are relative and refer to the items on the stack in reverse, from the top of the stack. So -1 is the item on top of the stack, -2 is the item below that, and so forth.

There is one "special" index on the stack, and that is index 0. This corresponds to the 'this' parameter that is passed to every MiniD function. The 0th item may never be replaced or removed.

* There are a few areas - almost all of them having to do with D classes - where MiniD will have to allocate memory on the D heap. However, in every case, MiniD will also keep track of that reference that it holds, and the D GC will be able to see it.

Functional Equivalence to MiniD

What that means is that virtually everything you can do in MiniD, you can do in the native API. There are a couple language features - upvalues come to mind - that can't be perfectly emulated, but for the most part, if you can do it in MiniD, there's a way to do it natively. Lua, for example, does not provide any API calls for the mathematical operations, even though they could possibly trigger a metamethod call. You then end up with a problem when trying to write generic code - if you want a generic "sum" function to work, you have to check that all the values are numbers, and if not, see if they have metamethods, and so on. In MiniD, you can use the add API function, and it will behave exactly like the + operator in MiniD, calling metamethods and all.

One other thing to note is that how you do something in the native API is usually the same as how you do it in the language. For example, there is no separate function for resuming coroutines. You simply call it as if it were a function, just like in the language.

VMs, Coroutines, and Concurrency

The MiniD library is designed so that it is entirely reentrant. What this means is that it depends on no global state; all state relating to the VM is kept in a structure that you allocate. Within a single VM, multiple MiniD threads (coroutines) can exist, each with their own value and call stacks, but which share a single global state (a single VM). The VM, then, holds all common resources - the global namespace hierarchy, the current exception and exception traceback info, interned objects, and so forth. Since the library is reentrant, you can create multiple VMs, initialize them separately, and they will be completely independent of one another.

This mostly has implications for multithreaded programs (that is, host programs that are multithreaded). The library is not explicitly designed with multithreading in mind, and so concurrent access to the threads and data that belong to a single VM is unsafe. However, if no two threads are accessing a single VM at the same time, it is safe. This could be accomplished by creating a light wrapper object around a VM and requiring that all threads acquire a lock on it before accessing it. Alternatively, each thread can have its own MiniD VM object and not have to lock anything.

Another use for reentrancy is for sandboxing. You can perform sandboxing within one state, but if you want to have two entirely different environments in which code is to be executed (say, one for untrusted code and one for built-in scripts, or even more disparate than that), making two VMs with different libraries loaded into them might be easier.