1 module dkh.scanner;
2 
3 import dkh.container.stackpayload;
4 
5 /**
6 Scanner, not slow, not fast.
7 */
8 class Scanner {
9     import std.stdio : File;
10     import std.conv : to;
11     import std.range : front, popFront, array, ElementType;
12     import std.array : split;
13     import std.traits : isSomeChar, isStaticArray, isArray; 
14     import std.algorithm : map;
15     private File f;
16     /// f is source file
17     this(File f) {
18         this.f = f;
19     }
20     private char[512] lineBuf;
21     private char[] line;
22     private bool succW() {
23         import std.range.primitives : empty, front, popFront;
24         import std.ascii : isWhite;
25         while (!line.empty && line.front.isWhite) {
26             line.popFront;
27         }
28         return !line.empty;
29     }
30     private bool succ() {
31         import std.range.primitives : empty, front, popFront;
32         import std.ascii : isWhite;
33         while (true) {
34             while (!line.empty && line.front.isWhite) {
35                 line.popFront;
36             }
37             if (!line.empty) break;
38             line = lineBuf[];
39             f.readln(line);
40             if (!line.length) return false;
41         }
42         return true;
43     }
44 
45     private bool readSingle(T)(ref T x) {
46         import std.algorithm : findSplitBefore;
47         import std..string : strip;
48         import std.conv : parse;
49         if (!succ()) return false;
50         static if (isArray!T) {
51             alias E = ElementType!T;
52             static if (isSomeChar!E) {
53                 //string or char[10] etc
54                 //todo optimize
55                 auto r = line.findSplitBefore(" ");
56                 x = r[0].strip.dup;
57                 line = r[1];
58             } else static if (isStaticArray!T) {
59                 foreach (i; 0..T.length) {
60                     assert(succW());
61                     x[i] = line.parse!E;
62                 }
63             } else {
64                 StackPayload!E buf;
65                 while (succW()) {
66                     buf ~= line.parse!E;
67                 }
68                 x = buf.data;
69             }
70         } else {
71             x = line.parse!T;
72         }
73         return true;
74     }
75 
76     /// Read tokens. Return the number of success read
77     int unsafeRead(T, Args...)(ref T x, auto ref Args args) {
78         if (!readSingle(x)) return 0;
79         static if (args.length == 0) {
80             return 1;
81         } else {
82             return 1 + read(args);
83         }
84     }
85     /// Read tokens. If can't read, throw exception.
86     void read(Args...)(auto ref Args args) {
87         import std.exception : enforce;
88         static if (args.length != 0) {
89             enforce(readSingle(args[0]));
90             read(args[1..$]);
91         }
92     }
93     /// Do file has next token?
94     bool hasNext() {
95         return succ();
96     }
97 }
98 
99 
100 ///
101 unittest {
102     import std.path : buildPath;
103     import std.file : tempDir;
104     import std.algorithm : equal;
105     import std.stdio : File;
106     string fileName = buildPath(tempDir, "kyuridenanmaida.txt");
107     auto fout = File(fileName, "w");
108     fout.writeln("1 2 3");
109     fout.writeln("ab cde");
110     fout.writeln("1.0 1.0 2.0");
111     fout.close;
112     Scanner sc = new Scanner(File(fileName, "r"));
113     int a;
114     int[2] b;
115     char[2] c;
116     string d;
117     double e;
118     double[] f;
119     sc.read(a, b, c, d, e, f);
120     assert(a == 1);
121     assert(equal(b[], [2, 3])); // array(except char) read whole line
122     assert(equal(c[], "ab")); // char array(char[], char[2], ...) return token
123     assert(equal(d, "cde")); // string is char array
124     assert(e == 1.0);
125     assert(equal(f, [1.0, 2.0]));
126     assert(!sc.hasNext);
127 }
128 
129 unittest {
130     import std.path : buildPath;
131     import std.file : tempDir;
132     import std.algorithm : equal;
133     import std.stdio : File, writeln;
134     import dkh.stopwatch;
135     string fileName = buildPath(tempDir, "kyuridenanmaida.txt");
136     auto fout = File(fileName, "w");
137     foreach (i; 0..1_000_000) {
138         fout.writeln(3*i, " ", 3*i+1, " ", 3*i+2);
139     }
140     fout.close;
141     StopWatch sw;
142     sw.start;
143     Scanner sc = new Scanner(File(fileName, "r"));
144     foreach (i; 0..500_000) {
145         int a, b, c;
146         sc.read(a, b, c);
147         assert(a == 3*i);
148         assert(b == 3*i+1);
149         assert(c == 3*i+2);
150     }
151     foreach (i; 500_000..700_000) {
152         int[3] d;
153         sc.read(d);
154         int a = d[0], b = d[1], c = d[2];
155         assert(a == 3*i);
156         assert(b == 3*i+1);
157         assert(c == 3*i+2);
158     }
159     foreach (i; 700_000..1_000_000) {
160         int[] d;
161         sc.read(d);
162         assert(d.length == 3);
163         int a = d[0], b = d[1], c = d[2];
164         assert(a == 3*i);
165         assert(b == 3*i+1);
166         assert(c == 3*i+2);
167     }
168     writeln("Scanner Speed Test(3*1,000,000 int): ", sw.peek.toMsecs);
169 }