On Thu, Mar 25, 2010 at 04:52:29PM -0500, Andrei Alexandrescu wrote:
> It does work well, but there's one more issue: the type of octal!x is 
> always ulong. To make the code industrial strength, you should observe 
> the usual D rules:

I think I made it work. Also used template constraints to pretty up the
error messages a little on bad strings. The line number is still that of
the template instead of the instantiation, but maybe it is just because
my dmd is a few months old. I seem to recall seeing this improved in the
compiler itself, so I'm not worrying about it here.

The code is way at the bottom of this message. I'm going to show off a bit
first :)


Error messages:

int w = octal!845;
octal.d(106): Error: static assert  "845 is not a valid octal literal"

int w = octal!"spam";
octal.d(106): Error: static assert  "spam is not a valid octal literal"



The type magic works pretty well too:

int w = octal!"017777777777"; // compiles
int w = octal!"020000000000"; // but this doesn't... (this is int.max+1 btw)
octal.d(147): Error: cannot implicitly convert expression (octal()) of type 
long to int

int w = octal!"1L";
octal.d(147): Error: cannot implicitly convert expression (octal()) of type 
long to int

int w = octal!"1uL";
octal.d(147): Error: cannot implicitly convert expression (octal()) of type 
ulong to int

This also works with integer literals instead of strings:
        auto w = octal!1L;
        pragma(msg, typeof(w)); // prints "long"
        auto w = octal!1;
        pragma(msg, typeof(w)); // prints "int"
        auto w = octal!1u;
        pragma(msg, typeof(w)); // prints "uint"


The only bug I'm seeing is if the integer literal is type long,
but once converted to octal it will fit in an int, the template returns a
long. Passing a string does the right thing and returns int, but the literal
doesn't.

