4 // ////////////////////////////////////////////////////////////////////////// //
5 import std
.exception
: basicExceptionCtors
;
7 class ParseException
: Exception
{
8 mixin basicExceptionCtors
;
10 override string
toString() const {
11 import std
.string
: format
;
12 return "(%s,%s): %s".format(line
, col
, msg
);
17 // ////////////////////////////////////////////////////////////////////////// //
18 import std
.range
: isInputRange
;
20 auto TextParser(IR
) (auto ref IR arng
) if (isInputRange
!IR
) {
21 return TextParserImpl
!IR(arng
);
25 auto TextParser (const(char)[] str) {
26 import std
.utf
: byChar
;
27 return TextParser(str.byChar
);
31 // ////////////////////////////////////////////////////////////////////////// //
32 struct TextParserImpl(IR
) if (isInputRange
!IR
) {
41 int line
, col
; // current
42 int tline
, tcol
; // token start
43 TType tk
; // token type
45 bool tquoted
; // was current token tquoted?
48 char[256] tbuf
; // token buffer, to avoid allocations
51 bool lastWasEOL
= true;
54 @disable this (this); // no copies
56 this() (auto ref IR arng
) {
61 @property bool empty () const pure nothrow @nogc { return (tk
== TType
.EOF
); }
63 @property auto tstr () const pure nothrow @nogc { return (tk
!= TType
.EOF ? tbuf
[0..tbpos
] : null); }
64 @property char tchar () const pure nothrow @nogc { return (tk
!= TType
.EOF
&& tbpos
> 0 ? tbuf
[0] : '\0'); }
66 @property bool isLabel () const pure nothrow @nogc { return (tk
== TType
.Str
&& !tquoted
&& tstr
.length
> 0 && tstr
[$-1] == ':'); }
67 @property bool isCommand () const pure nothrow @nogc { return (tk
== TType
.Str
&& !tquoted
&& tstr
.length
> 0 && tstr
[0] == '@'); }
69 void popFront () { nextToken(); }
71 void expectDelim (char ch
) { if (tchar
!= ch
) error("'"~ch
~"' expected"); }
72 void skipDelim (char ch
) { expectDelim(ch
); nextToken(); }
74 void expectStr () { if (tk
!= TType
.Str
) error("string expected"); }
75 void expectNum () { if (tk
!= TType
.Num
) error("number expected"); }
77 void error (string msg
, string file
= __FILE__
, size_t line
= __LINE__
) {
78 auto ex
= new ParseException(msg
, file
, line
);
86 if (eof || rng
.empty
) return '\0';
93 if (rng
.empty
) { eof
= true; return '\0'; }
95 if (ch
== '\0') ch
= ' ';
97 if (lastWasEOL
) { ++line
; col
= 0; }
99 lastWasEOL
= (ch
== '\n');
103 // return first non-blank char or 0
107 if (ch
== 0) return ch
;
109 char ach
= peekChar();
111 // single-line comment
112 do { ch
= getChar(); } while (ch
!= 0 && ch
!= '\n');
113 // it's ok to go on here
114 } else if (ach
== '*') {
116 getChar(); // skip star
120 if (ch
== '*' && peekChar() == '/') {
121 getChar(); // skip slash
127 if (ch
== 0 || ch
> 32) return ch
;
132 void putChar (char ch
) {
133 if (tbpos
>= tbuf
.length
) throw new Exception("token too long");
138 static bool isIdStart() (char ch
) {
140 (ch
>= 'A' && ch
<= 'Z') ||
141 (ch
>= 'a' && ch
<= 'z') ||
145 static bool isIdChar() (char ch
) {
147 (ch
>= 'A' && ch
<= 'Z') ||
148 (ch
>= 'a' && ch
<= 'z') ||
149 (ch
>= '0' && ch
<= '9') ||
154 char ch
= skipBlanks();
159 if (ch
== 0) { tk
= TType
.EOF
; return; }
160 scope(failure
) { tquoted
= false; tk
= TType
.EOF
; }
167 if (ch
== 0) error("unterminated string");
168 if (ch
== '"') break;
172 case 't': putChar('\t'); break;
173 case 'n': putChar('\n'); break;
174 case '\\': case '"': case '\'': putChar(ch
); break;
175 case '\0': error("unterminated string"); break;
176 default: error("unknown escape code"); break;
185 if ((ch
>= '0' && ch
<= '9') ||
((ch
== '+' || ch
== '-') && peekChar() >= '0' && peekChar() <= '9')) {
190 if (ch
== '+' || ch
== '-') {
197 if (ch
< '0' || ch
> '9') break;
200 if (n
> int.max
) error("integer overflow");
204 if (n
< int.min
) error("integer overflow");
210 if (isIdStart(ch
) ||
(ch
== '@' && isIdStart(peekChar()))) {
213 if (ch
== '@') putChar(getChar());
214 while (isIdChar(peekChar())) putChar(getChar());
215 if (peekChar() == ':') putChar(getChar());