diff qt/qtd/ctfe/Format.d @ 288:f9559a957be9 signals

new signals and slots implementation
author eldar
date Sun, 08 Nov 2009 19:28:01 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qt/qtd/ctfe/Format.d	Sun Nov 08 19:28:01 2009 +0000
@@ -0,0 +1,693 @@
+/**
+ * Compile-Time String Formatting.
+ * 
+ * Authors: Daniel Keep <daniel.keep@gmail.com>
+ * Copyright: See LICENSE.
+ */
+module qt.qtd.ctfe.Format;
+
+//debug = gb_Format_verbose;
+
+import Integer = qt.qtd.ctfe.Integer;
+import String = qt.qtd.ctfe.String;
+import Tuple = qt.qtd.util.Tuple;
+
+private
+{
+    string stringify(Args...)(size_t index, int alignment,
+                              string opt, Args args)
+    {
+        if( index >= args.length )
+            return "{invalid index " ~ Integer.format_ctfe(index) ~ "}";
+
+        if( alignment != 0 )
+            return "{non-zero alignments not supported yet}";
+
+        foreach( i,_ ; Args )
+        {
+            if( i == index )
+            {
+                static if( is( Args[i] == char ) )
+                {
+                    string r;
+                    r ~= args[i];
+                    return r;
+                }
+                else static if( is( Args[i] : long ) || is( Args[i] : ulong ) )
+                {
+                    int base = 10;
+                    string prefix = "";
+
+                    if( opt == "x" )
+                        base = 16;
+
+                    else if( opt == "xx" )
+                    {
+                        base = 16;
+                        prefix = "0x";
+                    }
+                    else if( opt == "o" )
+                        base = 8;
+
+                    else if( opt == "b" )
+                        base = 2;
+
+                    return prefix ~ Integer.format_ctfe(args[i], base);
+                }
+                else static if( is( Args[i] : string ) )
+                {
+                    if( opt == "x" )
+                    {
+                        return String.hexify_ctfe(args[i][]);
+                    }
+                    
+                    if( opt == "q" )
+                    {
+                        return String.escape_ctfe(args[i][]);
+                    }
+
+                    if( opt == "l" )
+                    {
+                        return Integer.format_ctfe(args[i].length);
+                    }
+                    
+                    // If you don't slice, then the CALLER has to slice the
+                    // string, otherwise CTFE barfs.
+                    return args[i][];
+                }
+                else static if( is( Args[i] Elem : Elem[] ) )
+                {
+                    if( opt == "l" )
+                    {
+                        return Integer.format_ctfe(args[i].length);
+                    }
+                    
+                    string r = "[";
+                    foreach( ei, e ; args[i][] )
+                    {
+                        if( ei != 0 )
+                            r ~= ", ";
+                        r ~= stringify(0, alignment, opt, e);
+                    }
+                    r ~= "]";
+                    return r;
+                }
+                else
+                {
+                    return "{cannot stringify "~Args[i].stringof~"}";
+                }
+            }
+        }
+
+        assert(false);
+    }
+
+    version( Unittest )
+    {
+        static assert( stringify(0, 0, "", 0) == "0" );
+        static assert( stringify(0, 0, "", 1, -2, "abc") == "1" );
+        static assert( stringify(1, 0, "", 1, -2, "abc") == "-2" );
+        static assert( stringify(2, 0, "", 1, -2, "abc") == "abc" );
+
+        static assert( stringify(0, 0, "x", "abc") == `616263` );
+        static assert( stringify(0, 0, "q", "abc") == `"abc"` );
+        static assert( stringify(0, 0, "l", "abc") == `3` );
+
+        static assert( stringify(0, 0, "x", 0x4a) == "4a" );
+
+        static assert( stringify(0, 0, "", [1,2,3]) == "[1, 2, 3]" );
+        static assert( stringify(0, 0, "l", [1,2,3]) == "3" );
+        static assert( stringify(0, 0, "x", [9,10]) == "[9, a]" );
+        static assert( stringify(0, 0, "", ["a","b"]) == "[a, b]" );
+        static assert( stringify(0, 0, "q", ["a","b"]) == "[\"a\", \"b\"]" );
+
+        static assert( stringify(0, 0, "", 'a') == "a" );
+    }
+}
+
+/**
+ * Substitutes a set of arguments into a template string.
+ *
+ * The template string allows for the following escape forms:
+ *
+ * - $$ -- Literal dollar.
+ * - $* -- Next argument.
+ * - $n -- nth argument; 0-9 only.
+ * - ${} -- Next argument.
+ * - ${:f} -- Next argument, using format options "f".
+ * - ${n} -- nth argument.
+ * - ${n:f} -- nth argument, using format options "f".
+ *
+ * formatNamed allows the use of named arguments (given as alternating
+ * name,value pairs), but disallows "next" argument and indexed forms.
+ *
+ * Eventually, alignment and named arguments will be supported.
+ *
+ * Supported formatting options are:
+ *
+ * Integers:
+ * - x -- format integer in hexadecimal.
+ * - o -- format integer in octal.
+ * - b -- format integer in binary.
+ *
+ * Strings:
+ * - q -- quotes the string as a literal.
+ * - x -- formats as hexadecimal data.
+ * - l -- length of the string in decimal.
+ *
+ * Arrays:
+ * - l -- length of the array in decimal.
+ * - Other options are used to control element formatting.
+ *
+ * Params:
+ *  tmpl    = template string.
+ *  args    = arguments to substitute.
+ * Returns:
+ *  formatted string.
+ */
+
+string format_ctfe(Args...)(string tmpl, Args args)
+{
+    string r = "";
+    int argPos = 0;
+    
+    while( tmpl.length > 0 )
+    {
+        bool inExp = false;
+       
+        // Look for a $
+        foreach( i,c ; tmpl )
+        {
+            if (c == '$')
+            {
+                inExp = true;
+                r ~= tmpl[0..i];
+                tmpl = tmpl[i+1..$];
+                break;
+            }
+        }
+
+        // If we didn't find a $, it's because we hit the end of the template.
+        if( !inExp )
+        {
+            r ~= tmpl;
+            break;
+        }
+        
+        // So we're in an expansion/substitution.
+
+        debug(gb_Format_verbose) r ~= "{in exp}";
+
+        if( tmpl.length == 0 )
+        {
+            r ~= "{unterminated substitution}";
+            break;
+        }
+
+        // c is the next character, whilst tmpl is everything left in the
+        // template string.
+        char c = tmpl[0];
+        tmpl = tmpl[1..$];
+        
+        // $$ - escaped $.
+        if( c == '$' )
+        {
+            debug(gb_Format_verbose) r ~= "{escaped $}";
+            r ~= '$';
+            continue;
+        }
+
+        // $n - shortcut for ${n}.
+        if( '0' <= c && c <= '9' )
+        {
+            debug(gb_Format_verbose) r ~= "{shorthand index}";
+            r ~= stringify(c-'0', 0, "", args);
+            continue;
+        }
+
+        // $* - shortcut for ${}
+        if( c == '*' )
+        {
+            debug(gb_Format_verbose) r ~= "{shorthand next}";
+            r ~= stringify(argPos++, 0, "", args);
+            continue;
+        }
+
+        // This means we got a $ followed by something unexpected.
+        if( c != '{' )
+        {
+            r ~= "{malformed substitution}";
+            break;
+        }
+        
+        if( tmpl.length == 0 )
+        {
+            r ~= "{unterminated substitution}";
+            break;
+        }
+        
+        debug(gb_Format_verbose)
+        {
+            r ~= "{parse complex at '";
+            r ~= c;
+            r ~= "':\"" ~ tmpl ~ "\"}";
+        }
+
+        // NOTE: We haven't updated c and tmpl yet.
+
+        {
+            // arg will contain the index of the argument the user wanted
+            // substituted.
+            size_t arg = size_t.max;
+            // fmt will contain any additional formatting options.
+            string fmt = "";
+
+            // If we didn't get a : or }, that means we expect an index.
+            if( !( tmpl[0] == ':' || tmpl[0] == '}' ) )
+            {
+                // So parse it.
+                auto used = Integer.parse_ctfe!(size_t)(tmpl, true);
+                
+                if( used == 0 )
+                {
+                    debug(gb_Format_verbose) r ~= "{used zero of \""~tmpl~"\"}";
+                    r ~= "{invalid argument index}";
+                    break;
+                }
+                
+                arg = Integer.parse_ctfe!(size_t)(tmpl);
+                tmpl = tmpl[used..$];
+                
+                if( tmpl.length == 0 )
+                {
+                    r ~= "{unterminated substitution}";
+                    break;
+                }
+            }
+            else
+            {
+                // Otherwise, the index was elided, which means we want to use
+                // the index of the "next" argument.
+                arg = argPos;
+                ++ argPos;
+            }
+
+            c = tmpl[0];
+            tmpl = tmpl[1..$];
+
+            debug(gb_Format_verbose)
+                r ~= "{index " ~ Integer.format_ctfe(arg) ~ "}";
+
+            // If c is :, then we've got formatting options to parse
+
+            if( c == ':' )
+            {
+                debug(gb_Format_verbose) r ~= "{fmt string}";
+
+                // Look for the closing }.
+                size_t len = 0;
+                foreach( i,d ; tmpl )
+                {
+                    if( d == '}' )
+                    {
+                        len = i;
+                        break;
+                    }
+                }
+                if( len == 0 )
+                {
+                    r ~= "{malformed format}";
+                    break;
+                }
+                fmt = tmpl[0..len];
+                tmpl = tmpl[len..$];
+
+                if( tmpl.length == 0 )
+                {
+                    r ~= "{unterminated substitution}";
+                    break;
+                }
+
+                c = tmpl[0];
+                tmpl = tmpl[1..$];
+            }
+
+            // At this point, we should have the closing }.  If not, someone's
+            // screwed up.
+            if( c != '}' )
+            {
+                debug(gb_Format_verbose)
+                {
+                    r ~= "{expected closing; got '";
+                    r ~= c;
+                    r ~= "':\"" ~ tmpl ~ "\"}";
+                }
+                r ~= "{malformed substitution}";
+                break;
+            }
+
+            // Stringify that bugger.
+            r ~= stringify(arg, 0, fmt, args);
+
+            // When we fall off the end here, we'll continue with the
+            // remainder of tmpl, unless it's empty in which case we're
+            // finished.
+        }
+    }
+    
+    return r;
+}
+
+version( Unittest )
+{
+    static assert(format_ctfe("A: $$", "foo"[]) == "A: $");
+    static assert(format_ctfe("B: a $$ c", "b"[]) == "B: a $ c");
+    
+    static assert(format_ctfe("C: ${}", "foo"[]) == "C: foo");
+    static assert(format_ctfe("D: a ${} c", "b"[]) == "D: a b c");
+    
+    static assert(format_ctfe("E: $0", "foo"[]) == "E: foo");
+    static assert(format_ctfe("F: a $0 c", "b"[]) == "F: a b c");
+    
+    static assert(format_ctfe("G: $*", "foo"[]) == "G: foo");
+    static assert(format_ctfe("H: a $* c", "b"[]) == "H: a b c");
+    
+    static assert(format_ctfe("I: ${0}", "foo"[]) == "I: foo");
+    static assert(format_ctfe("J: a ${0} c", "b"[]) == "J: a b c");
+
+    static assert(format_ctfe("K: ${} ${} ${}", 1, -2, "c"[]) == "K: 1 -2 c");
+    static assert(format_ctfe("L: $* $* $*", 1, -2, "c"[]) == "L: 1 -2 c");
+    static assert(format_ctfe("M: $0 $1 $2", 1, -2, "c"[]) == "M: 1 -2 c");
+    static assert(format_ctfe("N: ${0} ${1} ${2}", 1, -2, "c"[]) == "N: 1 -2 c");
+
+    static assert(format_ctfe("O: ${2} ${0} ${1}", 1, -2, "c"[]) == "O: c 1 -2");
+
+    static assert(format_ctfe("P: ${:x} ${0:x} ${0:o} ${0:b}", 42) == "P: 2a 2a 52 101010");
+
+    static assert(format_ctfe("Q: ${0} ${0:q} ${0:x}", "abc"[]) == "Q: abc \"abc\" 616263");
+    static assert(format_ctfe("R: ${0} ${0:q}", ["a","b","c"][]) == "R: [a, b, c] [\"a\", \"b\", \"c\"]");
+
+    const TORTURE_TMPL = `
+        struct $*Enum
+        {
+            const Name = ${0:q};
+            const string[${:l}] Members = ${1:q};
+
+            ${2} value()
+            {
+                return ${3:xx};
+            }
+        }
+    `[];
+
+    const TORTURE_EXPECTED = `
+        struct FooEnum
+        {
+            const Name = "Foo";
+            const string[3] Members = ["bar", "quxx", "zyzzy"];
+
+            int value()
+            {
+                return 0x42;
+            }
+        }
+    `[];
+
+    const TORTURE_ACTUAL = format_ctfe(TORTURE_TMPL,
+            "Foo"[], ["bar"[],"quxx","zyzzy"][],
+            "int"[], 0x42);
+
+    static assert( TORTURE_EXPECTED == TORTURE_ACTUAL );
+}
+
+private
+{
+    size_t findIndexByName(Args...)(string name, Args args)
+    {
+        foreach( i ; Tuple.Sequence!(0, Args.length, 2) )
+        {
+            static if( !is( Args[i] : string ) )
+            {
+                static assert(false, "expected string for argument "
+                        ~ Integer.format_ctfe(i) ~ " in " ~ Args.stringof
+                        ~ " not " ~ Args[i].stringof);
+            }
+            if( name == args[i][] )
+                return i+1;
+        }
+        return size_t.max;
+    }
+
+    version( Unittest )
+    {
+        static assert( findIndexByName("a", "a", 0, "b", 1) == 1 );
+        static assert( findIndexByName("b", "a", 0, "b", 1) == 3 );
+        static assert( findIndexByName("c", "a", 0, "b", 1) == size_t.max );
+    }
+}
+
+/// ditto
+
+string formatNamed_ctfe(Args...)(string tmpl, Args args)
+{
+    string r = "";
+    int argPos = 0;
+    
+    while( tmpl.length > 0 )
+    {
+        bool inExp = false;
+       
+        // Look for a $
+        foreach( i,c ; tmpl )
+        {
+            if (c == '$')
+            {
+                inExp = true;
+                r ~= tmpl[0..i];
+                tmpl = tmpl[i+1..$];
+                break;
+            }
+        }
+
+        // If we didn't find a $, it's because we hit the end of the template.
+        if( !inExp )
+        {
+            r ~= tmpl;
+            break;
+        }
+        
+        // So we're in an expansion/substitution.
+
+        debug(gb_Format_verbose) r ~= "{in exp}";
+
+        if( tmpl.length == 0 )
+        {
+            r ~= "{unterminated substitution}";
+            break;
+        }
+
+        // c is the next character, whilst tmpl is everything left in the
+        // template string.
+        char c = tmpl[0];
+        tmpl = tmpl[1..$];
+        
+        // $$ - escaped $.
+        if( c == '$' )
+        {
+            debug(gb_Format_verbose) r ~= "{escaped $}";
+            r ~= '$';
+            continue;
+        }
+
+        // $a... - shortcut for $a...
+        if( String.isIdentStartChar_ctfe(c) )
+        {
+            debug(gb_Format_verbose) r ~= "{shorthand name}";
+            size_t i = 0;
+            while( i < tmpl.length )
+            {
+                if( !String.isIdentChar_ctfe(tmpl[i]) )
+                    break;
+                ++ i;
+            }
+            string name = c ~ tmpl[0..i];
+            tmpl = tmpl[i..$];
+            r ~= stringify(findIndexByName(name, args), 0, "", args);
+            continue;
+        }
+
+        // This means we got a $ followed by something unexpected.
+        if( c != '{' )
+        {
+            r ~= "{malformed substitution}";
+            break;
+        }
+        
+        if( tmpl.length == 0 )
+        {
+            r ~= "{unterminated substitution}";
+            break;
+        }
+        
+        debug(gb_Format_verbose)
+        {
+            r ~= "{parse complex at '";
+            r ~= c;
+            r ~= "':\"" ~ tmpl ~ "\"}";
+        }
+
+        // NOTE: We haven't updated c and tmpl yet.
+
+        {
+            // arg will contain the index of the argument the user wanted
+            // substituted.
+            size_t arg = size_t.max;
+            // fmt will contain any additional formatting options.
+            string fmt = "";
+
+            // If we didn't get a : or }, that means we expect a name.
+            if( !( tmpl[0] == ':' || tmpl[0] == '}' ) )
+            {
+                // So parse it.
+                size_t i = 0;
+                while( i < tmpl.length )
+                {
+                    if( !String.isIdentChar_ctfe(tmpl[i]) )
+                        break;
+                    ++ i;
+                }
+                string name = tmpl[0..i];
+                tmpl = tmpl[i..$];
+
+                arg = findIndexByName(name, args);
+                
+                if( tmpl.length == 0 )
+                {
+                    r ~= "{unterminated substitution}";
+                    break;
+                }
+            }
+            else
+            {
+                // Otherwise, the name was elided.  Kaboom!
+                r ~= "{substitution missing name}";
+                break;
+            }
+
+            c = tmpl[0];
+            tmpl = tmpl[1..$];
+
+            debug(gb_Format_verbose)
+                r ~= "{index " ~ Integer.format_ctfe(arg) ~ "}";
+
+            // If c is :, then we've got formatting options to parse
+
+            if( c == ':' )
+            {
+                debug(gb_Format_verbose) r ~= "{fmt string}";
+
+                // Look for the closing }.
+                size_t len = 0;
+                foreach( i,d ; tmpl )
+                {
+                    if( d == '}' )
+                    {
+                        len = i;
+                        break;
+                    }
+                }
+                if( len == 0 )
+                {
+                    r ~= "{malformed format}";
+                    break;
+                }
+                fmt = tmpl[0..len];
+                tmpl = tmpl[len..$];
+
+                debug(gb_Format_verbose) r ~= "{fmt:"~fmt~"}";
+
+                if( tmpl.length == 0 )
+                {
+                    r ~= "{unterminated substitution}";
+                    break;
+                }
+
+                c = tmpl[0];
+                tmpl = tmpl[1..$];
+            }
+
+            // At this point, we should have the closing }.  If not, someone's
+            // screwed up.
+            if( c != '}' )
+            {
+                debug(gb_Format_verbose)
+                {
+                    r ~= "{expected closing; got '";
+                    r ~= c;
+                    r ~= "':\"" ~ tmpl ~ "\"}";
+                }
+                r ~= "{malformed substitution}";
+                break;
+            }
+
+            // Stringify that bugger.
+            r ~= stringify(arg, 0, fmt, args);
+
+            // When we fall off the end here, we'll continue with the
+            // remainder of tmpl, unless it's empty in which case we're
+            // finished.
+        }
+    }
+    
+    return r;
+}
+
+version( Unittest )
+{
+    static assert( formatNamed_ctfe("A: $$", "a"[], 0, "b"[], 1) == "A: $" );
+    static assert( formatNamed_ctfe("B: $a", "a"[], 0, "b"[], 1) == "B: 0" );
+    static assert( formatNamed_ctfe("C: $b", "a"[], 0, "b"[], 1) == "C: 1" );
+
+    static assert( formatNamed_ctfe("D: ${a}", "a"[], 0, "b"[], 1) == "D: 0" );
+    static assert( formatNamed_ctfe("E: ${b}", "a"[], 0, "b"[], 1) == "E: 1" );
+    
+    static assert( formatNamed_ctfe("F: $foo$bar", "foo"[], 0, "bar"[], 1) == "F: 01" );
+    static assert( formatNamed_ctfe("G: ${foo}${bar}", "foo"[], 0, "bar"[], 1) == "G: 01" );
+
+    static assert( formatNamed_ctfe("H: ${foo:x}${bar:xx}", "foo"[], 0, "bar"[], 1) == "H: 00x1" );
+
+    const TORTURE_NAMED_TMPL = `
+        struct ${name}Enum
+        {
+            const Name = ${name:q};
+            const string[${members:l}] Members = ${members:q};
+
+            ${retType} value()
+            {
+                return ${value:xx};
+            }
+        }
+    `[];
+
+    const TORTURE_NAMED_EXPECTED = `
+        struct FooEnum
+        {
+            const Name = "Foo";
+            const string[3] Members = ["bar", "quxx", "zyzzy"];
+
+            int value()
+            {
+                return 0x42;
+            }
+        }
+    `[];
+
+    const TORTURE_NAMED_ACTUAL = formatNamed_ctfe(TORTURE_NAMED_TMPL,
+            "name"[], "Foo"[],
+            "members"[], ["bar"[],"quxx","zyzzy"][],
+            "retType"[], "int"[],
+            "value"[], 0x42);
+
+    static assert( TORTURE_NAMED_EXPECTED == TORTURE_NAMED_ACTUAL );
+}
+