I don't see a way to fix this without breaking the octal!1777[...]7L case.
(To make the suffixes on literals work, I did typeof an alias for the helper
template instead of analyzing the string like I did for the main template.
I couldn't think of any other way to make it work; I even tried parsing
stringof the alias, but alas.


Anyway, aside from that, it works perfectly in my battery of tests. Handles
suffixes, embedded underscores, bad strings, types. Pretty cool.



Here's the code, with some comments added too:

===============================

/*
        Take a look at int.max and int.max+1 in octal and the logic for this 
function follows directly.
*/
template octalFitsInInt(string octalNum) {
        // note it is important to strip the literal of all non-numbers. kill 
the suffix and underscores
        // lest they mess up the number of digits here that we depend on.
        static if(strippedOctalLiteral(octalNum).length < 11)
                enum bool octalFitsInInt = true;
        else static if(strippedOctalLiteral(octalNum).length == 11 && 
strippedOctalLiteral(octalNum)[0] == '1')
                enum bool octalFitsInInt = true;
        else
                enum bool octalFitsInInt = false;
}

string strippedOctalLiteral(string original) {
        string stripped = "";
        foreach(c; original)
                if(c >= '0' && c <= '7')
                        stripped ~= c;
        return stripped;
}

template literalIsLong(string num) {
        static if(num.length > 1)
                enum literalIsLong = (num[$-1] == 'L' || num[$-2] == 'L'); // 
can be xxL or xxLu according to spec
        else
                enum literalIsLong = false;
}

template literalIsUnsigned(string num) {
        static if(num.length > 1)
                enum literalIsUnsigned = (num[$-1] == 'u' || num[$-2] == 'u') 
// can be xxL or xxLu according to spec
                   || (num[$-1] == 'U' || num[$-2] == 'U'); // both cases are 
allowed too
        else
                enum literalIsUnsigned = false;
}

/**
        These pass out the correctly typed literal based on the specifiers on 
the end of the string
        and the size of the literal.

        If it can fit in an int, it is an int. Otherwise, it is a long.

        But, if the user specifically asks for a long with the L suffix, always 
give the long.

        Give an unsigned iff it is asked for with the U or u suffix.
*/

int octal(string num)() if((octalFitsInInt!(num) && !literalIsLong!(num)) && 
!literalIsUnsigned!(num)) {
        return octal!(int, num);
}

long octal(string num)() if((!octalFitsInInt!(num) || literalIsLong!(num)) && 
!literalIsUnsigned!(num)) {
        return octal!(long, num);
}

uint octal(string num)() if((octalFitsInInt!(num) && !literalIsLong!(num)) && 
literalIsUnsigned!(num)) {
        return octal!(int, num);
}

ulong octal(string num)() if((!octalFitsInInt!(num) || literalIsLong!(num)) && 
literalIsUnsigned!(num)) {
        return octal!(long, num);
}

/*
        Returns if the given string is a correctly formatted octal literal.

        The format is specified in lex.html. The leading zero is allowed, but 
not required.
*/
bool isOctalLiteralString(string num) {
        if(num.length == 0)
                return false;

        // must start with a number
        if(num[0] < '0' || num[0] > '7')
                return false;
        
        foreach(i, c; num) {
                if((c < '0' || c > '7') && c != '_') // not a legal character
                        if(i < num.length - 2)
                                return false;
                        else { // gotta check for those suffixes
                                if(c != 'U' && c != 'u' && c != 'L')
                                        return false;
                                if(i != num.length - 1) { // if we're not the 
last one, the next one must also be a suffix to be valid
                                        char c2 = num[$-1];
                                        if(c2 != 'U' && c2 != 'u' && c2 != 'L')
                                                return false; // spam at the 
end of the string
                                        if(c2 == c)
                                                return false; // repeats are 
disallowed
                                }
                        }
        }

        return true;
}

/*
        Returns true if the given compile time string is an octal literal.
*/
template isOctalLiteral(string num) {
        enum bool isOctalLiteral = isOctalLiteralString(num);
}

/*
        Takes a string, num, which is an octal literal, and returns its value, 
in the
        type T specified.

        So:

        int a = octal!(int, "10");

        assert(a == 8);
*/
T octal(T, string num)() {
    static assert(isOctalLiteral!num, num ~ " is not a valid octal literal");

    ulong pow = 1;
    T value = 0;

    for(int pos = num.length - 1; pos >= 0; pos--) {
        char s = num[pos];
        if(s < '0' || s > '7') // we only care about digits; skip the rest
                continue; // safe to skip - this is checked out in the assert 
so these are just suffixes
        
        value += pow * (s - '0');
        pow *= 8;
  }

  return value;
}

import std.metastrings: toStringNow;
import std.traits: isIntegral;

/**
        Constructs an octal literal from a number, maintaining the type of the 
original number.
        For example:

        octal!10
        
        is exactly the same as integer literal 8.

        octal!10U

        is identical to the literal 8U.
*/
template octal(alias s) if(isIntegral!(typeof(s))) {
        enum auto octal = octal!(typeof(s), toStringNow!(s));
}

unittest
{

        // ensure that you get the right types, even with embedded underscores
        auto w = octal!"100_000_000_000";
        static assert(!is(typeof(w) == int));
        auto w2 = octal!"1_000_000_000";
        static assert(is(typeof(w2) == int));

    static assert(octal!"45" == 37);
    static assert(octal!"0" == 0);
    static assert(octal!"7" == 7);
    static assert(octal!"10" == 8);
    static assert(octal!"666" == 438);

    static assert(octal!45 == 37);
    static assert(octal!0 == 0);
    static assert(octal!7 == 7);
    static assert(octal!10 == 8);
    static assert(octal!666 == 438);

    static assert(octal!"66_6" == 438);

    static assert(octal!2520046213 == 356535435);
    static assert(octal!"2520046213" == 356535435);

    static assert(octal!17777777777 == int.max);

    static assert(!__traits(compiles, octal!823));
//    static assert(!__traits(compiles, octal!"823")); // for some reason, this 
line fails, though if you try it in code, it indeed doesn't compile... weird.
    static assert(!__traits(compiles, octal!"_823"));
    static assert(!__traits(compiles, octal!"spam"));
    static assert(!__traits(compiles, octal!"77%"));

    int a;
    long b;

    static assert(__traits(compiles,  a = octal!"17777777777")); // biggest 
value that should fit in an it
    static assert(!__traits(compiles, a = octal!"20000000000")); // should not 
fit in the int
    static assert(__traits(compiles, b = octal!"20000000000")); // ... but 
should fit in a long

   static assert(!__traits(compiles, a = octal!"1L"));
   //static assert(!__traits(compiles, a = octal!1L)); // this should pass, but 
it doesn't, since the int converter doesn't pass along its suffix to helper 
templates

   static assert(__traits(compiles, b = octal!"1L"));
   static assert(__traits(compiles, b = octal!1L));
}

===============================

-- 
Adam D. Ruppe
http://arsdnet.net

Reply via email to