`RandomChance` type, and two new item fields: `MagicEffectChance`, `MagicEffectDuration`
[k8-i-v-a-n.git] / src / felib / feparse.cpp
blob56c621edea412247693764392babdb875d58e8ec
1 /*
3 * Iter Vehemens ad Necem (IVAN)
4 * Copyright (C) Timo Kiviluoto
5 * Released under the GNU General
6 * Public License
8 * See LICENSING which should be included
9 * along with this file for more details
12 #ifndef _GNU_SOURCE
13 # define _GNU_SOURCE
14 #endif
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
22 #include <cctype>
24 #include "feparse.h"
25 #include "femath.h"
28 ////////////////////////////////////////////////////////////////////////////////
29 struct InputFileSaved {
30 friend TextInput;
32 private:
33 TextInput *ifile;
34 void *svbuf;
35 int mCharBuf[4];
36 int mCharBufPos;
37 int mCurrentLine;
38 int mTokenLine;
39 sLong mRealPos;
41 private:
42 InputFileSaved (TextInput *aifile) : ifile(aifile), svbuf(nullptr) {
43 if (aifile) {
44 memcpy(mCharBuf, aifile->mCharBuf, sizeof(mCharBuf));
45 mCharBufPos = aifile->mCharBufPos;
46 mCurrentLine = aifile->mCurrentLine;
47 mTokenLine = aifile->mTokenLine;
48 mRealPos = aifile->realGetPos();
52 public:
53 ~InputFileSaved () {
54 if (ifile) {
55 memcpy(ifile->mCharBuf, mCharBuf, sizeof(ifile->mCharBuf));
56 ifile->mCharBufPos = mCharBufPos;
57 ifile->mCurrentLine = mCurrentLine;
58 ifile->mTokenLine = mTokenLine;
59 ifile->realSetPos(mRealPos);
65 ////////////////////////////////////////////////////////////////////////////////
66 TextInput::TextInput (const valuemap *aValueMap) {
67 setup(aValueMap);
71 TextInput::~TextInput () {
72 //fprintf(stderr, "TI:~this();\n");
73 Close();
74 //fprintf(stderr, "TI:~this(); -- exit\n");
78 void TextInput::setup (const valuemap *aValueMap) {
79 ValueMap = aValueMap;
80 lastWasNL = false;
81 lastWordWasString = false;
82 mCharBufPos = 0;
83 mCurrentLine = 1;
84 mTokenLine = 1;
85 mNumStr = "";
86 mCollectingNumStr = false;
87 mAllowFloatNums = false;
91 void TextInput::Close () {
92 //fprintf(stderr, "TI:Close();\n");
93 lastWasNL = false;
94 while (!mIfStack.empty()) mIfStack.pop();
95 mCharBufPos = 0;
96 mCurrentLine = 0;
97 mTokenLine = 0;
98 mNumStr = "";
99 mCollectingNumStr = false;
100 mAllowFloatNums = false;
101 //fprintf(stderr, "TI:Close(); -- exit\n");
105 int TextInput::GetChar () {
106 if (mCharBufPos > 0) {
107 return mCharBuf[--mCharBufPos];
108 } else {
109 if (lastWasNL) { ++mCurrentLine; lastWasNL = false; }
110 int ch = realGetChar();
111 if (ch == 0) ch = ' ';
112 lastWasNL = (ch == '\n');
113 return (ch < 0 ? EOF : ch);
118 void TextInput::UngetChar (int ch) {
119 if (ch >= 0) {
120 if (mCharBufPos > MaxUngetChars) die("too many unread chars");
121 mCharBuf[mCharBufPos++] = ch;
126 truth TextInput::Eof () {
127 if (mCharBufPos > 0) return false;
128 return isRealEof();
132 // just read `ch`, skip possible comment; returns `ch` or -1
133 int TextInput::gotCharSkipComment (int ch, truth allowSingleLineComments) {
134 if (ch < 0) ABORT("The thing that should not be");
135 if (ch != '/') return ch;
136 ch = GetChar();
137 if (ch == EOF) return '/';
138 // single-line comment?
139 if (allowSingleLineComments && ch == '/') {
140 while (ch != EOF && ch != '\n') ch = GetChar();
141 return -1;
143 // multiline comment? (possibly nested)
144 if (ch != '*') { UngetChar(ch); return '/'; }
145 int prevch = 0, level = 1;
146 for (;;) {
147 ch = GetChar();
148 if (ch == EOF) ABORT("Unterminated comment in file %s, beginning at line %d!", GetFileName().CStr(), mTokenLine);
149 // close comment
150 if (prevch == '*' && ch == '/') {
151 if (--level == 0) return -1;
152 prevch = 0;
153 continue;
155 // open comment
156 if (prevch == '/' && ch == '*') {
157 ++level;
158 prevch = 0;
159 continue;
161 // other chars
162 prevch = ch;
167 void TextInput::skipBlanks () {
168 for (;;) {
169 int ch = GetChar();
170 if (ch == EOF) return;
171 if (ch <= ' ') continue;
172 ch = gotCharSkipComment(ch);
173 if (ch < 0) continue;
174 UngetChar(ch);
175 return;
180 #define StackDepth (256)
181 int TextInput::countArrayItems (char echar) {
182 auto savedPos = InputFileSaved(this);
183 char stack[StackDepth]; // end chars
184 int sp = 0;
185 stack[0] = echar;
186 skipBlanks();
187 int ch = GetChar();
188 if (ch == EOF) return -1; // oops
189 if (ch == ',' || ch == ';') return -1;
190 //fprintf(stderr, "COUNT: ch='%c'\n", ch);
191 if (ch == echar) return 0;
192 UngetChar(ch);
193 int count = 1;
194 while (sp >= 0) {
195 skipBlanks();
196 ch = GetChar();
197 if (ch == EOF) return -1; // oops
198 // string?
199 if (ch == '"' || ch == '\'') {
200 echar = ch;
201 while (ch != EOF) {
202 ch = GetChar();
203 if (ch == '\\') {
204 ch = GetChar();
205 if (ch == EOF) return -1; // oops
206 } else if (ch == echar) {
207 break;
210 continue;
212 if (sp == 0 && (ch == ',' || ch == ';')) {
213 skipBlanks();
214 ch = GetChar();
215 if (ch == EOF) return -1;
216 if (ch == ',' || ch == ';') return -1;
217 //fprintf(stderr, " oldcount=%d; ch='%c'; sp=%d\n", count, ch, sp);
218 if (sp == 0 && ch == stack[0]) {} else ++count;
219 //fprintf(stderr, " newcount=%d; ch='%c'; sp=%d\n", count, ch, sp);
220 //if (ch != ')' && ch != ']' && ch != '}') ++count;
222 // endchar?
223 if (ch == stack[sp]) {
224 //fprintf(stderr, " *close; ch='%c'; sp=%d\n", ch, sp);
225 --sp;
226 continue;
228 // check for openings
229 switch (ch) {
230 case '(': echar = ')'; break;
231 case '[': echar = ']'; break;
232 case '{': echar = '}'; break;
233 case ')': case ']': case '}': return -1; // oops
234 default: echar = 0; break;
236 if (echar) {
237 if (sp >= StackDepth-1) return -1; // oops
238 //fprintf(stderr, " *open; ch='%c'; echar='%c'; sp=%d\n", ch, echar, sp);
239 stack[++sp] = echar;
242 return count;
244 #undef StackDepth
247 // ////////////////////////////////////////////////////////////////////////// //
248 festring TextInput::findVar (cfestring &name, truth *found) const {
249 VarMap::const_iterator i = mVars.find(name);
250 if (i != mVars.end()) {
251 if (found) *found = true;
252 return i->second;
254 if (found) *found = false;
255 return "";
259 festring TextInput::getVar (cfestring &name) {
260 truth found;
261 festring res = findVar(name, &found);
262 if (!found) {
263 if (mGetVar) {
264 res = mGetVar(this, name);
265 } else {
266 festring s = "unknown variable: "+name;
267 die(s);
270 return res;
274 void TextInput::setVar (cfestring &name, cfestring &value) {
275 mVars[name] = value;
279 //TODO: invoke callback
280 truth TextInput::delVar (cfestring &name) {
281 VarMap::iterator i = mVars.find(name);
282 if (i != mVars.end()) {
283 mVars.erase(i);
284 return true;
286 return false;
290 // ////////////////////////////////////////////////////////////////////////// //
291 void TextInput::die (cfestring &msg) {
292 ABORT("ERROR in file %s, line %d: %s", GetFileName().CStr(), mTokenLine, msg.CStr());
296 // ////////////////////////////////////////////////////////////////////////// //
297 // 0: term
298 // 1: unary
299 // 2: comparisons
300 // 3: &&
301 // 4: ||
302 static const int maxCPrio = 4;
303 static const char *opers[5][7] = {
304 {NULL},
305 {NULL},
306 {"<", ">", "<=", ">=", "==", "!=", NULL},
307 {"&&", NULL},
308 {"||", NULL}
311 festring TextInput::readCondition (festring &token, int prio, truth skipIt) {
312 festring res, op1, opc;
313 //fprintf(stderr, "IN: prio: %d; skip: %s; [%s]\n", prio, skipIt?"t":"o", token.CStr());
314 switch (prio) {
315 case 0: // term
316 if (token == "(") {
317 readWordIntr(token, true);
318 res = readCondition(token, maxCPrio, skipIt);
319 if (token != ")") die("')' expected");
320 } else if (token == "@") {
321 readWordIntr(token, true);
322 if (!skipIt) res = getVar(token);
323 } else {
324 res = token;
326 readWordIntr(token, true);
327 goto done;
328 //return res;
329 case 1:
330 if (token == "!") {
331 readWordIntr(token, true);
332 res = readCondition(token, 1, skipIt);
333 if (!skipIt) {
334 if (res == "") res = "tan"; else res = "";
336 } else {
337 res = readCondition(token, prio-1, skipIt);
339 goto done;
340 //return res;
343 if (prio > 4) return res;
344 res = readCondition(token, prio-1, skipIt);
345 for (;;) {
346 //readWordIntr(token, true);
347 bool myOp = false;
348 if (token == "=") die("no assignments yet!");
349 if (token == ";") {
350 //fprintf(stderr, " RET: [%s]\n", res.CStr());
351 break;
353 if (token == "less") token = "<";
354 else if (token == "great") token = ">";
355 else if (token == "equ") token = "==";
356 else if (token == "neq") token = "!=";
357 else if (token == "lessequ") token = "<=";
358 else if (token == "greatequ") token = ">=";
359 for (int f = 0; opers[prio][f]; f++) {
360 if (!strcmp(opers[prio][f], token.CStr())) { myOp = true; break; }
362 //fprintf(stderr, "tk: [%s]; %s\n", token.CStr(), myOp?"MY":"skip");
363 if (!myOp) break;
364 opc = token;
365 readWordIntr(token, true);
366 op1 = readCondition(token, prio-1, skipIt);
367 //fprintf(stderr, " prio: %d; opc=[%s]; res=[%s]; op1=[%s]\n", prio, opc.CStr(), res.CStr(), op1.CStr());
368 switch (prio) {
369 case 2: // comparisons
370 if (opc == "==") {
371 if (!skipIt) res = (res == op1 ? "tan" : "");
372 } else if (opc == "!=") {
373 if (!skipIt) res = (res != op1 ? "tan" : "");
374 } else if (opc == "<") {
375 if (!skipIt) res = (res < op1 ? "tan" : "");
376 } else if (opc == ">") {
377 if (!skipIt) res = (res > op1 ? "tan" : "");
378 } else if (opc == "<=") {
379 if (!skipIt) res = (res <= op1 ? "tan" : "");
380 } else if (opc == ">=") {
381 if (!skipIt) res = (res >= op1 ? "tan" : "");
383 break;
384 case 3: // &&
385 if (opc == "&&") {
386 if (!skipIt) {
387 res = (res != "" && op1 != "" ? "tan" : "");
388 if (res == "") skipIt = true;
391 break;
392 case 4: // ||
393 if (opc == "||") {
394 if (!skipIt) {
395 res = (res != "" || op1 != "" ? "tan" : "");
396 if (res != "") skipIt = true;
399 break;
400 default:
401 die("invalid priority");
404 done:
405 //fprintf(stderr, "OUT: prio: %d; skip: %s; [%s]\n", prio, skipIt?"t":"o", token.CStr());
406 return res;
410 // stack top:
411 // 1: processing 'then'
412 // 2: processing 'else'
413 // -1: skiping 'then'
414 // -2: skiping 'else'
415 // -3: skiping whole 'if', 'then' part
416 // -4: skiping whole 'if', 'else' part
417 // -666: skiping '{}'
418 // 666: in '{}', processing
419 truth TextInput::ReadWord (festring &str, truth abortOnEOF) {
420 for (;;) {
421 int prc = (mIfStack.empty() ? 0 : mIfStack.top());
422 if (!readWordIntr(str, abortOnEOF)) return false; // EOF
423 if (str == "if") {
424 readWordIntr(str, true);
425 festring res = readCondition(str, maxCPrio, prc<0);
426 if (str != ";") die("';' expected");
427 if (prc < 0) {
428 // skiping
429 mIfStack.push(-3);
430 } else {
431 mIfStack.push(res.IsEmpty() ? -1 : 1);
433 continue;
435 if (str == "else") {
436 switch (prc) {
437 case 1: // processing 'then'
438 mIfStack.pop();
439 mIfStack.push(-2);
440 break;
441 case -1: // skiping 'then'
442 mIfStack.pop();
443 mIfStack.push(2);
444 break;
445 case -3: // skiping whole, 'then'
446 mIfStack.pop();
447 mIfStack.push(-4);
448 break;
449 default: die("unexpected 'else'");
451 continue;
453 if (str == "endif") {
454 switch (prc) {
455 case 1: // processing 'then'
456 case 2: // processing 'else'
457 case -1: // skiping 'then'
458 case -2: // skiping 'else'
459 case -3: // skiping whole, 'then'
460 case -4: // skiping whole, 'else'
461 mIfStack.pop();
462 break;
463 default: die("unexpected 'endif'");
465 continue;
467 if (str == "{") {
468 mIfStack.push(prc >= 0 ? 666 : -666);
469 if (prc >= 0) return true;
470 continue;
472 if (str == "}") {
473 if (abs(prc) != 666) die("unexpected '}'");
474 mIfStack.pop();
475 if (prc >= 0) return true;
476 continue;
478 if (prc >= 0) return true;
483 festring TextInput::ReadWord (truth abortOnEOF) {
484 festring ToReturn;
485 ReadWord(ToReturn, abortOnEOF);
486 return ToReturn;
490 truth TextInput::readWordIntr (festring &String, truth abortOnEOF) {
491 String.Empty();
492 lastWordWasString = false;
493 skipBlanks();
494 mTokenLine = mCurrentLine;
495 for (;;) {
496 int ch = GetChar();
497 if (ch == EOF) {
498 if (abortOnEOF) ABORT("Unexpected end of file %s!", GetFileName().CStr());
499 return false;
501 // identifier?
502 if (isalpha(ch) || ch == '_') {
503 String << (char)(ch);
504 for (;;) {
505 ch = GetChar();
506 if (ch == EOF) break;
507 if (ch != '_' && !isalpha(ch) && !isdigit(ch)) { UngetChar(ch); break; }
508 String << (char)(ch);
510 return true;
512 // number?
513 if (isdigit(ch)) {
514 String << (char)(ch);
515 bool wasdot = !mAllowFloatNums;
516 bool ishex = false;
517 // allow hex literals
518 if (!mAllowFloatNums && ch == '0') {
519 ch = GetChar();
520 if (ch == 'X' || ch == 'x') {
521 ishex = true;
522 String << 'x';
523 } else {
524 UngetChar(ch);
527 for (;;) {
528 ch = GetChar();
529 if (ch == EOF) break;
530 if (ch == '_') continue;
531 if (ch == '.') {
532 if (wasdot) die("invalid number");
533 wasdot = true;
534 String << '.';
535 continue;
537 if (ishex) {
538 if (isxdigit(ch)) { String << (char)(ch); continue; }
539 } else {
540 if (isdigit(ch)) { String << (char)(ch); continue; }
542 if (isalpha(ch)) die("invalid number");
543 UngetChar(ch);
544 break;
546 return true;
548 // string?
549 if (ch == '"') {
550 lastWordWasString = true;
551 for (;;) {
552 ch = GetChar();
553 if (ch == EOF) ABORT("Unterminated string in file %s, beginning at line %d!", GetFileName().CStr(), mTokenLine);
554 if (ch == '"') return true;
555 if (ch == '\\') {
556 ch = GetChar();
557 if (ch == EOF) ABORT("Unterminated string in file %s, beginning at line %d!", GetFileName().CStr(), mTokenLine);
558 switch (ch) {
559 case 't': String << '\t'; break;
560 case 'n': String << '\n'; break;
561 case 'r': String << '\r'; break;
562 case '1': String << '\x01'; break;
563 case '2': String << '\x02'; break;
564 case '"': String << '"'; break;
565 default: ABORT("Invalid escape in string in file %s at line %d!", GetFileName().CStr(), mTokenLine);
567 continue;
569 String << (char)ch;
572 // punctuation
573 ch = gotCharSkipComment(ch);
574 if (ch < 0) continue;
575 // delimiter
576 String << (char)ch;
577 // two-char delimiters?
578 if (ch == '=' || ch == '<' || ch == '>' || ch == '!') {
579 ch = GetChar();
580 if (ch == '=') String << (char)ch; else UngetChar(ch);
581 } else if (ch == '&' || ch == '|') {
582 int c1 = GetChar();
583 if (c1 == ch) String << (char)c1; else UngetChar(c1);
584 } else if (ch == ':') {
585 ch = GetChar();
586 if (ch == '=') String << (char)ch; else UngetChar(ch);
588 return true;
593 char TextInput::ReadLetter (truth abortOnEOF) {
594 mTokenLine = mCurrentLine;
595 for (;;) {
596 int ch = GetChar();
597 if (ch == EOF) {
598 if (abortOnEOF) ABORT("Unexpected end of file %s!", GetFileName().CStr());
599 return 0;
601 if (ch <= ' ') continue;
602 //ch = gotCharSkipComment(ch);
603 //if (ch >= 0) return ch;
604 return ch;
609 /* Reads a number or a formula from inputfile. Valid values could be for
610 instance "3", "5 * 4+5", "2+Variable%4" etc. */
611 //sLong inputfile::ReadNumber (int CallLevel, truth PreserveTerminator) {
612 template<typename numtype> festring TextInput::ReadNumberIntr (int CallLevel, numtype *num, truth *isString, truth allowStr, truth PreserveTerminator, truth *wasCloseBrc, truth allowFloats) {
613 struct AllowFloatSaver {
614 truth oldallowfloat;
615 truth *var;
616 AllowFloatSaver (truth *avar, truth newval) { oldallowfloat = *avar; var = avar; *avar = newval; }
617 ~AllowFloatSaver () { *var = oldallowfloat; }
619 numtype Value = 0;
620 festring Word, res;
621 truth NumberCorrect = false;
622 truth firstWord = true;
623 if (isString) *isString = false;
624 if (num) *num = 0;
625 if (wasCloseBrc) *wasCloseBrc = false;
626 mTokenLine = mCurrentLine;
627 auto oldflt = AllowFloatSaver(&mAllowFloatNums, allowFloats);
628 //fprintf(stderr, ">>> ReadNumberIntr()\n");
629 for (;;) {
630 ReadWord(Word);
631 //fprintf(stderr, " ReadNumberIntr: word='%s'\n", Word.CStr());
632 // specials?
633 if (Word == "@") {
634 // variable
635 if (mCollectingNumStr) mNumStr << Word;
636 ReadWord(Word, true);
637 if (mCollectingNumStr) mNumStr << Word;
638 //fprintf(stderr, "var: [%s]\n", Word.CStr());
639 Word = getVar(Word);
640 //fprintf(stderr, " value: [%s]\n", Word.CStr());
641 const char *s = Word.CStr();
642 char *e;
643 sLong l = strtoll(s, &e, 10);
644 if (*e == '\0') {
645 //fprintf(stderr, " number: [%d]\n", l);
646 Value = (numtype)l;
647 NumberCorrect = true;
648 continue;
650 if (firstWord && allowStr) {
651 if (isString) *isString = true;
652 return Word;
653 } else {
654 ABORT("Number expected in file %s, line %d!", GetFileName().CStr(), mTokenLine);
657 // first word?
658 if (firstWord) {
659 if (allowStr && lastWordWasString) {
660 if (isString) *isString = true;
661 ReadWord(res);
662 if (res.GetSize() == 1) {
663 if (res[0] != ';' && res[0] != ',' && res[0] != ':') {
664 ABORT("Invalid terminator in file %s, line %d!", GetFileName().CStr(), mTokenLine);
666 if (PreserveTerminator) UngetChar(res[0]);
667 } else {
668 ABORT("Terminator expected in file %s, line %d!", GetFileName().CStr(), mTokenLine);
670 return Word;
672 firstWord = false;
674 // other things
675 char First = Word[0];
676 // number?
677 if (isdigit(First)) {
678 if (mCollectingNumStr) mNumStr << Word;
679 char *e;
680 if (allowFloats) {
681 Value = (numtype)strtod(Word.CStr(), &e);
682 } else {
683 Value = (numtype)strtol(Word.CStr(), &e, 0);
685 if (*e != '\0') ABORT("Invalid number '%s' in file %s, line %d!", Word.CStr(), GetFileName().CStr(), mTokenLine);
686 NumberCorrect = true;
687 // HACK: autoinsert terminator
688 skipBlanks();
689 int ch = GetChar();
690 if (ch != EOF) {
691 UngetChar(ch);
692 if (ch == '}') UngetChar(';');
694 continue;
696 // delimiter/math?
697 if (Word.GetSize() == 1) {
698 //fprintf(stderr, " ReadNumberIntr: First='%c'\n", First);
699 if (First == ';' || First == ',' || First == ':' || (wasCloseBrc && First == '}')) {
700 if (First == '}' && wasCloseBrc) *wasCloseBrc = true;
701 if (CallLevel != HIGHEST || PreserveTerminator) UngetChar(First);
702 if (num) *num = Value;
703 return res;
705 if (First == ')') {
706 if ((CallLevel != HIGHEST && CallLevel != 4) || PreserveTerminator) UngetChar(')');
707 if (num) *num = Value;
708 return res;
710 if (First == '~') {
711 if (mCollectingNumStr) mNumStr << Word;
712 Value = ~ReadNumber(4);
713 NumberCorrect = true;
714 continue;
716 /* Convert this into an inline function! */
717 #define CHECK_OP(op, cl, opertype) \
718 if (First == #op[0]) { \
719 if (cl < CallLevel) {\
720 if (mCollectingNumStr) mNumStr << Word; \
721 /*Value op##= ReadNumber(cl);*/\
722 numtype rhs = 0;\
723 ReadNumberIntr<numtype>(cl, &rhs, nullptr, false, false, nullptr, allowFloats);\
724 /*Value op##= rhs;*/\
725 Value = (numtype)((opertype)Value op (opertype)rhs);\
726 NumberCorrect = true;\
727 continue;\
728 } else {\
729 UngetChar(#op[0]);\
730 if (num) *num = Value;\
731 return res;\
734 CHECK_OP(&, 1, int);
735 CHECK_OP(|, 1, int);
736 CHECK_OP(^, 1, int);
737 CHECK_OP(*, 2, numtype);
738 CHECK_OP(/, 2, numtype);
739 CHECK_OP(%, 2, int);
740 CHECK_OP(+, 3, numtype);
741 CHECK_OP(-, 3, numtype);
742 #undef CHECK_OP
743 if (First == '<') {
744 char Next = GetChar();
745 if (Next == '<') {
746 if (1 < CallLevel) {
747 if (mCollectingNumStr) mNumStr << "<<";
748 //Value <<= ReadNumber(1);
749 Value = (numtype)((int)Value<<(int)ReadNumber(1));
750 NumberCorrect = true;
751 continue;
752 } else {
753 UngetChar('<');
754 UngetChar('<');
755 if (num) *num = Value;
756 return res;
758 } else {
759 UngetChar(Next);
762 if (First == '>') {
763 char Next = GetChar();
764 if (Next == '>') {
765 if (1 < CallLevel) {
766 if (mCollectingNumStr) mNumStr << ">>";
767 //Value >>= ReadNumber(1);
768 Value = (numtype)((int)Value>>(int)ReadNumber(1));
769 NumberCorrect = true;
770 continue;
771 } else {
772 UngetChar('>');
773 UngetChar('>');
774 if (num) *num = Value;
775 return res;
777 } else {
778 UngetChar(Next);
781 if (First == '(') {
782 if (NumberCorrect) {
783 UngetChar('(');
784 if (num) *num = Value;
785 return res;
786 } else {
787 if (mCollectingNumStr) mNumStr << Word;
788 Value = ReadNumber(4);
789 if (mCollectingNumStr) mNumStr << ")";
790 NumberCorrect = false;
791 continue;
794 if (First == '=' && CallLevel == HIGHEST) continue;
795 if (First == '#') {
796 // for #defines
797 UngetChar('#');
798 if (num) *num = Value;
799 return res;
803 if (Word == "enum" || Word == "bitenum") {
804 if (CallLevel != HIGHEST || PreserveTerminator) UngetChar(';');
805 if (num) *num = Value;
806 return res;
809 // rgbX?
810 if (Word == "rgb16" || Word == "rgb24") {
811 truth is16 = (Word == "rgb16");
812 if (mCollectingNumStr) mNumStr << Word;
813 int Red = ReadNumber();
814 if (Red < 0 || Red > 255) ABORT("Illegal Red value (%d) file %s, line %d!", Red, GetFileName().CStr(), mTokenLine);
815 int Green = ReadNumber();
816 if (Green < 0 || Green > 255) ABORT("Illegal Green value (%d) file %s, line %d!", Green, GetFileName().CStr(), mTokenLine);
817 int Blue = ReadNumber();
818 if (Blue < 0 || Blue > 255) ABORT("Illegal Blue value (%d) file %s, line %d!", Blue, GetFileName().CStr(), mTokenLine);
819 Value = (is16 ? MakeRGB16(Red, Green, Blue) : MakeRGB24(Red, Green, Blue));
820 NumberCorrect = true;
821 continue;
823 // `true` literal?
824 if (Word == "true" || Word == "tan") {
825 if (mCollectingNumStr) mNumStr << Word;
826 Value = 1;
827 NumberCorrect = true;
828 continue;
830 // `false` literal?
831 if (Word == "false" || Word == "ona") {
832 if (mCollectingNumStr) mNumStr << Word;
833 Value = 0;
834 NumberCorrect = true;
835 continue;
837 // known value?
838 if (ValueMap) {
839 valuemap::const_iterator Iterator = ValueMap->find(Word);
840 if (Iterator != ValueMap->end()) {
841 if (mCollectingNumStr) mNumStr << Word;
842 Value = Iterator->second;
843 NumberCorrect = true;
844 continue;
847 // something bad
848 ABORT("Odd numeric value \"%s\" encountered in file %s, line %d!", Word.CStr(), GetFileName().CStr(), mTokenLine);
853 sLong TextInput::ReadNumber (int CallLevel, truth PreserveTerminator, truth *wasCloseBrc) {
854 sLong num = 0;
855 ReadNumberIntr<sLong>(CallLevel, &num, nullptr, false, PreserveTerminator, wasCloseBrc, false);
856 return num;
860 festring TextInput::ReadStringOrNumber (sLong *num, truth *isString, truth PreserveTerminator, truth *wasCloseBrc) {
861 return ReadNumberIntr<sLong>(HIGHEST, num, isString, true, PreserveTerminator, wasCloseBrc, false);
865 float TextInput::ReadFloat () {
866 float num = 0;
867 ReadNumberIntr<float>(HIGHEST, &num, nullptr, false, false, nullptr, true);
868 return num;
872 // ////////////////////////////////////////////////////////////////////////// //
873 // fuck you, shitplusplus!
874 struct FuckedShitForFuckedFinally {
875 truth *var;
876 truth oval;
877 FuckedShitForFuckedFinally (truth *avar, truth nval=true) {
878 var = avar;
879 oval = *var;
880 *var = nval;
882 ~FuckedShitForFuckedFinally () {
883 *var = oval;
888 sLong TextInput::ReadNumberKeepStr (int CallLevel, truth PreserveTerminator, truth *wasCloseBrc) {
889 auto fuck = FuckedShitForFuckedFinally(&mCollectingNumStr);
890 mNumStr = "";
891 return ReadNumber(CallLevel, PreserveTerminator, wasCloseBrc);
895 festring TextInput::ReadStringOrNumberKeepStr (sLong *num, truth *isString, truth PreserveTerminator, truth *wasCloseBrc) {
896 auto fuck = FuckedShitForFuckedFinally(&mCollectingNumStr);
897 mNumStr = "";
898 return ReadStringOrNumber(num, isString, PreserveTerminator, wasCloseBrc);
902 // ////////////////////////////////////////////////////////////////////////// //
903 v2 TextInput::ReadVector2d () {
904 skipBlanks();
905 int ch = GetChar();
906 if (ch == '{') ch = '}'; else { UngetChar(ch); ch = 0; }
908 v2 Vector;
909 Vector.X = ReadNumber();
910 Vector.Y = ReadNumber();
912 if (ch) {
913 skipBlanks();
914 if (GetChar() != ch) ABORT("Vector syntax error: \"%c\" expected in file %s, line %d!", ch, GetFileName().CStr(), TokenLine());
915 skipBlanks();
916 ch = GetChar();
917 if (ch == '}') UngetChar(ch);
918 else if (ch != ';' && ch != ',') ABORT("Vector syntax error: terminator expected in file %s, line %d!", GetFileName().CStr(), TokenLine());
921 return Vector;
925 rect TextInput::ReadRect () {
926 skipBlanks();
927 int ch = GetChar();
928 if (ch == '{') ch = '}'; else { UngetChar(ch); ch = 0; }
930 rect Rect;
931 Rect.X1 = ReadNumber();
932 Rect.Y1 = ReadNumber();
933 Rect.X2 = ReadNumber();
934 Rect.Y2 = ReadNumber();
936 if (ch) {
937 skipBlanks();
938 if (GetChar() != ch) ABORT("Vector syntax error: \"%c\" expected in file %s, line %d!", ch, GetFileName().CStr(), TokenLine());
939 skipBlanks();
940 ch = GetChar();
941 if (ch == '}') UngetChar(ch);
942 else if (ch != ';' && ch != ',') ABORT("Vector syntax error: terminator expected in file %s, line %d!", GetFileName().CStr(), TokenLine());
945 return Rect;
949 // ////////////////////////////////////////////////////////////////////////// //
950 #define GETC(var) do { \
951 var = inFile->GetChar(); \
952 if (var == EOF) { \
953 if (infStack.empty()) ABORT("'}' missing in datafile %s line %d!", inFile->GetFileName().CStr(), inFile->TokenLine()); \
954 delete inFile; \
955 *iff = inFile = infStack.top(); \
956 infStack.pop(); \
957 continue; \
959 break; \
960 } while (1) \
963 festring collectSourceCode (std::stack<TextInput *> &infStack, TextInput **iff) {
964 if (!iff || !*iff) ABORT("Wut?!");
965 TextInput *inFile = *iff;
966 int brclevel = 0;
967 int ch;
968 festring res;
970 for (;;) {
971 GETC(ch);
972 res << (char)ch;
973 // brackets?
974 if (ch == '{') { ++brclevel; continue; }
975 if (ch == '}') { if (--brclevel == 0) break; continue; }
976 // string?
977 if (ch == '"') {
978 // waiting for bracket?
979 if (brclevel == 0) ABORT("'{' expected in datafile %s line %d!", inFile->GetFileName().CStr(), inFile->TokenLine()); \
980 for (;;) {
981 GETC(ch);
982 res << (char)ch;
983 if (ch == '"') break;
984 if (ch == '\\') {
985 GETC(ch);
986 res << (char)ch;
989 continue;
991 // comment
992 if (ch == '/') {
993 GETC(ch);
994 res << (char)ch;
995 // single-line?
996 if (ch == '/') {
997 for (;;) {
998 GETC(ch);
999 res << (char)ch;
1000 if (ch == '\n') break;
1002 continue;
1004 // multiline?
1005 if (ch == '*') {
1006 int prevch = 0;
1007 int level = 1;
1008 for (;;) {
1009 GETC(ch);
1010 res << (char)ch;
1011 // comment start (nested)
1012 if (prevch == '/' && ch == '*') {
1013 ++level;
1014 prevch = 0;
1015 continue;
1017 // comment end
1018 if (prevch == '*' && ch == '/') {
1019 if (--level == 0) break;
1020 prevch = 0;
1021 continue;
1023 prevch = ch;
1025 continue;
1027 // waiting for bracket?
1028 if (brclevel == 0) ABORT("'{' expected in datafile %s line %d!", inFile->GetFileName().CStr(), inFile->TokenLine()); \
1029 continue;
1031 if (brclevel == 0 && ch > ' ') ABORT("'{' expected in datafile %s line %d!", inFile->GetFileName().CStr(), inFile->TokenLine()); \
1034 return res;
1037 #undef GETC
1040 EventHandlerSource collectEventHandlerSource (std::stack<TextInput *> &infStack, TextInput **iff) {
1041 if (!iff || !*iff) ABORT("Wut?!");
1042 TextInput *inFile = *iff;
1043 EventHandlerSource res;
1044 // read type
1045 res.fname = inFile->GetFileName();
1046 res.type = inFile->ReadWord();
1047 res.startline = inFile->CurrentLine();
1048 res.text = collectSourceCode(infStack, iff);
1049 return res;
1053 // ////////////////////////////////////////////////////////////////////////// //
1054 EventHandlerMap::EventHandlerMap () {
1058 EventHandlerMap::~EventHandlerMap () {
1059 clear();
1063 void EventHandlerMap::clear () {
1064 mMap.clear();
1068 // "on" skipped, expecting type
1069 void EventHandlerMap::collectSource (std::stack<TextInput *> &infStack, TextInput **iff) {
1070 auto ehs = collectEventHandlerSource(infStack, iff);
1071 if (ehs.type.IsEmpty()) ABORT("Empty handler type in file '%s' at line %d", ehs.fname.CStr(), ehs.startline);
1072 auto it = mMap.find(ehs.type);
1073 if (it != mMap.end()) {
1074 //ABORT("Duplicate handler type '%s' in file '%s' at line %d", ehs.type.CStr(), ehs.fname.CStr(), ehs.startline);
1075 mMap.erase(it);
1077 mMap.insert(std::make_pair(ehs.type, ehs));
1081 // "on" skipped, expecting type
1082 void EventHandlerMap::collectSource (TextInput &iff) {
1083 std::stack<TextInput *> infStack;
1084 TextInput *ifp = &iff;
1085 infStack.push(ifp);
1086 collectSource(infStack, &ifp);
1090 // can return `nullptr`
1091 TextInput *EventHandlerMap::openHandler (cfestring &atype, const valuemap *aValueMap) const {
1092 auto it = mMap.find(atype);
1093 if (it == mMap.end()) return nullptr;
1094 return new MemTextFile((*it).second.fname, (*it).second.startline, (*it).second.text, aValueMap);
1098 // ////////////////////////////////////////////////////////////////////////// //
1099 void ReadData (festring &String, TextInput &SaveFile) {
1100 SaveFile.ReadWord(String);
1101 if (String == "=") SaveFile.ReadWord(String);
1102 SaveFile.ReadWord();
1106 void ReadData (fearray<sLong> &Array, TextInput &SaveFile) {
1107 Array.Clear();
1108 festring Word;
1109 SaveFile.ReadWord(Word);
1110 if (Word == "==") {
1111 Array.Allocate(1);
1112 Array.Data[0] = SaveFile.ReadNumber();
1113 } else if (Word == ":=") {
1114 SaveFile.ReadWord(Word);
1115 if (Word != "{") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1116 std::vector<sLong> v;
1117 for (;;) {
1118 truth wasCloseBrc = false;
1119 sLong n = SaveFile.ReadNumber(HIGHEST, false, &wasCloseBrc);
1120 if (wasCloseBrc) break;
1121 v.push_back(n);
1123 Array.Allocate(v.size());
1124 for (unsigned int f = 0; f < v.size(); ++f) Array.Data[f] = v[f];
1125 } else if (Word == "=") {
1126 SaveFile.ReadWord(Word);
1127 if (Word != "{") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1128 fearray<sLong>::sizetype Size = SaveFile.ReadNumber();
1129 Array.Allocate(Size);
1130 for (fearray<sLong>::sizetype c = 0; c < Size; ++c) Array.Data[c] = SaveFile.ReadNumber();
1131 if (SaveFile.ReadWord() != "}") ABORT("Illegal array terminator \"%s\" encountered in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1132 } else {
1133 ABORT("Array syntax error: '=', '==' or ':=' expected in file %s, line %d!", SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1138 void ReadData (fearray<festring> &Array, TextInput &SaveFile) {
1139 Array.Clear();
1140 festring Word;
1141 SaveFile.ReadWord(Word);
1142 if (Word == "==") {
1143 Array.Allocate(1);
1144 SaveFile.ReadWord(Array.Data[0]);
1145 if (SaveFile.ReadWord() != ";") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1146 } else if (Word == ":=") {
1147 SaveFile.ReadWord(Word);
1148 if (Word != "{") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1150 std::vector<festring> v;
1152 for (;;) {
1153 SaveFile.ReadWord(Word);
1154 if (Word == "}") break;
1155 v.push_back(Word);
1156 SaveFile.ReadWord(Word);
1157 if (Word == "}") break;
1158 if (Word != "," && Word != ";") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1160 Array.Allocate(v.size());
1161 for (unsigned int f = 0; f < v.size(); ++f) Array.Data[f] = v[f];
1162 } else if (Word == "=") {
1163 SaveFile.ReadWord(Word);
1164 if (Word != "{") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1165 fearray<festring>::sizetype Size = SaveFile.ReadNumber();
1166 Array.Allocate(Size);
1167 for (fearray<festring>::sizetype c = 0; c < Size; ++c) {
1168 SaveFile.ReadWord(Array.Data[c]);
1169 SaveFile.ReadWord(Word);
1170 if (Word != "," && Word != ";") ABORT("Array syntax error \"%s\" found in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1172 if (SaveFile.ReadWord() != "}") ABORT("Illegal array terminator \"%s\" encountered in file %s, line %d!", Word.CStr(), SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1173 } else {
1174 ABORT("Array syntax error: '=', '==' or ':=' expected in file %s, line %d!", SaveFile.GetFileName().CStr(), SaveFile.TokenLine());
1179 void ReadData (RandomChance &rc, TextInput &fl) {
1180 festring w;
1181 rc.clear();
1182 w = fl.ReadWord();
1183 auto tkline = fl.TokenLine();
1184 if (w == "==") {
1185 rc.rnd = fl.ReadNumber();
1186 } else if (w == "=" || w == ":=") {
1187 if (fl.ReadWord() != "{") ABORT("RandomChance syntax error: '{' expected in file %s, line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1188 for (;;) {
1189 w = fl.ReadWord();
1190 if (w == "}") break;
1191 sLong *fptr = nullptr;
1192 if (w.CompareIgnoreCase("add") == 0) fptr = &rc.add;
1193 else if (w.CompareIgnoreCase("rnd") == 0) fptr = &rc.rnd;
1194 else if (w.CompareIgnoreCase("rand") == 0) fptr = &rc.rnd;
1195 else if (w.CompareIgnoreCase("rmin") == 0) fptr = &rc.rmin;
1196 else if (w.CompareIgnoreCase("rmax") == 0) fptr = &rc.rmax;
1197 if (!fptr) ABORT("RandomChance syntax error: unknown field '%s' in file %s, line %d!", w.CStr(), fl.GetFileName().CStr(), fl.TokenLine());
1198 w = fl.ReadWord();
1199 if (w != ":" && w != "=") ABORT("RandomChance syntax error: ':' expected in file %s, line %d!", fl.GetFileName().CStr(), fl.TokenLine());
1200 *fptr = fl.ReadNumber();
1202 } else {
1203 ABORT("RandomChance syntax error: '=' or '==' expected in file %s, line %d!", fl.GetFileName().CStr(), tkline);
1205 if (rc.rnd < 0) ABORT("Invalid random chance in file %s, line %d!", fl.GetFileName().CStr(), tkline);
1209 // ////////////////////////////////////////////////////////////////////////// //
1210 TextInputFile::TextInputFile (cfestring &FileName, const valuemap *aValueMap, truth AbortOnErr) {
1211 ifile.Open(FileName, AbortOnErr);
1212 setup(aValueMap);
1216 TextInputFile::~TextInputFile () {
1217 //fprintf(stderr, "TIF:~this();\n");
1218 Close();
1219 //fprintf(stderr, "TIF:~this(); -- exit\n");
1223 cfestring &TextInputFile::GetFileName () const { return ifile.GetFileName(); }
1224 int TextInputFile::realGetChar () { return ifile.Get(); }
1225 truth TextInputFile::isRealEof () { return ifile.Eof(); }
1226 truth TextInputFile::IsOpen () { return ifile.IsOpen(); }
1227 void TextInputFile::Close () { /*fprintf(stderr, "TIF:Close();\n");*/ ifile.Close(); TextInput::Close(); }
1229 sLong TextInputFile::realGetPos () { return (ifile.IsOpen() ? ifile.TellPos() : 0); }
1230 void TextInputFile::realSetPos (sLong apos) { if (ifile.IsOpen()) ifile.SeekPosBegin(apos); }
1234 // ////////////////////////////////////////////////////////////////////////// //
1235 MemTextFile::MemTextFile (cfestring &afname, cfestring &str, const valuemap *aValueMap) :
1236 buf(nullptr),
1237 bufSize(0),
1238 bufPos(0),
1239 tfname(afname)
1241 bufSize = str.GetSize();
1242 buf = (unsigned char *)calloc(1, bufSize+1);
1243 memmove(buf, str.CStr(), bufSize);
1244 setup(aValueMap);
1248 MemTextFile::MemTextFile (cfestring &afname, int stline, cfestring &str, const valuemap *aValueMap) :
1249 buf(nullptr),
1250 bufSize(0),
1251 bufPos(0),
1252 tfname(afname)
1254 bufSize = str.GetSize();
1255 buf = (unsigned char *)calloc(1, bufSize+1);
1256 memmove(buf, str.CStr(), bufSize);
1257 setup(aValueMap);
1258 mCurrentLine = mTokenLine = stline;
1262 MemTextFile::~MemTextFile () {
1263 //fprintf(stderr, "MTF:~this();\n");
1264 Close();
1265 //fprintf(stderr, "MTF:~this(); -- exit\n");
1269 int MemTextFile::realGetChar () {
1270 if (bufPos >= bufSize) return EOF;
1271 return buf[bufPos++];
1275 cfestring &MemTextFile::GetFileName () const { return tfname; }
1276 truth MemTextFile::isRealEof () { return (bufPos >= bufSize); }
1277 truth MemTextFile::IsOpen () { return (buf != nullptr); }
1280 void MemTextFile::Close () {
1281 //fprintf(stderr, "MTF:Close();\n");
1282 if (buf) {
1283 free(buf);
1284 buf = nullptr;
1285 tfname = "";
1286 bufSize = 0;
1287 bufPos = 0;
1289 TextInput::Close();
1292 sLong MemTextFile::realGetPos () { return bufPos; }
1293 void MemTextFile::realSetPos (sLong apos) { if (buf != nullptr) bufPos = apos; }