Source code: com/arranger/jarl/script/gsp/GSP.java
1 package com.arranger.jarl.script.gsp;
2
3 import java.io.Reader;
4 import java.util.HashMap;
5 import java.util.Map;
6
7 /**
8 * GSP General Script Parser.
9 */
10 public class GSP {
11
12 protected static final int LL = 15;
13 protected static final int BUF_SIZE = 0x2000;
14 protected static final int BUF_MAX = BUF_SIZE - LL - 1;
15
16 protected static final int STATE_BODY = 0;
17 protected static final int STATE_CODE = 1;
18 protected static final int STATE_EXPR = 2;
19 protected static final int STATE_DECL = 3;
20 protected static final int STATE_DIRV = 4;
21 protected static final int STATE_COMM = 5;
22 protected static final int STATE_TAG_START = 6;
23 protected static final int STATE_TAG_END = 7;
24 protected static final int STATE_TAG_ATTR = 8;
25
26 /**
27 * Processes the specified the input stream with the specified language and compiler.
28 *
29 * @param in Input stream to parse.
30 * @param lang Language with which to parse.
31 * @param compiler Compiler directing the parse.
32 */
33 public void parse(Reader in, IGSPLanguage lang, IGSPCompiler compiler) throws GSPException {
34 int state = STATE_BODY;
35 String prefix = null;
36 char attr = 0;
37
38 // first char is always a space
39 char[] buf = new char[BUF_SIZE];
40 int cur = 0;
41 buf[cur] = ' ';
42
43 int ch = 1;
44 int line = 1;
45 int column = 1;
46
47 try {
48 ch = in.read();
49
50 while (ch != -1) {
51 switch (ch) {
52
53 case '%':
54 // start token
55 if (state == STATE_BODY && buf[cur] == '<') {
56 state = STATE_CODE;
57 lang.print(buf, 1, cur - 1);
58 cur = 0;
59 buf[cur] = ' ';
60
61 ch = in.read();
62 column++;
63
64 switch (ch) {
65 case '=':
66 state = STATE_EXPR;
67 break;
68 case '!':
69 state = STATE_DECL;
70 break;
71 case '@':
72 state = STATE_DIRV;
73 break;
74 case '-':
75 state = STATE_COMM;
76 break;
77 case '\r':
78 column = 0;
79 line++;
80 cur = append(buf, cur, lang, state, (char) ch);
81 break;
82 case '\n':
83 if (buf[cur] == '\r') {
84 column = 0;
85 line++;
86 }
87 cur = append(buf, cur, lang, state, (char) ch);
88 break;
89 default:
90 cur = append(buf, cur, lang, state, (char) ch);
91 }
92
93 // escape sequence
94 } else if (buf[cur] == '\\') {
95 cur--;
96 cur = append(buf, cur, lang, state, (char) ch);
97
98 // literal
99 } else {
100 cur = append(buf, cur, lang, state, (char) ch);
101 }
102 break;
103
104
105 case ':':
106 // tag start/end
107 if (state == STATE_BODY) {
108 boolean isPrefix = false;
109 int cutoff = cur - LL;
110 if (cutoff <= 0) {
111 cutoff = 1;
112 }
113
114 // look for tag prefix start
115 for (int i = cur; i >= cutoff; i--) {
116 int ch1 = buf[i];
117
118 // colon not in a tag
119 if (ch1 == ' ' || ch1 == '>') {
120 break;
121
122 } else if (ch1 == '<') {
123 // end tag
124 if (buf[i + 1] == '/') {
125 state = STATE_TAG_END;
126 prefix = new String(buf, i + 2, cur - i - 1);
127 // start tag
128 } else {
129 state = STATE_TAG_START;
130 prefix = new String(buf, i + 1, cur - i);
131 }
132
133 lang.print(buf, 1, i - 1);
134 cur = 0;
135 buf[cur] = ' ';
136
137 isPrefix = true;
138 break;
139 }
140 }
141
142 // sometimes a colon is just a colon (iykwim)
143 if (!isPrefix) {
144 cur = append(buf, cur, lang, state, (char) ch);
145 }
146
147 // literal
148 } else {
149 cur = append(buf, cur, lang, state, (char) ch);
150 }
151 break;
152
153
154 case '>':
155
156 // escape sequence
157 if (buf[cur] == '\\') {
158 cur--;
159 cur = append(buf, cur, lang, state, (char) ch);
160
161 // literal
162 } else if (state == STATE_BODY || state == STATE_TAG_ATTR) {
163 cur = append(buf, cur, lang, state, (char) ch);
164
165 // end start tag
166 } else if (state == STATE_TAG_START) {
167 parseStartTag(buf, 1, cur + 1, lang, prefix);
168 prefix = null;
169
170 state = STATE_BODY;
171 cur = 0;
172 buf[cur] = ' ';
173
174 // end end tag
175 } else if (state == STATE_TAG_END) {
176 lang.tagEnd(prefix, new String(buf, 1, cur));
177 prefix = null;
178
179 state = STATE_BODY;
180 cur = 0;
181 buf[cur] = ' ';
182
183 // end token
184 } else if (buf[cur] == '%') {
185 // literal
186 if (state == STATE_COMM && !(buf[cur - 1] == '-' && buf[cur - 2] == '-')) {
187 cur = append(buf, cur, lang, state, (char) ch);
188
189 } else {
190 switch (state) {
191 case STATE_CODE:
192 lang.code(buf, 1, cur - 1);
193 break;
194 case STATE_EXPR:
195 lang.eval(buf, 1, cur - 1);
196 break;
197 case STATE_DECL:
198 lang.decl(buf, 1, cur - 1);
199 break;
200 case STATE_DIRV:
201 parseDirective(buf, 1, cur, lang, compiler);
202 break;
203 case STATE_COMM:
204 // skip
205 break;
206 }
207 state = STATE_BODY;
208 cur = 0;
209 buf[cur] = ' ';
210 }
211
212 // literal
213 } else {
214 cur = append(buf, cur, lang, state, (char) ch);
215 }
216 break;
217
218
219 case '\r':
220 column = 0;
221 line++;
222 cur = append(buf, cur, lang, state, (char) ch);
223 break;
224
225
226 case '\n':
227 if (buf[cur] != '\r') {
228 column = 0;
229 line++;
230 }
231 cur = append(buf, cur, lang, state, (char) ch);
232 break;
233
234
235
236 case '\"':
237 case '\'':
238 switch (state) {
239 // switch to protected attr mode
240 case STATE_TAG_START:
241 state = STATE_TAG_ATTR;
242 attr = (char) ch;
243 cur = append(buf, cur, lang, state, (char) ch);
244 break;
245
246 // switch out of protected attr mode
247 case STATE_TAG_ATTR:
248 // only bust out if the quote matches & is not being escaped
249 if (attr == ch && buf[cur] != '\\') {
250 state = STATE_TAG_START;
251 attr = 0;
252 }
253 cur = append(buf, cur, lang, state, (char) ch);
254 break;
255
256 default:
257 cur = append(buf, cur, lang, state, (char) ch);
258 }
259 break;
260
261
262 default:
263 cur = append(buf, cur, lang, state, (char) ch);
264 }
265
266 ch = in.read();
267 column++;
268 }
269
270 // flush remaining
271 lang.print(buf, 1, cur);
272
273 } catch (GSPException gspe) {
274 if (gspe.getLine() < 1) {
275 gspe.setLine(line);
276 }
277 if (gspe.getColumn() < 1) {
278 gspe.setColumn(column);
279 }
280 throw gspe;
281
282 } catch (Exception e) {
283 throw new GSPException(e, line, column);
284 }
285 }
286
287 /**
288 * Appends the specified character to the buffer.
289 * Flushes the buffer if it's almost full.
290 *
291 * @param buf Buffer.
292 * @param cur Buffer cursor (current position).
293 * @param ch Character to append.
294 * @param lang GSP language.
295 * @param state Parser state.
296 *
297 * @return New buffer cursor.
298 */
299 protected int append(char[] buf, int cur, IGSPLanguage lang, int state, char ch) throws GSPException {
300 // flush buf
301 if (cur > BUF_MAX) {
302 switch (state) {
303 case STATE_BODY:
304 lang.print(buf, 1, cur - LL);
305 break;
306 case STATE_CODE:
307 lang.code(buf, 1, cur - LL);
308 break;
309 case STATE_DECL:
310 lang.decl(buf, 1, cur - LL);
311 break;
312 case STATE_COMM:
313 break;
314 default:
315 throw new GSPException("expr too long", 0, 0);
316 }
317
318 // keep lookahead chars
319 System.arraycopy(buf, cur - LL + 1, buf, 1, LL);
320 cur = LL;
321 }
322
323 // append char
324 buf[++cur] = ch;
325
326 // new cursor
327 return cur;
328 }
329
330 /**
331 * Passes the specified directive to the specified language.
332 *
333 * @param buf Buffer containing directive.
334 * @param start Starting index of directive.
335 * @param length Length of directive.
336 * @param lang GSP language.
337 * @param compiler GSP compiler.
338 */
339 protected void parseDirective(char[] buf, int start, int length, IGSPLanguage lang, IGSPCompiler compiler) throws GSPException {
340 String directive = null;
341 Map params = new HashMap(0x0f);
342
343 // skip leading whitespace
344 while (start < length &&
345 (buf[start] == ' ' || buf[start] == '\t' || buf[start] == '\r' || buf[start] == '\n')) {
346 start++;
347 }
348
349 int end = start;
350
351 // find directive name end
352 while (end < length &&
353 (buf[end] != ' ' && buf[end] != '\t' && buf[end] != '\r' && buf[end] != '\n')) {
354 end++;
355 }
356
357 directive = new String(buf, start, end - start);
358 params = parseParameters(buf, end, length, params);
359
360 if (!handleDirective(directive, params, lang, compiler)) {
361 lang.directive(directive, params);
362 }
363 }
364
365 /**
366 * Handles the specified directive.
367 *
368 * @param d Directive to handle.
369 * @param p Directive params.
370 * @param l Language.
371 * @param c Compiler.
372 *
373 * @return true if directive was handled.
374 */
375 protected boolean handleDirective(String d, Map p, IGSPLanguage l, IGSPCompiler c) throws GSPException {
376 if ("include".equals(d)) {
377 String file = (String) p.get("file");
378
379 try {
380 Reader in = c.open(file);
381 parse(in, l, c);
382
383 } catch (GSPException gspe) {
384 if (gspe.getFile() == null) {
385 gspe.setFile(file);
386 }
387 throw gspe;
388 }
389 return true;
390 }
391
392 return false;
393 }
394
395 /**
396 * Passes the specified start tag to the specified language.
397 *
398 * @param buf Buffer containing directive.
399 * @param start Starting index of directive.
400 * @param length Length of directive.
401 * @param lang GSP language.
402 * @param prefix Tag prefix.
403 */
404 protected void parseStartTag(char[] buf, int start, int length, IGSPLanguage lang, String prefix) throws GSPException {
405 String name = null;
406 Map params = new HashMap(0x1f);
407 boolean empty = false;
408
409 // test for empty tag (<p:n n='v' />)
410 if (buf[length - 1] == '/') {
411 empty = true;
412 length--;
413 }
414
415 int end = start;
416
417 // find tag name end
418 while (end < length &&
419 (buf[end] != ' ' && buf[end] != '\t' && buf[end] != '\r' && buf[end] != '\n')) {
420 end++;
421 }
422
423 name = new String(buf, start, end - start);
424 params = parseParameters(buf, end, length, params);
425
426 lang.tagStart(prefix, name, params);
427
428 if (empty) {
429 lang.tagEnd(prefix, name);
430 }
431 }
432
433 /**
434 * Parses attribute name="value" pairs out of the specified buffer.
435 *
436 * @param buf Buffer containing directive.
437 * @param start Starting index of directive.
438 * @param length Length of directive.
439 * @param params (optional) Map in which to store params.
440 *
441 * @return Map of parsed params.
442 */
443 protected Map parseParameters(char[] buf, int start, int length, Map params) {
444 if (params == null) {
445 params = new HashMap(0x20);
446 }
447
448 String name = null;
449 String value = null;
450 int end = start;
451
452 // find name start
453 while (end < length &&
454 (buf[end] == ' ' || buf[end] == '\t' || buf[end] == '\r' || buf[end] == '\n')) {
455 end++;
456 }
457
458 while (end < length) {
459 start = end;
460
461 // find name end
462 while (end < length &&
463 (buf[end] != '=' && buf[end] != ' ' && buf[end] != '\t' && buf[end] != '\r' && buf[end] != '\n')) {
464 end++;
465 }
466
467 name = new String(buf, start, end - start);
468
469 start = end;
470
471 // find value start
472 while (start < length &&
473 (buf[start] == '=' || buf[start] == ' ' || buf[start] == '\t' || buf[start] == '\r' || buf[start] == '\n')) {
474 start++;
475 }
476
477 boolean foundEnd = false;
478 end = start + 1;
479
480 // find value end
481 while (end < length && !foundEnd) {
482 switch (buf[end]) {
483 case '\\':
484 end++;
485 break;
486 case '\'':
487 case '\"':
488 if (buf[end] == buf[start]) {
489 foundEnd = true;
490 }
491 break;
492 }
493 end++;
494 }
495
496 value = new String(buf, start + 1, end - start - 2);
497
498 // add to map
499 params.put(name, value);
500
501 // find name start
502 while (end < length &&
503 (buf[end] == ' ' || buf[end] == '\t' || buf[end] == '\r' || buf[end] == '\n')) {
504 end++;
505 }
506 }
507
508 return params;
509 }
510 }