1
2 /* "Species" - a CoreWars evolver. Copyright (C) 2003 'Varfar'
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the Free
6 * Software Foundation; either version 1, or (at your option) any later
7 * version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include "ini.hpp"
20
21 #include <string.h>
22
23 using namespace std;
24
25 namespace { // anonymous
26
27 const char *KVP_ERRORS[KeyValuePair::KVP_ERROR_LAST] = {
28 "VALUE_NOT_INT","VALUE_NOT_BOOL","VALUE_NOT_FLOAT",
29 "VALUE_NOT_DOUBLE","KEY_NOT_VALID","KEY_HAS_NO_CATEGORY"
30 };
31
str_equals(const string str1,const char * s2)32 bool str_equals(const string str1,const char *s2) {
33 const char *s1 = str1.data();
34 unsigned int s11, s12, s21, s22;
35 for(s11=0; s11<strlen(s1); s11++) if(s1[s11] > ' ') break;
36 for(s12=strlen(s1)-1; s12 > s11; s11--) if(s1[s12] > ' ') break;
37 for(s21=0; s21<strlen(s2); s21++) if(s2[s21] > ' ') break;
38 for(s22=strlen(s2)-1; s22 > s21; s22--) if(s2[s22] > ' ') break;
39 // easy length check
40 if((s12-s11) != (s22-s21)) return false;
41 // ok, have to compare each character
42 return (strncmp(&s1[s11],&s2[s21],s12-s11) == 0);
43 }
44
45 } // end of anon namespace
46
KeyValuePair()47 KeyValuePair::KeyValuePair() {
48 linenum = 0;
49 valid = false;
50 }
51
~KeyValuePair()52 KeyValuePair::~KeyValuePair() {}
53
ERROR_MSG(const KVP_ERROR err)54 const char *KeyValuePair::ERROR_MSG(const KVP_ERROR err) {
55 if(0 <= err < KVP_ERROR_LAST)
56 return KVP_ERRORS[err];
57 return 0;
58 }
59
60 // checking key
61
is(const char * key) const62 bool KeyValuePair::is(const char *key) const {
63 if(!valid) return false; // obviously
64 return (strcasecmp(this->key,key) == 0);
65 }
66
67 // getting key
68
getKeyAsString(string & key)69 void KeyValuePair::getKeyAsString(string &key) {
70 if(!valid) throw KEY_NOT_VALID;
71 key = string(this->key); // reconstruct
72 }
73
getKeyAsChar()74 const char *KeyValuePair::getKeyAsChar() {
75 if(!valid) throw KEY_NOT_VALID;
76 return key;
77 }
78
79 // checking category
80
isCategory(const char * category)81 bool KeyValuePair::isCategory(const char *category) {
82 if(!valid) return false; // obviously
83 if(!hascategory) return false; // obviously
84 return (strcasecmp(this->category,category) == 0);
85 }
86
hasSubCategory()87 bool KeyValuePair::hasSubCategory() { // more categories? category.category...key
88 for(char *ch=key; *ch != '\0'; ch++)
89 if(*ch == '.') return true;
90 return false;
91 }
92
getSubCategory()93 bool KeyValuePair::getSubCategory() { // will divide the current key into category.key
94 hascategory = false;
95 char *ch;
96 for(ch = key; *ch != '\0'; ch++)
97 if(*ch == '.') { // find dot delimiter
98 hascategory = true;
99 break;
100 }
101 if(hascategory) {
102 category = key;
103 *ch = '\0';
104 key = ++ch;
105 //cout << "category = \"" << category << "\"\tkey=\"" << key << '\"' << endl;
106 if(*key == '\0') { // empty key now? was trailing . ?
107 // repair damage!
108 hascategory = false;
109 *--ch = '.';
110 key = category;
111 }
112 } else
113 category = NULL;
114 return hascategory;
115 }
116
117 // getting category
118
getCategoryAsString(string & category)119 void KeyValuePair::getCategoryAsString(string &category) {
120 if(!valid) throw KEY_NOT_VALID;
121 if(!hascategory) throw KEY_HAS_NO_CATEGORY;
122 category = string(this->category); // reconstruct
123 }
124
getCategoryAsChar() const125 const char *KeyValuePair::getCategoryAsChar() const {
126 if(!valid) throw KEY_NOT_VALID;
127 if(!hascategory) throw KEY_HAS_NO_CATEGORY;
128 return category;
129 }
130
131 // checking value
132
isValue(const char * val) const133 bool KeyValuePair::isValue(const char *val) const {
134 if(!valid) return false; // obviously
135 return (strcasecmp(this->value,val) == 0);
136 }
137
138 // getting value
139
getValueAsString(string & value)140 void KeyValuePair::getValueAsString(string &value) {
141 if(!valid) throw KEY_NOT_VALID;
142 value = string(this->value); // reconstruct
143 }
144
getValueAsChar()145 const char *KeyValuePair::getValueAsChar() {
146 if(!valid) throw KEY_NOT_VALID;
147 return value;
148 }
149
getValueAsInt(int def)150 int KeyValuePair::getValueAsInt(int def) {
151 // Will's patent parser detects errors better than atoi
152 if(!valid) throw KEY_NOT_VALID;
153 char *ch = value;
154 if('\0' == *ch) return def;
155 bool neg = (*ch=='-'); // remember if negative
156 if(*ch=='+' || *ch=='-') // step past sign?
157 ch++;
158 int result = 0;
159 while(*ch>='0' && *ch<='9') {
160 int digit = (*ch)-'0'; // 0..9
161 result *= 10;
162 result += digit;
163 ch++;
164 }
165 if(*ch!='\0') throw VALUE_NOT_INT;
166 if(neg)
167 result = -result;
168 return result;
169 }
170
getValueAsFloat(float def,bool percent_allowed)171 float KeyValuePair::getValueAsFloat(float def,bool percent_allowed) {
172 // Will's patent parser detects errors better than atof
173 if(!valid) throw KEY_NOT_VALID;
174 char *ch = value;
175 if('\0' == *ch) return def;
176 bool neg = (*ch=='-'); // remember if negative
177 if(*ch=='+' || *ch=='-') // step past sign?
178 ch++;
179 float result = 0.0f;
180 int digit;
181 while(*ch>='0' && *ch<='9') {
182 digit = (*ch)-'0'; // 0..9
183 result *= 10;
184 result += digit;
185 ch++;
186 }
187 if(*ch=='.' || *ch==',') { // floating point? Note European-friendly :)
188 ch++; // step past it
189 float weight = 1.0f;
190 while(*ch>='0' && *ch<='9') {
191 digit = (*ch)-'0'; // 0..9
192 weight *= 10.0f;
193 result += digit/weight;
194 ch++;
195 }
196 }
197 if(*ch=='%') {
198 if(percent_allowed)
199 result = result / 100.0f;
200 else
201 throw VALUE_NOT_FLOAT;
202 ch++; // step over it
203 }
204 if(*ch!='\0')
205 throw VALUE_NOT_FLOAT;
206 if(neg)
207 result = -result;
208 return result;
209 }
210
getValueAsDouble(double def,bool percent_allowed)211 double KeyValuePair::getValueAsDouble(double def,bool percent_allowed) {
212 // Will's patent parser detects errors better than atof
213 if(!valid) throw KEY_NOT_VALID;
214 char *ch = value;
215 if('\0' == *ch) return def;
216 bool neg = (*ch=='-'); // remember if negative
217 if(*ch=='+' || *ch=='-') // step past sign?
218 ch++;
219 double result = 0.0;
220 int digit;
221 while(*ch>='0' && *ch<='9') {
222 digit = (*ch)-'0'; // 0..9
223 result *= 10;
224 result += digit;
225 ch++;
226 }
227 if(*ch=='.' || *ch==',') { // floating point? Note European-friendly :)
228 ch++; // step past it
229 float weight = 1.0;
230 while(*ch>='0' && *ch<='9') {
231 digit = (*ch)-'0'; // 0..9
232 weight *= 10.0;
233 result += digit/weight;
234 ch++;
235 }
236 }
237 if(*ch=='%') {
238 if(percent_allowed)
239 result = result / 100.0;
240 else
241 throw VALUE_NOT_DOUBLE;
242 ch++; // step over it
243 }
244 if(*ch!='\0')
245 throw VALUE_NOT_DOUBLE;
246 if(neg)
247 result = -result;
248 return result;
249 }
250
getValueAsBool(bool def)251 bool KeyValuePair::getValueAsBool(bool def) {
252 if(!valid) throw KEY_NOT_VALID;
253 if('\0' == *value) return def;
254 if(strcasecmp(value,"true")==0 || strcasecmp(value,"on")==0 || strcasecmp(value,"1")==0 || strcasecmp(value,"yes")==0)
255 return true;
256 if(strcasecmp(value,"false")==0 || strcasecmp(value,"off")==0 || strcasecmp(value,"0")==0 || strcasecmp(value,"no")==0)
257 return false;
258 throw VALUE_NOT_BOOL;
259 }
260
261 // read line from input stream
262
263 // uses very c-style parsing techniques with character pointers
fromSerial(istream & is)264 istream &KeyValuePair::fromSerial(istream &is) {
265 valid = false;
266 while(!is.eof()) {
267 // get the line
268 is.getline(line,MAX_KVP_LINE_LEN);
269 linenum++;
270 //cout << "line #" << linenum << " is \"" << line << '\"' << endl;
271 // trim leading whitespace
272 key = line;
273 while(*key==' ' || *key=='\t')
274 key++;
275 // is blank line?
276 if(*key=='\0' || *key=='\r' || *key==';' || *key=='#') // empty or full-line comment?
277 continue; // try again
278 // is another section?
279 if(*key=='[')
280 break; // abort parse
281 // truncate at any trailing comment
282 char *end = key;
283 while(*end!=';' && *end!='#' && *end!='\0' && *end!='\r')
284 end++;
285 *end = '\0'; // do truncate
286 // work out split between key and value
287 value = key;
288 while(*value!='=' && *value!='\0')
289 value++;
290 if(*value=='\0') // no separator?
291 continue;
292 // trim key
293 char *ch = value;
294 while(ch>key && (*ch==' ' || *ch=='\t' || *ch=='='))
295 ch--;
296 ch++;
297 *ch = '\0'; // truncate; key done
298 if(*key=='\0') // check again
299 continue;
300 // check key for illegal characters
301 bool illegal = false;
302 for(ch=key; *ch != '\0'; ch++)
303 if(!((*ch>='A' && *ch<='Z') || (*ch>='a' && *ch<='z') ||
304 (*ch>='0' && *ch<='9') || *ch=='.' || *ch=='-' || *ch=='_')) {
305 cerr << "ini: illegal char (" << int(*ch) << ") in key on line " << linenum << endl;
306 illegal = true;
307 break;
308 }
309 if(illegal) continue;
310 // trim whitespace from leading value
311 value++; // move past =
312 while(*value==' ' || *value=='\t')
313 value++;
314 if(*value=='\0') { // value is empty?
315 cerr << "ini: value is empty on line " << linenum << " (" << key << ')' << endl;
316 continue;
317 }
318 // trim whitespace from trailing value
319 ch = end;
320 ch--;
321 while(ch>value && (*ch==' ' || *ch=='\t'))
322 ch--;
323 ch++;
324 *ch = '\0'; // truncate here too
325 // check value for illegal characters
326 illegal = false;
327 for(ch=value; *ch != '\0'; ch++)
328 if(*ch<32 || *ch>126) { // 7-bit printable ASCII
329 cerr << "ini: illegal char (" << int(*ch) << ") in value on line " << linenum << endl;
330 illegal = true;
331 break;
332 }
333 if(illegal) continue;
334 // category in key?
335 getSubCategory();
336 // done
337 valid = true;
338 break;
339 }
340 return is;
341 }
342
343 /******* INIFile class implementation ***********************/
344
INIFile(istream & is)345 INIFile::INIFile(istream &is): _is(&is), _section_ofs(NO_SECTION), _kvp() {
346 // find out if we have an empty stream
347 _is->seekg(0,ios::end); // go to the bottom of the stream
348 _bad_stream = (0 >= _is->tellg());
349 }
350
seek(const string & section)351 bool INIFile::seek(const string §ion) { // returns success
352 if(_bad_stream)
353 return false;
354 string wanted = "["; wanted += section; wanted += ']';
355 _is->clear(); // clear any flags
356 _is->seekg(0,ios::beg); // go to top of stream
357 while(!_is->eof()) {
358 _is->getline(_section,KeyValuePair::MAX_KVP_LINE_LEN);
359 //cout << "x: " << _section << endl;
360 if(str_equals(wanted,_section)) { // found?
361 _section_ofs = _is->tellg();
362 _section_ofs--; // just step back a bit to try to fix obscure bug
363 return true;
364 }
365 }
366 _section_ofs = NO_SECTION;
367 return false; // not found
368 }
369
get(const string & key)370 KeyValuePair *INIFile::get(const string &key) { // returns null if no such key
371 if(NO_SECTION == _section_ofs) // bad section?
372 return 0;
373 _is->clear(); // clear any flags
374 _is->seekg(_section_ofs,ios::beg); // seek to beginning of section data
375 _kvp = KeyValuePair();
376 *_is >> _kvp;
377 while(_kvp.isValid()) {
378 if(_kvp.is(key)) // match?
379 return &_kvp;
380 *_is >> _kvp; // next
381 }
382 return 0; // bad
383 }
384
385