view mde/mergetag/DefaultData.d @ 17:5f90774ea1ef

Applied the GNU GPL v2 to mde. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 15 Mar 2008 15:14:25 +0000
parents 0047b364b6d9
children 611f7b9063c6
line wrap: on
line source

/* LICENSE BLOCK
Part of mde: a Modular D game-oriented Engine
Copyright © 2007-2008 Diggory Hardy

This program is free software; you can redistribute it and/or modify it under the terms of
the GNU General Public License, version 2, as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

/** This module contains the DefaultData class, and some notes possibly useful for implementing
* other types of DataSection.
*/
module mde.mergetag.DefaultData;

public import mde.mergetag.iface.IDataSection;
import mde.mergetag.exception;

import tango.scrapple.text.convert.parseTo : parseTo;
import tango.scrapple.text.convert.parseFrom : parseFrom;


/**
* Default DataSection class.
*
* Supports most of the basic types supported by D (excluding cent/ucent and imaginary/complex
* types) and array versions of each of these types, plus arrays of strings.
*
* Extending the class to support more types, even custom types, shouldn't be particularly difficult
* provided mde.text.parseTo and mde.text.parseFrom are extended to support the new types.
*/
/* The implementation now uses a fair bit of generic programming. Adjusting the types supported
* should be as simple as adjusting the list dataTypes, and possibly implemting new conversions in
* parseFrom and parseTo if you add new types (e.g. for cent or imaginary/complex types, or user types).
*
* There shouldn't really be any need to adjust the implementation, except perhaps to add new
* functions to the class (such as another type of output where the delegate used in writeAll isn't
* enough).
*/
class DefaultData : IDataSection
{
    //BEGIN META
    /* These functions are used to generate code. Compile-time functions rather than templates are
    * used because they are easier to write and understand. Mixins are used to compile the resultant
    * code. Must be declared before used since forward references aren't supported for compile-time
    * functions. */
    
    // Generate the correct name for each variable type.
    static char[] varName (char[] type) {
        char[] append = "";
        while (type.length >= 2 && type[$-2..$] == "[]") {
            type = type[0..$-2];
            append ~= "A";
        }
        return "_" ~ type ~ append;
    }
    
    // Int-to-string converter, which may not be efficient but will run at compile time.
    static char[] int2str (uint i) {
        char[] ret;
        const digits = "0123456789";
        if (i == 0) ret = "0";
        else for (; i > 0; i /= 10) ret = digits[i%10] ~ ret;
        return ret;
    }
    
    // Generate the code for variable declarations.
    static char[] declerations (char[][] types) {
        char[] ret = "";
        foreach (char[] type; types) ret ~= type ~ "[ID]\t" ~ varName(type) ~ ";\n";
        return ret;
    }
    
    // Purely to add indentation. Could just return "" without affecting functionality.
    static char[] indent (uint i) {
        char[] ret;
        for (; i > 0; --i) ret ~= "  ";
        // This is not executable at compile time:
        //ret.length = i * 4;		// number of characters for each indentation
        //ret[] = ' ';		// character to indent with
        return ret;
    }
    
    /* Generates a binary search algorithm.
    *
    * Currently this is tailored to it's particular use (addTag). */
    static char[] binarySearch (char[] var, char[][] consts, int indents = 0) {
        if (consts.length > 3) {
            return indent(indents) ~ "if (" ~ var ~ " <= \"" ~ consts[$/2 - 1] ~ "\") {\n" ~
                binarySearch (var, consts[0 .. $/2], indents + 1) ~
            indent(indents) ~ "} else {\n" ~
                binarySearch (var, consts[$/2 .. $], indents + 1) ~
            indent(indents) ~ "}\n";
        } else {
            char[] ret;
            ret ~= indent(indents);
            foreach (c; consts) {
                ret ~= "if (" ~ var ~ " == \"" ~ c ~ "\") {\n" ~
                    indent(indents+1) ~ varName(c) ~ "[id] = parseTo!(" ~ c ~ ") (dt);\n" ~
                indent(indents) ~ "} else ";
            }
            ret = ret[0..$-6] ~ '\n';  // remove last else
            return ret;
        }
    }
    
    // Generates the code to write data members (writeAll).
    static char[] writeVars () {
        char[] code = "";
        foreach (i,type; dataTypes) {
            code ~= "foreach (id, dt; " ~ varName(type) ~ ") itemdlg (dataTypes[" ~ int2str(i) ~ "], id, parseFrom!(" ~ type ~ ")(dt));\n";
        }
        return code;
    }
    //END META
    
    /** Data Members
    *
    * These types are all stored directly, as below, are available for direct access. The variable
    * names are created dynamically at compile-time based on the dataTypes list.
    * ------------------
    * int[ID] _int;		// name is type prefixed by _
    * char[][ID] _charA;	// [] is replaced by A
    * ------------------
    *
    * An alternative access method is to use the provided templates:
    * --------------------
    * template Arg(T) {
    *     alias Name Arg;
    * }
    *
    * type y = Arg!(type).Arg;	// example of use
    * --------------------
    * Note: trying to use Arg!(type) to implicitly refer to Arg!(type).Arg causes compiler errors
    * due to the "alias Name Arg;" statement actually being a mixin.
    */
    const char[][] dataTypes = ["bool","bool[]",
                                "byte","byte[]",
                                "char","char[]","char[][]",
                                "double","double[]",
                                "float","float[]",
                                "int","int[]",
                                "long","long[]",
                                "real","real[]",
                                "short","short[]",
                                "ubyte","ubyte[]",
                                "uint","uint[]",
                                "ulong","ulong[]",
                                "ushort","ushort[]"];
    
    mixin (declerations (dataTypes));	// Declare all the variables.
    
    void addTag (char[] type, ID id, char[] dt) {	/// Supports all types listed in dataTypes.
        mixin (binarySearch ("type", dataTypes));
    }
    
    void writeAll (ItemDelg itemdlg) {	/// Supports all types listed in dataTypes.
        mixin (writeVars ());
    }
    
    /* These make no attempt to check Arg is valid.
    * But if the symbol doesn't exist the complier will throw an error anyway, e.g.:
    * Error: identifier '_boolAA' is not defined
    */
    template Arg(T : T[]) {
        const ArgString = Arg!(T).ArgString ~ `A`;
        mixin(`alias `~ArgString~` Arg;`);
    }
    template Arg(T) {
        const ArgString = `_` ~ T.stringof;
        mixin(`alias `~ArgString~` Arg;`);
    }
}