view mde/mergetag/DefaultData.d @ 79:61ea26abe4dd

Moved mde/mergetag/parse/parse(To/From) to mde/mergetag/(de)serialize. Implemented (de)serialization of structs.
author Diggory Hardy <diggory.hardy@gmail.com>
date Tue, 05 Aug 2008 11:51:51 +0100
parents 25cb7420dc91
children
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 as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

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, see <http://www.gnu.org/licenses/>. */

/** 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 mde.mergetag.serialize;


/*************************************************************************************************
 * Default DataSection class.
 * 
 * Supported types are given by dataTypes.
 * 
 * Currently DefaultData is only used for headers, and thus the list of supported types has been
 * reduced to just those used in headers. Load order is HIGH_LOW, i.e. existing entries aren't
 * overwritten.
 *************************************************************************************************/
/* 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+1) ~ "if ((id in "~varName(c)~") is null)\n" ~
                    indent(indents+2) ~ 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.
    */
    /+ All types previously supported. Most of these weren't used.
    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[]"];
    +/
    const char[][] dataTypes = ["char[]", "char[][]"];
    
    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 ArgName (T : T[]) {
        const char[] ArgName = ArgName!(T)~`A`;
    }
    template ArgName (T) {
        const char[] ArgName = `_`~T.stringof;
    }
    template Arg(T) {
        mixin(`alias `~ArgName!(T)~` Arg;`);
    }
}