diff mde/file/mergetag/DefaultData.d @ 81:d8fccaa45d5f

Moved file IO code from mde/mergetag to mde/file[/mergetag] and changed how some errors are caught.
author Diggory Hardy <diggory.hardy@gmail.com>
date Fri, 29 Aug 2008 11:59:43 +0100
parents mde/mergetag/DefaultData.d@61ea26abe4dd
children 01f4f5f1acc9
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/file/mergetag/DefaultData.d	Fri Aug 29 11:59:43 2008 +0100
@@ -0,0 +1,181 @@
+/* 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.file.mergetag.DefaultData;
+
+public import mde.file.mergetag.iface.IDataSection;
+import mde.file.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;`);
+    }
+}