1 /**
2  * Implements a simple reader for Command Sequence Unoriginal Format
3  * 
4  * Authors:
5  *    Richard Andrew Cattermole
6  * 
7  * License:
8  *              Copyright Richard Andrew Cattermole 2004 - 2006.
9  *     Distributed under the Boost Software License, Version 1.0.
10  *        (See accompanying file LICENSE_1_0.txt or copy at
11  *              http://www.boost.org/LICENSE_1_0.txt)
12  */
13 module csuf.reader;
14 import std.traits : isSomeString;
15 
16 ///
17 struct CommandSequenceReader(String) if (isSomeString!String) {
18 	private {
19 		String[] allArgsCommands, allArgsInformation, allArgsInformationCommands;
20 		Item!String[] allCommands, allInformation, allInformationCommands;
21 	}
22 	
23 	///
24 	Entry!String[] entries;
25 	
26 	///
27 	this(String sourceText, String resetCommand = "new") {
28 		import std.algorithm : sum, map, count, splitter;
29 		import std..string : lineSplitter, strip;
30 		
31 		auto countCommands = sourceText
32 			.lineSplitter
33 				.map!strip
34 				.map!(line => (line.length > 0 && line[0] == '.' && ((line.length > 1 && line[1] != '.') || line.length == 1)) ? 1 : 0)
35 				.sum;
36 		auto countInformationCommands = sourceText
37 			.lineSplitter
38 				.map!strip
39 				.map!(line => (line.length > 1 && line[0] == '.' && line[1] == '.') ? 1 : 0)
40 				.sum;
41 		auto countInformation = sourceText
42 			.lineSplitter
43 				.map!strip
44 				.map!(line => (line.length > 0) ? 1 : 0)
45 				.sum - countCommands;
46 		
47 		auto countEntries = sourceText
48 			.lineSplitter
49 				.map!strip
50 				.map!(line => (line.length > resetCommand.length && line[0] == '.' && line[1 .. resetCommand.length+1] == resetCommand) ? 1 : 0)
51 				.sum;
52 		auto countArgsCommand = sourceText
53 			.lineSplitter
54 				.map!strip
55 				.map!(line => (line.length > 0 && line[0] == '.' && ((line.length > 1 && line[1] != '.') || line.length == 1)) ? line.count(' ') : 0)
56 				.sum;
57 		auto countArgsInformation = sourceText
58 			.lineSplitter
59 				.map!strip
60 				.map!(line => (line.length > 0 && line[0] != '.') ? line.count(' ') : 0)
61 				.sum;
62 		auto countArgsInformationCommand = sourceText
63 			.lineSplitter
64 				.map!strip
65 				.map!(line => (line.length > 1 && line[0] == '.' && line[1] == '.') ? line.count(' ') : 0)
66 				.sum;
67 		
68 		entries.length = countEntries + 1;
69 		
70 		allCommands.length = countCommands + 1;
71 		allInformation.length = countInformation + 1;
72 		allInformationCommands.length = countInformationCommands + 1;
73 		
74 		allArgsCommands.length = countArgsCommand + 1;
75 		allArgsInformation.length = countArgsInformation + 1;
76 		allArgsInformationCommands.length = countArgsInformationCommand + 1;
77 		
78 		Entry!String* entry;
79 		
80 		size_t offsetEntry, offsetCommand, offsetInformation, offsetInformationCommand,
81 			offsetCommandArg, offsetInformationArg, offsetInformationCommandArg;
82 		
83 		bool lastWasCommand, lastWasInformationCommand, needInfoCmdReset;
84 		foreach(line; sourceText.lineSplitter) {
85 			line = line.strip;
86 			
87 			if (line.length > 0) {
88 				uint idx;
89 				
90 				foreach(v; line.splitter(' ')) {
91 					if (idx == 0) {
92 						if (entry !is null) {
93 							if (lastWasCommand) {
94 								offsetCommandArg += entry.commands[$-1].args.length;
95 							} else if (lastWasInformationCommand) {
96 								offsetInformationCommandArg += entry.information[$-1].commands[$-1].args.length;
97 							} else {
98 								offsetInformationArg += entry.information[$-1].args.length;
99 							}
100 						}
101 						
102 						if (v[0] == '.') {
103 							if (entry !is null && v.length > 1 && v[1] == '.') {
104 								// ..token
105 								
106 								lastWasCommand = false;
107 								lastWasInformationCommand = true;
108 								needInfoCmdReset = true;
109 								
110 								if (entry.commands is null) {
111 									entry.information[$-1].commands = allInformationCommands[offsetInformationCommand .. offsetInformationCommand + 1];
112 								} else {
113 									entry.information[$-1].commands = allInformationCommands[offsetInformationCommand .. offsetInformationCommand + 1 + entry.information[$-1].commands.length];
114 								}
115 								
116 								entry.information[$-1].commands[$-1].name = v[2 .. $];
117 							} else if (v[1 .. $] == resetCommand) {
118 								// .new
119 								
120 								if (entry !is null) {
121 									offsetCommand += entry.commands.length;
122 									offsetInformation += entry.information.length;
123 									
124 									if (needInfoCmdReset) {
125 										needInfoCmdReset = false;
126 										offsetInformationCommand += entry.information[$-1].commands.length;
127 									}
128 								}
129 								
130 								entry = &entries[offsetEntry++];
131 								goto DotToken;
132 							} else if (entry !is null) {
133 								// .token
134 							DotToken:
135 								
136 								lastWasCommand = true;
137 								lastWasInformationCommand = false;
138 								
139 								if (entry.commands is null) {
140 									entry.commands = allCommands[offsetCommand .. offsetCommand + 1];
141 								} else {
142 									entry.commands = allCommands[offsetCommand .. offsetCommand + 1 + entry.commands.length];
143 								}
144 								
145 								entry.commands[$-1].name = v[1 .. $];
146 							}
147 						} else {
148 							// token
149 							
150 							lastWasCommand = false;
151 							lastWasInformationCommand = false;
152 							
153 							if (needInfoCmdReset) {
154 								needInfoCmdReset = false;
155 								offsetInformationCommand += entry.information[$-1].commands.length;
156 							}
157 							
158 							if (entry.information is null) {
159 								entry.information = allInformation[offsetInformation .. offsetInformation + 1];
160 							} else {
161 								entry.information = allInformation[offsetInformation .. offsetInformation + 1 + entry.information.length];
162 							}
163 							
164 							entry.information[$-1].name = v;
165 						}
166 					} else {
167 						if (lastWasCommand) {
168 							if (entry.commands[$-1].args is null) {
169 								entry.commands[$-1].args = allArgsCommands[offsetCommandArg .. offsetCommandArg + 1];
170 							} else {
171 								entry.commands[$-1].args = allArgsCommands[offsetCommandArg .. offsetCommandArg + entry.commands[$-1].args.length + 1];
172 							}
173 							
174 							entry.commands[$-1].args[$-1] = v;
175 						} else if (lastWasInformationCommand) {
176 							assert(entry.information[$-1].commands.length > 0);
177 							
178 							if (entry.information[$-1].commands[$-1].args is null) {
179 								entry.information[$-1].commands[$-1].args = allArgsInformationCommands[offsetInformationCommandArg .. offsetInformationCommandArg + 1];
180 							} else {
181 								entry.information[$-1].commands[$-1].args = allArgsInformationCommands[offsetInformationCommandArg .. offsetInformationCommandArg + entry.information[$-1].commands[$-1].args.length + 1];
182 							}
183 							
184 							entry.information[$-1].commands[$-1].args[$-1] = v;
185 						} else {
186 							if (entry.information[$-1].args is null) {
187 								entry.information[$-1].args = allArgsInformation[offsetInformationArg .. offsetInformationArg + 1];
188 							} else {
189 								entry.information[$-1].args = allArgsInformation[offsetInformationArg .. offsetInformationArg + entry.information[$-1].args.length + 1];
190 							}
191 							
192 							entry.information[$-1].args[$-1] = v;
193 						}
194 					}
195 					
196 					idx++;
197 				}
198 			}
199 		}
200 		
201 		entries.length--;
202 		allCommands.length--;
203 		allInformation.length--;
204 		allArgsCommands.length--;
205 		allArgsInformation.length--;
206 		allArgsInformationCommands.length--;
207 	}
208 	
209 	@disable
210 	this(this);
211 }
212 
213 ///
214 struct Entry(String) if (isSomeString!String) {
215 	///
216 	Item!String[] commands;
217 	///
218 	Item!String[] information;
219 }
220 
221 ///
222 struct Item(String) if (isSomeString!String) {
223 	import std.traits : isPointer;
224 	
225 	///
226 	String name;
227 	///
228 	String[] args;
229 	
230 	Item!String[] commands;
231 	
232 	///
233 	T get(T)(size_t offset, T default_ = T.init) if (!(is(T == struct) || is(T == class) || is(T == union) || isPointer!T)) {
234 		import std.conv : to;
235 		if (args.length > offset) {
236 			return to!T(args[offset]);
237 		} else {
238 			return default_;
239 		}
240 	}
241 }
242 
243 ///
244 unittest {
245 	CommandSequenceReader!string reader = CommandSequenceReader!string("
246 .new
247 
248 Hi <name>
249 
250 .new 8
251 .something back 6
252 
253 Bye <name> loozer
254 
255 ", "new");
256 }
257 
258 unittest {
259 	CommandSequenceReader!dstring reader = CommandSequenceReader!dstring("
260 .new
261 
262 Hi <name>
263 
264 .new 8
265 .something back 6
266 
267 Bye <name> loozer
268 
269 "d, "new"d);
270 }
271 
272 ///
273 unittest {
274 	CommandSequenceReader!string reader = CommandSequenceReader!string("
275 .new
276 
277 Hi <name>
278 
279 .new 8
280 .something back 6
281 
282 Bye <name> loozer
283 ..myend here!
284 
285 .\\.a command
286 ", "new");
287 }
288 
289 ///
290 unittest {
291 	CommandSequenceReader!string reader = CommandSequenceReader!string("
292 .new HEADER
293 .something here
294 
295 .new
296 abcd
297 ", "new");
298 }