Metaprogramming
Metaprogramming is when you use a program to write another program. Metaprogramming is a powerful tool, allowing you to save time and boilerplate code by automating programming tasks. Metaprogramming comes in a variety of forms depending on the language, and MiniD has its share.
Closely related to metaprogramming is a concept known as introspection. Introspection is the act of a program determining something about its own structure. This comes up a lot in metaprogramming since many times, you'll be generating code or functions based on some already-existing program structures.
Contents
- How do you determine the type of something?
- How do you determine the name of something?
- How do you list all globals?
How do you determine the type of something?
Let's start with a really simple one. To find out the type of any value, you can use the typeof function defined in the base library. The name is inherited from D but it's just a normal function. The typeof function takes a single value and returns a string telling you what type it is:
local x = 5 local y = "hello" local z = {} writeln(typeof(x)) // prints "int" writeln(typeof(y)) // prints "string" writeln(typeof(z)) // prints "table"
There are 15 types and therefore 15 possible return values: "null", "bool", "int", "float", "char", "string", "table", "array", "function", "class", "instance", "namespace", "thread", "weakref", and "nativeobj".
The typeof function is especially useful for switches, but if you're just looking to see if a value is a specific type, there are also functions that take a value and return a bool telling whether or not they are of that type. They are isNull, isBool, isInt, isFloat, isChar, isString, isTable, isArray, isFunction, isClass, isInstance, isNamespace, isThread, isWeakref, and isNativeObj.
local x = 5 writeln(isInt(x)) // prints "true" writeln(isChar(x)) // prints "false"
How do you determine the name of something?
Classes, namespaces, and functions have names. If you want to get the name of one of these, you can use the nameOf function defined in the base library. You just pass it an object of one of these three types and it'll give you a string.
class A { function foo() {} } writeln(nameOf(A)) // prints "A" writeln(nameOf(A.foo)) // prints "foo"
How do you list all globals?
I get this one a lot. It's really simple:
dumpVal(_G)
The dumpVal prints a textual representation of the given value to the console. _G is the namespace which holds the top-level globals. When you dump a namespace, it prints out the names of all the fields in it.
Okay, so it's not all that pretty, and it's not in alphabetical order. We can do better:
foreach(name; hash.keys(_G).sort()) writeln(name)
All we've done here is get the keys of _G (the names of all the globals), sorted the array, and printed out each name on its own line.
You want more? You want an exhaustive, recursive listing of all globals, including in module namespaces? Oh fine. This is still relatively simple. All we're doing is writing a recursive tree walk. We also keep namespaces in a "seen" table so that we don't print them out more than once, and to avoid infinite recursion:
module test function printGlobals() { // This table will be used to keep track of which namespaces we've seen local seen = {} // The worker function. This takes a namespace to print and an indent level. function printImpl(ns: namespace, tab: int) { // Indentation strings local indent1 = "\t".repeat(tab) local indent2 = indent1 ~ "\t" // Special-case the name printing for _G since it has no name if(ns is _G) writeln(indent1, "_G:") else writeln(indent1, nameOf(ns), ":") // If we've seen this namespace already, just print out ... if(seen[ns]) writeln(indent2, "...") else { // Otherwise, add it to the seen table seen[ns] = true // Go through the names. We're sorting case-insensitvely here. You // could sort on other criteria - place sub-namespaces first, whatever foreach(name; hash.keys(ns).sort(\a, b -> a.icompare(b))) { local v = ns.(name) // Recurse if the member is a namespace, else just print its name if(isNamespace(v)) printImpl(v, tab + 1) else writeln(indent2, name) } } } // Start it! printImpl(_G, 0) } printGlobals()
