www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - D input: how-to (RFC)

reply "Tyro[a.c.edwards]" <nospam home.com> writes:
I am looking for a D version of scanf() but I'm sure there is no such 
thing so I tried contrived one. I am sure I missed a slew of obvious 
things and that this cannot be used for much more than a little toy on 
my personal computer. I would like to make it usable for others so I am 
asking for some guidance/suggestions for making it better.

Thanks,
Andrew

This is written in D2 v2.029 with an updated std.random to fix a problem 
I was having with the current release.

module ace.io;

private import std.conv: to, ConvError;
private import std.string: split, stripr;

public import std.stdio;

int read(/+File inFile = stdin,+/ A...)(out A a) /+Uncommenting results
     in: Error: arithmetic/string type expected for value-parameter, not
     File+/
{  start:
    auto data = stripr(readln());
    auto input = split(data);

    string assign()
    {
        return
        `  try {
               		if(a.length != input.length)
           					return -1;
               		a[i] = to!(typeof(t))(input[i]);
               } catch (ConvError e) {
               	  writeln("Invalid input!");
               	  goto start;
               }`;
    }

    foreach(i, t; a)
    {
      static if(is(typeof(t) == void))
        {}
      else static if(is(typeof(t) == bool))
        {}
      else static if(is(typeof(t) == byte))
        mixin(assign);
      else static if(is(typeof(t) == ubyte))
        mixin(assign);
      else static if(is(typeof(t) == short))
        mixin(assign);
      else static if(is(typeof(t) == ushort))
        mixin(assign);
      else static if(is(typeof(t) == int))
        mixin(assign);
      else static if(is(typeof(t) == uint))
        mixin(assign);
      else static if(is(typeof(t) == long))
        mixin(assign);
      else static if(is(typeof(t) == ulong))
        mixin(assign);
      /+static if(is(typeof(t) == cent))
        mixin(assign);
      static if(is(typeof(t) == ucent))
        mixin(assign);+/
      else static if(is(typeof(t) == float))
        mixin(assign);
      else static if(is(typeof(t) == double))
        mixin(assign);
      else static if(is(typeof(t) == real))
        mixin(assign);
      else static if(is(typeof(t) == ifloat))
        a[i] = ifloat.max;
      else static if(is(typeof(t) == idouble))
        a[i] = idouble.max;
      else static if(is(typeof(t) == ireal))
        a[i] = ireal.max;
      else static if(is(typeof(t) == cfloat))
        a[i] = cfloat.max;
      else static if(is(typeof(t) == cdouble))
        a[i] = cdouble.max;
      else static if(is(typeof(t) == creal))
        a[i] = creal.max;
      else static if(is(typeof(t) == char))
        a[i] = input[i][0];
      else static if(is(typeof(t) == wchar))
        mixin(assign);
      else static if(is(typeof(t) == dchar))
        a[i] = input[i][0];
      else static if(is(typeof(t) == string))
        if(a.length > 1)
          a[i] = input[i];
        else
          a[i] = data;
      else static if(is(typeof(t) == dstring))
        {}
      else static if(is(typeof(t) == char[]))
        a[i] = stripr(data).dup;
    }
    return 0;
}
May 10 2009
parent reply grauzone <none example.net> writes:
Tyro[a.c.edwards] wrote:
 I am looking for a D version of scanf() but I'm sure there is no such 
 thing so I tried contrived one. I am sure I missed a slew of obvious 
There's readf() in std.stream. I think you have to use std.cstream : din to use it with stdin.
 int read(/+File inFile = stdin,+/ A...)(out A a) /+Uncommenting results
     in: Error: arithmetic/string type expected for value-parameter, not
     File+/
