1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module parser
is aliced
;
24 // ////////////////////////////////////////////////////////////////////////// //
30 string
toString () const {
31 import std
.format
: format
;
32 if (fname
.length
) return "'%s' (%d,%d)".format(fname
, line
, col
);
33 return "'?' (%d,%d)".format(line
, col
);
40 enum TT
{ EOF
, IdNum
, Str
, Delim
}
43 string
toString () const {
44 import std
.format
: format
;
45 return "Token(%s [%s])".format(type
, loc
.toString
);
54 @property bool isEOF () const nothrow @safe @nogc { return (type
== TT
.EOF
); }
55 @property bool isStr () const nothrow @safe @nogc { return (type
== TT
.Str
); }
56 @property bool isIdNum () const nothrow @safe @nogc { return (type
== TT
.IdNum
); }
57 @property bool isDelim () const nothrow @safe @nogc { return (type
== TT
.Delim
); }
59 @property bool isIdNum (const(char)[] s
) const nothrow @safe @nogc { return (type
== TT
.IdNum
&& value
== s
); }
60 @property bool isDelim (const(char)[] s
) const nothrow @safe @nogc { return (type
== TT
.Delim
&& value
== s
); }
62 @property string
unquote () const nothrow {
63 if (!isStr
) return value
;
64 assert(value
.length
>= 2 && value
[0] == '"' && value
[$-1] == '"');
67 while (pos
< value
.length
-1) {
68 if (value
[pos
] == '\\') {
70 switch (value
[pos
++]) {
71 case 'n': res
~= '\n'; break;
72 case 't': res
~= '\t'; break;
73 default: res
~= value
[pos
-1]; break;
82 bool opEquals() (in auto ref Token b
) const nothrow @safe @nogc {
83 if (type
!= b
.type
) return false;
84 if (type
== TT
.EOF
) return true;
85 return (value
== b
.value
);
90 // ////////////////////////////////////////////////////////////////////////// //
91 class SimpleIvanParser
{
94 char lastChar
, nextChar
;
98 if (lastChar
== 0) return;
99 if (lastChar
== '\n') { loc
.col
= 1; ++loc
.line
; } else ++loc
.col
;
101 if (fl
.rawRead((&nextChar
)[0..1]).length
== 0) { nextChar
= 0; return; }
102 if (nextChar
== 0) nextChar
= ' ';
106 void openFile (VFile afl
) {
109 fl
.rawReadExact((&nextChar
)[0..1]);
110 if (nextChar
== 0xEF) {
112 assert(nextChar
== 0xBB);
114 assert(nextChar
== 0xBF);
120 loc
.fname
= fl
.name
.idup
;
133 final void error (const(char)[] msg
) {
134 import std
.format
: format
;
135 throw new Exception("parser error in '%s' at (%d,%d): %s".format(loc
.fname
, loc
.line
, loc
.col
, msg
));
138 final void tokerror (const(char)[] msg
) {
139 import std
.format
: format
;
140 throw new Exception("parser error in '%s' at (%d,%d): %s".format(tok
.loc
.fname
, tok
.loc
.line
, tok
.loc
.col
, msg
));
143 final void errorAt() (in auto ref Loc aloc
, const(char)[] msg
) {
144 import std
.format
: format
;
145 throw new Exception("parser error in '%s' at (%d,%d): %s".format(aloc
.fname
, aloc
.line
, aloc
.col
, msg
));
148 final @property bool eof () const nothrow @safe @nogc { return tok
.isEOF
; }
150 final void expectDelim (const(char)[] s
) {
151 if (!tok
.isDelim || tok
.value
!= s
) tokerror("delimiter '"~s
~"' expected");
155 final void expectIdNum (const(char)[] s
) {
156 if (!tok
.isIdNum || tok
.value
!= s
) tokerror("identifier '"~s
~"' expected");
160 final string
expectIdNum () {
161 if (!tok
.isIdNum
) tokerror("delimiter expected");
162 string res
= tok
.value
;
167 final bool eatIdNum (const(char)[] s
) { if (tok
.isIdNum
&& tok
.value
== s
) { nextToken(); return true; } return false; }
168 final bool eatDelim (const(char)[] s
) { if (tok
.isDelim
&& tok
.value
== s
) { nextToken(); return true; } return false; }
171 final void skipSpaces () {
172 if (lastChar
== 0) return;
174 if (lastChar
<= ' ') { skipChar(); continue; }
175 if (lastChar
== '/' && nextChar
== '/') {
176 while (lastChar
&& lastChar
!= '\n') skipChar();
179 if (lastChar
== '/' && nextChar
== '*') {
183 if (lastChar
== '*' && nextChar
== '/') { skipChar(); skipChar(); break; }
194 tok
= Token(Token
.TT
.EOF
, null, loc
);
195 if (lastChar
== 0) return; // EOF
196 if (lastChar
== '\'') error("invalid quote");
198 if (lastChar
== '"') {
199 tok
.type
= Token
.TT
.Str
;
203 if (lastChar
== '\\') { tok
.value
~= lastChar
; skipChar(); assert(lastChar
!= 0); }
204 else if (lastChar
== '"') break;
205 tok
.value
~= lastChar
;
208 assert(lastChar
== '"');
214 if (lastChar
>= 128 ||
isalnum(lastChar
) || lastChar
== '_') {
215 tok
.type
= Token
.TT
.IdNum
;
216 while (lastChar
>= 128 ||
isalnum(lastChar
) || lastChar
== '_') {
217 tok
.value
~= lastChar
;
223 tok
.type
= Token
.TT
.Delim
;
224 if (lastChar
== '=' && nextChar
== '=') { tok
.value
= "=="; skipChar(); skipChar(); return; }
225 if (lastChar
== ':' && nextChar
== '=') { tok
.value
= ":="; skipChar(); skipChar(); return; }
226 if (lastChar
== '<' && nextChar
== '<') { tok
.value
= "<<"; skipChar(); skipChar(); return; }
227 if (lastChar
== '>' && nextChar
== '>') { tok
.value
= ">>"; skipChar(); skipChar(); return; }
228 if (lastChar
== '<' && nextChar
== '=') { tok
.value
= "<="; skipChar(); skipChar(); return; }
229 if (lastChar
== '>' && nextChar
== '=') { tok
.value
= ">="; skipChar(); skipChar(); return; }
230 if (lastChar
== '!' && nextChar
== '=') { tok
.value
= "!="; skipChar(); skipChar(); return; }
231 if (lastChar
== '<' && nextChar
== '>') { tok
.value
= "<>"; skipChar(); skipChar(); return; }
232 tok
.value
= ""~lastChar
;
238 // ////////////////////////////////////////////////////////////////////////// //
239 class IvanParser
: SimpleIvanParser
{
241 static struct State
{
243 char lastChar
, nextChar
;
253 if (mStateStack
.length
== 0) return false;
254 fl
= mStateStack
[$-1].fl
;
255 lastChar
= mStateStack
[$-1].lastChar
;
256 nextChar
= mStateStack
[$-1].nextChar
;
257 loc
= mStateStack
[$-1].loc
;
258 mStateStack
.unsafeArraySetLength(mStateStack
.length
-1);
262 void pushState (const(char)[] newfile
) {
263 if (mStateStack
.length
> 128) throw new Exception("too many includes");
264 assert(newfile
.length
);
265 mStateStack
.unsafeArraySetLength(mStateStack
.length
+1);
266 mStateStack
[$-1].fl
= fl
;
267 mStateStack
[$-1].lastChar
= lastChar
;
268 mStateStack
[$-1].nextChar
= nextChar
;
269 mStateStack
[$-1].loc
= loc
;
270 openFile(VFile(basePath
~newfile
));
275 import std
.path
: dirName
;
277 basePath
= afl
.name
.dirName
.idup
;
278 if (basePath
.length
&& basePath
[$-1] != '/') basePath
~= '/';
281 override void nextToken () {
286 if (!popState()) return;
291 if (tok
.isIdNum("Include")) {
292 super.nextToken(); // get file name
293 if (!tok
.isStr
) tokerror("string expected");
294 auto fname
= tok
.unquote
;
295 super.nextToken(); // get ";"
296 if (!tok
.isDelim(";")) tokerror("';' expected");
307 // ////////////////////////////////////////////////////////////////////////// //
311 Token
[] value
; // just tokens
313 this (SimpleIvanParser par
) {
315 void collectCurly () {
318 if (par
.tok
.isEOF
) par
.error("unexpected EOF");
320 if (par
.tok
.isDelim("{")) ++level
;
321 else if (par
.tok
.isDelim("}")) --level
;
327 while (!par
.eof
&& !par
.tok
.isDelim(";")) {
328 //{ import iv.vfs.io; writeln(" ONE: ", par.tok); }
331 if (value
[$-1].isDelim("{")) {
336 par
.expectDelim(";");
337 //{ import iv.vfs.io; writeln(" ONEendloc=", par.tok.loc, "; ", name); }
341 name
= par
.expectIdNum();
342 //{ import iv.vfs.io; writeln("fld: loc=", par.tok.loc, "; ", name); }
343 if (!par
.tok
.isDelim
) par
.expectDelim("=");
344 if (par
.eatDelim("=")) {
346 if (par
.tok
.isDelim("{")) {
347 // skip counter and comma
349 par
.expectDelim("{");
351 par
.expectDelim(",");
356 } else if (par
.eatDelim("==")) {
358 } else if (par
.eatDelim(":=")) {
360 par
.expectDelim("{");
363 par
.expectDelim("=");
365 //{ import iv.vfs.io; writeln("fld: ENDloc=", par.tok.loc, "; ", name); }
368 override bool opEquals (in Object o
) const {
369 if (o
is null) return false;
370 if (o
is this) return true;
371 if (auto op
= cast(SimpleField
)o
) return (name
== op
.name
&& value
== op
.value
);
375 override string
toString () const {
377 foreach (immutable idx
, const ref tk
; value
) {
378 if (idx
!= 0) res
~= " ";
385 @property bool asBool () const nothrow @safe @nogc {
386 if (value
.length
== 0) return false;
387 if (value
.length
> 1) return true;
388 if (value
[0].isIdNum("false")) return false;
389 if (value
[0].isIdNum("0")) return false;
395 // ////////////////////////////////////////////////////////////////////////// //
396 // "{" should be already parsed; will stop after eating ending "}"
402 SimpleField
[string
] fields
;
404 this () { name
= "<default>"; }
406 this (SimpleIvanParser par
, string aname
, bool ignoredups
=false) {
410 if (par
.eof
) par
.expectDelim("}");
411 if (par
.tok
.isDelim("{")) par
.tokerror("wtf?!");
412 if (par
.eatDelim("}")) break;
413 parseField(par
, ignoredups
);
417 void parseField (SimpleIvanParser par
, bool ignoredups
=false) {
418 auto fld = new SimpleField(par
);
419 if (fld.name
== "CanBeBurned") return;
420 if (auto ofp
= fld.name
in fields
) {
422 writeln("WARNING: ", fld.loc
, ": duplicate field '"~fld.name
~"' (prev vas at "~ofp
.loc
.toString
~")");
425 writeln("WARNING: ", fld.loc
, ": duplicate DIFFERENT field '"~fld.name
~"' (prev vas at "~ofp
.loc
.toString
~")");
427 par
.errorAt(fld.loc
, "duplicate field '"~fld.name
~"' (prev vas at "~ofp
.loc
.toString
~")");
431 fields
[fld.name
] = fld;
434 override bool opEquals (in Object o
) const {
435 if (o
is null) return false;
436 if (o
is this) return true;
437 if (auto op
= cast(SimpleConfig
)o
) {
438 if (name
!= op
.name
) return false;
440 foreach (const SimpleField
fld; op
.fields
.byValue
) {
441 if (auto myfldp
= fld.name
in fields
) {
442 if (*myfldp
!= fld) return false;
443 hit
[fld.name
] = true;
448 foreach (const SimpleField
fld; fields
.byValue
) {
449 if (fld.name
!in hit
) return false;
456 string
[] diff (in SimpleConfig other
) const {
457 assert(other
!is null);
459 if (name
!= other
.name
) { res
~= "* "~name
; res
~= "* "~other
.name
; }
462 foreach (const SimpleField myfld
; fields
.byValue
) {
463 if (myfld
.name
!in other
.fields
) continue;
464 hit
[myfld
.name
] = true;
465 if (myfld
!= other
.fields
[myfld
.name
]) {
466 res
~= "* "~myfld
.name
;
467 res
~= " "~myfld
.toString
;
468 res
~= " "~other
.fields
[myfld
.name
].toString
;
471 // remove unhit fields
472 foreach (const SimpleField myfld
; fields
.byValue
) {
473 if (myfld
.name
!in hit
) {
474 res
~= "- "~myfld
.name
;
475 res
~= " "~myfld
.toString
;
478 // add other unhit fields
479 foreach (const SimpleField itfld
; other
.fields
.byValue
) {
480 if (itfld
.name
!in fields
) {
481 res
~= "+ "~itfld
.name
;
482 res
~= " "~itfld
.toString
;
488 inout(SimpleField
) opIndex (const(char)[] afldname
) inout nothrow @safe @nogc {
489 if (auto fp
= afldname
in fields
) return cast(typeof(return))(*fp
);
495 // ////////////////////////////////////////////////////////////////////////// //
500 SimpleConfig
[string
] configs
;
502 //this (string aname) { name = aname; }
503 this (SimpleIvanParser par
, bool ignoredups
=false) {
505 name
= par
.expectIdNum();
506 auto defcfg
= new SimpleConfig();
507 defcfg
.loc
= par
.tok
.loc
;
509 par
.expectDelim("{");
510 configs
["<default>"] = defcfg
;
512 if (par
.eof
) par
.expectDelim("}");
513 if (par
.tok
.isDelim("{")) par
.tokerror("wtf?!");
514 if (par
.eatDelim("}")) break;
515 if (par
.eatIdNum("Config")) {
517 string cfg
= par
.expectIdNum();
518 if (par
.eatDelim("|")) {
519 if (cfg
== "BROKEN") {
520 cfg
= par
.expectIdNum();
522 par
.expectIdNum("BROKEN");
526 } else if (cfg
== "BROKEN") {
530 par
.expectDelim(";");
531 par
.expectDelim("{");
532 auto cc
= new SimpleConfig(par
, cfg
, ignoredups
);
534 if (auto ocp
= cc
.name
in configs
) {
536 writeln("WARNING: ", cc
.loc
, ": duplicate DIFFERENT config '"~cc
.name
~"' (prev vas at "~ocp
.loc
.toString
~")");
538 par
.errorAt(cc
.loc
, "duplicate config '"~cc
.name
~"' (prev vas at "~ocp
.loc
.toString
~")");
541 configs
[cc
.name
] = cc
;
542 //{ import iv.vfs.io; writeln("ccend: loc=", par.loc, "; ", cc.name); }
544 defcfg
.parseField(par
, ignoredups
);
549 override bool opEquals (in Object o
) const {
550 if (o
is null) return false;
551 if (o
is this) return true;
552 if (auto op
= cast(SimpleDef
)o
) {
553 if (name
!= op
.name
) return false;
555 foreach (const SimpleConfig cfg
; op
.configs
.byValue
) {
556 if (auto mycfgp
= cfg
.name
in configs
) {
557 if (*mycfgp
!= cfg
) return false;
558 hit
[cfg
.name
] = true;
563 foreach (const SimpleConfig cfg
; configs
.byValue
) {
564 if (cfg
.name
!in hit
) return false;
571 string
[] diff (in SimpleDef other
) const {
572 assert(other
!is null);
574 if (name
!= other
.name
) { res
~= "- "~name
; res
~= "+ "~other
.name
; }
577 foreach (const SimpleConfig mycfg
; configs
.byValue
) {
578 if (mycfg
.name
!in other
.configs
) continue;
579 hit
[mycfg
.name
] = true;
580 if (mycfg
!= other
.configs
[mycfg
.name
]) {
581 res
~= "*--* "~mycfg
.name
~" *--*";
582 res
~= mycfg
.diff(other
.configs
[mycfg
.name
]);
585 // remove unhit configs
586 foreach (const SimpleConfig mycfg
; configs
.byValue
) {
587 if (mycfg
.name
!in hit
) {
588 res
~= "--- "~mycfg
.name
;
591 // add other unhit fields
592 foreach (const SimpleConfig itcfg
; other
.configs
.byValue
) {
593 if (itcfg
.name
!in configs
) {
594 res
~= "+++ "~itcfg
.name
;
600 @property bool hasConfig (const(char)[] aconfig
) const nothrow @safe @nogc {
601 if (aconfig
.length
== 0) aconfig
= "<default>";
602 return ((aconfig
in configs
) !is null);
605 @property bool hasBrokenConfig (const(char)[] aconfig
) const nothrow @safe {
606 if (aconfig
.length
== 0) aconfig
= "<default>";
607 if (auto cp
= aconfig
in configs
) { if (cp
.broken
) return true; }
608 auto bkn
= "BROKEN|"~aconfig
;
609 if (auto cp
= bkn
in configs
) return cp
.broken
;
610 if (auto cp
= "BROKEN|<default>" in configs
) return cp
.broken
;
614 inout(SimpleField
) opIndex (const(char)[] aconfig
, const(char)[] afldname
) inout nothrow @safe @nogc {
615 if (aconfig
.length
== 0) aconfig
= "<default>";
616 if (auto cp
= aconfig
in configs
) {
617 if (auto fld = (*cp
)[afldname
]) return fld;
619 // check default config
620 if (aconfig
!= "<default>") {
621 if (auto cp
= "<default>" in configs
) {
622 if (auto fld = (*cp
)[afldname
]) return fld;
630 // ////////////////////////////////////////////////////////////////////////// //
631 class SimpleDefList
{
632 SimpleDef
[string
] defs
;
637 void parse (VFile fl
, bool ignoredups
=false) { parse(new SimpleIvanParser(fl
), ignoredups
); }
639 void parse (SimpleIvanParser par
, bool ignoredups
=false) {
641 auto def
= new SimpleDef(par
, ignoredups
);
642 if (auto odp
= def
.name
in defs
) {
644 writeln("WARNING: ", def
.loc
, ": duplicate DIFFERENT definition '"~def
.name
~"' (prev vas at "~odp
.loc
.toString
~")");
646 par
.errorAt(def
.loc
, "duplicate definition '"~def
.name
~"' (prev vas at "~odp
.loc
.toString
~")");
649 defs
[def
.name
] = def
;
650 if (def
.name
== "character" || def
.name
== "item") {
657 override bool opEquals (in Object o
) const {
658 if (o
is null) return false;
659 if (o
is this) return true;
660 if (auto op
= cast(SimpleDefList
)o
) {
662 foreach (const SimpleDef def
; op
.defs
.byValue
) {
663 if (auto mydefp
= def
.name
in defs
) {
664 if (*mydefp
!= def
) return false;
665 hit
[def
.name
] = true;
670 foreach (const SimpleDef def
; defs
.byValue
) {
671 if (def
.name
!in hit
) return false;
678 SimpleField
opIndex (const(char)[] adefname
, const(char)[] aconfig
, const(char)[] afldname
) nothrow @safe @nogc {
679 SimpleField
tryfld (SimpleDef def
) {
680 if (def
is null) return null;
681 // try field in given config
682 auto fld = def
[aconfig
, afldname
];
683 if (fld !is null) return fld;
684 // find field in default config
685 return def
["", afldname
];
687 // find field in given def
688 if (auto dp
= adefname
in defs
) { if (auto fld = tryfld(*dp
)) return fld; }
689 // find field in base def
690 return tryfld(maindef
);