console logger; template loader
[dd2d.git] / tparser.d
blobd00b83c65e4458d4ba4debcc298faed40aeacb90
1 module tparser;
4 // ////////////////////////////////////////////////////////////////////////// //
5 import std.exception : basicExceptionCtors;
7 class ParseException : Exception {
8 mixin basicExceptionCtors;
9 int line, col;
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) {
33 enum TType {
34 EOF = -1,
35 Str,
36 Num,
37 Delim,
40 IR rng;
41 int line, col; // current
42 int tline, tcol; // token start
43 TType tk; // token type
44 int tnum;
45 bool tquoted; // was current token tquoted?
47 private:
48 char[256] tbuf; // token buffer, to avoid allocations
49 int tbpos;
50 bool eof;
51 bool lastWasEOL = true;
53 public:
54 @disable this (this); // no copies
56 this() (auto ref IR arng) {
57 rng = arng;
58 nextToken();
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);
79 ex.line = tline;
80 ex.col = tcol;
81 throw ex;
84 private:
85 char peekChar () {
86 if (eof || rng.empty) return '\0';
87 return rng.front;
90 // return char or 0
91 char getChar () {
92 if (eof) return '\0';
93 if (rng.empty) { eof = true; return '\0'; }
94 char ch = rng.front;
95 if (ch == '\0') ch = ' ';
96 rng.popFront();
97 if (lastWasEOL) { ++line; col = 0; }
98 ++col;
99 lastWasEOL = (ch == '\n');
100 return ch;
103 // return first non-blank char or 0
104 char skipBlanks () {
105 mainloop: for (;;) {
106 char ch = getChar();
107 if (ch == 0) return ch;
108 if (ch == '/') {
109 char ach = peekChar();
110 if (ach == '/') {
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 == '*') {
115 // multiline comment
116 getChar(); // skip star
117 for (;;) {
118 ch = getChar();
119 if (ch == 0) break;
120 if (ch == '*' && peekChar() == '/') {
121 getChar(); // skip slash
122 continue mainloop;
127 if (ch == 0 || ch > 32) return ch;
129 assert(0);
132 void putChar (char ch) {
133 if (tbpos >= tbuf.length) throw new Exception("token too long");
134 tbuf[tbpos++] = ch;
137 void nextToken () {
138 static bool isIdStart() (char ch) {
139 return
140 (ch >= 'A' && ch <= 'Z') ||
141 (ch >= 'a' && ch <= 'z') ||
142 ch == '_';
145 static bool isIdChar() (char ch) {
146 return
147 (ch >= 'A' && ch <= 'Z') ||
148 (ch >= 'a' && ch <= 'z') ||
149 (ch >= '0' && ch <= '9') ||
150 ch == '_';
153 tbpos = 0;
154 char ch = skipBlanks();
155 tline = line;
156 tcol = col;
157 tquoted = false;
158 tnum = 0;
159 if (ch == 0) { tk = TType.EOF; return; }
160 scope(failure) { tquoted = false; tk = TType.EOF; }
161 // tquoted string
162 if (ch == '"') {
163 tk = TType.Str;
164 tquoted = true;
165 for (;;) {
166 ch = getChar();
167 if (ch == 0) error("unterminated string");
168 if (ch == '"') break;
169 if (ch == '\\') {
170 ch = getChar();
171 switch (ch) {
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;
178 } else {
179 putChar(ch);
182 return;
184 // number
185 if ((ch >= '0' && ch <= '9') || ((ch == '+' || ch == '-') && peekChar() >= '0' && peekChar() <= '9')) {
186 bool neg = false;
187 long n = 0;
188 tk = TType.Num;
189 putChar(ch);
190 if (ch == '+' || ch == '-') {
191 neg = (ch == '-');
192 } else {
193 n = ch-'0';
195 for (;;) {
196 ch = peekChar();
197 if (ch < '0' || ch > '9') break;
198 putChar(getChar());
199 n = n*10+ch-'0';
200 if (n > int.max) error("integer overflow");
202 if (neg) {
203 n = -n;
204 if (n < int.min) error("integer overflow");
206 tnum = cast(int)n;
207 return;
209 // identifier
210 if (isIdStart(ch) || (ch == '@' && isIdStart(peekChar()))) {
211 tk = TType.Str;
212 putChar(ch);
213 if (ch == '@') putChar(getChar());
214 while (isIdChar(peekChar())) putChar(getChar());
215 if (peekChar() == ':') putChar(getChar());
216 return;
218 // delimiter
219 tk = TType.Delim;
220 putChar(ch);