That would make inFile a template parameter, which obviously doesn't make sense with objects, and I guess File is an object. This should work: int read(A...)(File inFile, out A a)
 {  start:
    auto data = stripr(readln());
    auto input = split(data);
 
    string assign()
    {
        return
        `  try {
                       if(a.length != input.length)
                               return -1;
                       a[i] = to!(typeof(t))(input[i]);
               } catch (ConvError e) {
                     writeln("Invalid input!");
                     goto start;
               }`;
    }
If the user types in too much or too less input, read() returns with an error (-1), and the caller has to deal with it. If the user typed in something unparseable, the function prompts an error and lets the user retry. This is inconsistent. Also, instead of using CTFE to return a const string, why not simply const assign = ` .... ` ; You're using D 2.0, you probably have to replace "const" by "enum". I don't know. You use goto to retry. I'd replace it by a loop. If someone wants to argue with me if goto is or is not evil, go ahead.
    foreach(i, t; a)
    {
      static if(is(typeof(t) == void))
        {}
      else static if(is(typeof(t) == bool))
        {}
bools can't be read? If the implementation is incomplete, there should be at least an "assert(false);", maybe even a "static assert(false);".
      else static if(is(typeof(t) == byte))
        mixin(assign);
      else static if(is(typeof(t) == ubyte))
        mixin(assign);
      else static if(is(typeof(t) == short))
        mixin(assign);
      else static if(is(typeof(t) == ushort))
        mixin(assign);
      else static if(is(typeof(t) == int))
        mixin(assign);
      else static if(is(typeof(t) == uint))
        mixin(assign);
      else static if(is(typeof(t) == long))
        mixin(assign);
      else static if(is(typeof(t) == ulong))
        mixin(assign);
      /+static if(is(typeof(t) == cent))
        mixin(assign);
      static if(is(typeof(t) == ucent))
        mixin(assign);+/
      else static if(is(typeof(t) == float))
        mixin(assign);
      else static if(is(typeof(t) == double))
        mixin(assign);
      else static if(is(typeof(t) == real))
        mixin(assign);
Oh god, what the fuck! String mixins are the new #define! OK, at least this reduces the code duplication, but the code duplication shouldn't be there in the first place. You could just throw all the types into a tuple, and then foreach() on it, checking for the type in each iteration. Or just make a single giant if() statement to check for all types to!() supports (like if(is(typeof(t) == int) && is(typeof(t) == uint)...). In any case, you could reduce the number of is() parts by using isNumeric() from std.traits. Or find a way to check for to!() supported data types automatically (but I don't know to!() well enough if this is possible).
      else static if(is(typeof(t) == ifloat))
        a[i] = ifloat.max;
      else static if(is(typeof(t) == idouble))
        a[i] = idouble.max;
      else static if(is(typeof(t) == ireal))
        a[i] = ireal.max;
      else static if(is(typeof(t) == cfloat))
        a[i] = cfloat.max;
      else static if(is(typeof(t) == cdouble))
        a[i] = cdouble.max;
      else static if(is(typeof(t) == creal))
        a[i] = creal.max;
What?
      else static if(is(typeof(t) == char))
        a[i] = input[i][0];
input could be an empty string, and random things could happen.
      else static if(is(typeof(t) == wchar))
        mixin(assign);
      else static if(is(typeof(t) == dchar))
        a[i] = input[i][0];
      else static if(is(typeof(t) == string))
        if(a.length > 1)
          a[i] = input[i];
        else
          a[i] = data;
I see, if the caller of the function only requests a char[], the splitting by white space is disregarded, and the whole string is returned. Isn't that a but inconsistent? At least, it could always return the remainder of the string, if this char[] parameter is the last of the function. Also, the array index i isn't checked for input, and random things could happen. (You'd get an exception in debug mode, but not in release mode.) Also, http://en.wikipedia.org/wiki/Dangling_else
      else static if(is(typeof(t) == dstring))
        {}
      else static if(is(typeof(t) == char[]))
        a[i] = stripr(data).dup;
Shouldn't split() already remove all white space?
    }
    return 0;
 }
May 10 2009
parent reply "Tyro[a.c.edwards]" <nospam home.com> writes:
On 5/11/2009 2:35 PM, grauzone wrote:
 Tyro[a.c.edwards] wrote:
 I am looking for a D version of scanf() but I'm sure there is no such
 thing so I tried contrived one. I am sure I missed a slew of obvious
There's readf() in std.stream. I think you have to use std.cstream : din to use it with stdin.
Thanks, but I'm trying to learn here... Hoping I can get a better understanding of how things work or are supposed to work. I'll probably change it back from read() to get().
 int read(/+File inFile = stdin,+/ A...)(out A a) /+Uncommenting results
 in: Error: arithmetic/string type expected for value-parameter, not
 File+/
That would make inFile a template parameter, which obviously doesn't make sense with objects, and I guess File is an object. This should work: int read(A...)(File inFile, out A a)
OK... that works, but how would I set stdin as the default input "file"? I tried: int read(A...)(out A a, File inFile = stdin) but the compiler hangs trying to compile it whenever I call read() using 50% of the CPU resource in the process.
 { start:
 auto data = stripr(readln());
 auto input = split(data);

 string assign()
 {
 return
 ` try {
 if(a.length != input.length)
 return -1;
 a[i] = to!(typeof(t))(input[i]);
 } catch (ConvError e) {
 writeln("Invalid input!");
 goto start;
 }`;
 }
If the user types in too much or too less input, read() returns with an error (-1), and the caller has to deal with it. If the user typed in something unparseable, the function prompts an error and lets the user retry. This is inconsistent.
Got it... would it be better for the caller handles such problems or the compiler?
 Also, instead of using CTFE to return a const string, why not simply

 const assign = ` .... ` ;
tried that but kept tying "string assign = " and "auto assign = "; both of which kept failing until I placed it in a function. Thanks I have changed it to enum.
 You're using D 2.0, you probably have to replace "const" by "enum". I
 don't know.

 You use goto to retry. I'd replace it by a loop. If someone wants to
 argue with me if goto is or is not evil, go ahead.

 foreach(i, t; a)
 {
 static if(is(typeof(t) == void))
 {}
 else static if(is(typeof(t) == bool))
 {}
bools can't be read? If the implementation is incomplete, there should be at least an "assert(false);", maybe even a "static assert(false);".
Got it. What value would you read for a bool though? to me it can be 0||1, true||false, yes||no, etc... Would I simply use 0 && 1 and forget about the rest?
 else static if(is(typeof(t) == byte))
 mixin(assign);
 else static if(is(typeof(t) == ubyte))
 mixin(assign);
 else static if(is(typeof(t) == short))
 mixin(assign);
 else static if(is(typeof(t) == ushort))
 mixin(assign);
 else static if(is(typeof(t) == int))
 mixin(assign);
 else static if(is(typeof(t) == uint))
 mixin(assign);
 else static if(is(typeof(t) == long))
 mixin(assign);
 else static if(is(typeof(t) == ulong))
 mixin(assign);
 /+static if(is(typeof(t) == cent))
 mixin(assign);
 static if(is(typeof(t) == ucent))
 mixin(assign);+/
 else static if(is(typeof(t) == float))
 mixin(assign);
 else static if(is(typeof(t) == double))
 mixin(assign);
 else static if(is(typeof(t) == real))
 mixin(assign);
Oh god, what the fuck! String mixins are the new #define! OK, at least
ROTFDWL - that was truly unexpected. Thanks.
 this reduces the code duplication, but the code duplication shouldn't be
 there in the first place.

 You could just throw all the types into a tuple, and then foreach() on
 it, checking for the type in each iteration.
Awesome... I didn't even think about that. Got it!
 Or just make a single giant if() statement to check for all types to!()
 supports (like if(is(typeof(t) == int) && is(typeof(t) == uint)...). In
 any case, you could reduce the number of is() parts by using isNumeric()
 from std.traits.

 Or find a way to check for to!() supported data types automatically (but
 I don't know to!() well enough if this is possible).

 else static if(is(typeof(t) == ifloat))
 a[i] = ifloat.max;
 else static if(is(typeof(t) == idouble))
 a[i] = idouble.max;
 else static if(is(typeof(t) == ireal))
 a[i] = ireal.max;
 else static if(is(typeof(t) == cfloat))
 a[i] = cfloat.max;
 else static if(is(typeof(t) == cdouble))
 a[i] = cdouble.max;
 else static if(is(typeof(t) == creal))
 a[i] = creal.max;
What?
That's a residue of my first try... I will take care of those. I really should have replaced those with "assert(false);" because I didn't understand how to implement them. Will read some more and try again in.
 else static if(is(typeof(t) == char))
 a[i] = input[i][0];
input could be an empty string, and random things could happen.
got it...
 else static if(is(typeof(t) == wchar))
 mixin(assign);
 else static if(is(typeof(t) == dchar))
 a[i] = input[i][0];
 else static if(is(typeof(t) == string))
 if(a.length > 1)
 a[i] = input[i];
 else
 a[i] = data;
I see, if the caller of the function only requests a char[], the splitting by white space is disregarded, and the whole string is returned. Isn't that a but inconsistent? At least, it could always return the remainder of the string, if this char[] parameter is the last of the function.
I was actually hoping to find a way to put the unused portion back unto the buffer but that seems a much better approach.
 Also, the array index i isn't checked for input, and random things could
 happen. (You'd get an exception in debug mode, but not in release mode.)
??? not sure what you mean here. i is the index of the variable as it appears in the tuple "A". Why would I need to check it for input? i is valid as long as we have not reached the end of the tuple.
 Also, http://en.wikipedia.org/wiki/Dangling_else
Some more reading to do... got it!
 else static if(is(typeof(t) == dstring))
 {}
 else static if(is(typeof(t) == char[]))
 a[i] = stripr(data).dup;
Shouldn't split() already remove all white space?
split removes the white space but only from the copy of the string returned. The original remains untouched.
 }
 return 0;
 }
Thank you very much for responding. Andrew
May 11 2009
parent grauzone <none example.net> writes:
 int read(/+File inFile = stdin,+/ A...)(out A a) /+Uncommenting results
 in: Error: arithmetic/string type expected for value-parameter, not
 File+/
That would make inFile a template parameter, which obviously doesn't make sense with objects, and I guess File is an object. This should work: int read(A...)(File inFile, out A a)
OK... that works, but how would I set stdin as the default input "file"?
You can't have default arguments and variadic arguments at the same time. That's just how the syntax works. At best, you could change the signature to "int read(A...)(ref A a)". Then you could check if the first argument is of the type File, and if not, use a default value for inFile, and so on.
 I tried: int read(A...)(out A a, File inFile = stdin) but the compiler 
 hangs trying to compile it whenever I call read() using 50% of the CPU 
 resource in the process.
No matter if it's allowed or not, the compiler shouldn't hang. Maybe report a bug: http://d.puremagic.com/issues/
 bools can't be read? If the implementation is incomplete, there should
 be at least an "assert(false);", maybe even a "static assert(false);".
Got it. What value would you read for a bool though? to me it can be 0||1, true||false, yes||no, etc... Would I simply use 0 && 1 and forget about the rest?
Because this function seems to deal with user input, maybe you should allow as many as possible.
 else static if(is(typeof(t) == string))
 if(a.length > 1)
 a[i] = input[i];
 else
 a[i] = data;
 Also, the array index i isn't checked for input, and random things could
 happen. (You'd get an exception in debug mode, but not in release mode.)
??? not sure what you mean here. i is the index of the variable as it appears in the tuple "A". Why would I need to check it for input? i is valid as long as we have not reached the end of the tuple.
You loop over a, so a[i] will always be correct. But input[i] could be out of bounds, as far as I can see.
 Shouldn't split() already remove all white space?
split removes the white space but only from the copy of the string returned. The original remains untouched.
Oops... right.
May 11 2009