1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.millscript.millscript.syntax;
24
25 import org.millscript.commons.alert.Alert;
26 import org.millscript.commons.util.IMap;
27 import org.millscript.commons.util.MapIterator;
28 import org.millscript.commons.util.map.EHashMap;
29 import org.millscript.millscript.alert.Alerts;
30 import org.millscript.millscript.conf.Configuration;
31 import org.millscript.millscript.datatypes.Atom;
32 import org.millscript.millscript.datatypes.XmlComment;
33 import org.millscript.millscript.expr.ApplyExpr;
34 import org.millscript.millscript.expr.Block;
35 import org.millscript.millscript.expr.CheckNoneExpr;
36 import org.millscript.millscript.expr.CommaExpr;
37 import org.millscript.millscript.expr.ConstantExpr;
38 import org.millscript.millscript.expr.Expr;
39 import org.millscript.millscript.expr.ListExpr;
40 import org.millscript.millscript.expr.NameExpr;
41 import org.millscript.millscript.expr.SkipExpr;
42 import org.millscript.millscript.tools.IntegerTools;
43
44 import java.io.Reader;
45 import java.io.StringReader;
46
47 /**
48 * This class implements the MillScript parser for use with configuration
49 * files. As such it is actually the complete parser implementation but it
50 * excludes nearly all syntax entries. The only syntax this parser understands
51 * is <code>"(", "," and ")"</code>.
52 */
53 public class ConfigurationParserImpl extends TokenizerImpl implements Parser {
54
55 /**
56 * Lookup table mapping a syntax keyword to its syntax object.
57 */
58 static final EHashMap< String, Syntax > TABLE = new EHashMap< String, Syntax >();
59
60
61 static {
62 put( ")", new NonfixSyntax() );
63 put( ",", new CommaSyntax().setPrec( COMMA_PREC ) );
64 put( "(", new ParenSyntax().setPrec( TIGHT_PREC + 400 ) );
65 }
66
67 /**
68 * Constructs a new <code>ParserImpl</code> with the specified origin, source,
69 * interactive prompt and end of line comment status.
70 *
71 * @param origin the origin message for this tokenizer
72 * @param r the character source to tokenize
73 * @param eolc flag indicating if end of line comments are supported
74 * @param c the configuration
75 */
76 public ConfigurationParserImpl( final String origin, final Reader r, final boolean eolc, final Configuration c ) {
77 super( origin, r, eolc, c );
78 }
79
80 /**
81 * Annotates the specified expression with the specified origin and line
82 * number.
83 *
84 * @param e the expression to annotate
85 * @param og the origin message
86 * @param ln the line number
87 * @return the annotated expression
88 */
89 private Expr annotate( final Expr e, final String og, final int ln ) {
90 e.setOrigin( og );
91 e.setLineNumber( ln );
92 return e;
93 }
94
95 /**
96 * @see org.millscript.millscript.syntax.Parser#getEnclosingClass()
97 */
98 public NameExpr getEnclosingClass() {
99
100 throw(
101 Alerts.compile(
102 "No enclosing class",
103 "You are attempting an operation that requires an enclosing class"
104 ).mishap()
105 );
106 }
107
108 /**
109 * Returns a string containing the required format control codes for the
110 * specified interpolation map. Any "%" characters in the input string will
111 * be translated to "%%", while a "%p" will be inserted at each
112 * interpolation point in the string.
113 *
114 * @param w the input string to prepare for interpolation
115 * @param interpolationMapping the map of positions in the input string to
116 * their respective interpolation values
117 * @return a copy of the input string with "%" replaced by "%%" and a "%p"
118 * inserted for each interpolation point
119 */
120 private String insertPercentPee( final String w, final IMap< Integer, CharSequence > interpolationMapping ) {
121 final StringBuffer b = new StringBuffer();
122 for ( int i = 0; i < w.length(); i++ ) {
123 final CharSequence cs = interpolationMapping.get( new Integer( i ) );
124 if ( cs != null ) {
125 b.append( "%p" );
126 }
127 if ( i >= 0 ) {
128 final char ch = w.charAt( i );
129 if ( ch == '%' ) {
130 b.append( "%%" );
131 } else {
132 b.append( ch );
133 }
134 }
135 }
136 if ( interpolationMapping.get( new Integer( w.length() ) ) != null ) {
137 b.append( "%p" );
138 }
139 return b.toString();
140 }
141
142 /**
143 * @see org.millscript.millscript.syntax.Parser#makeInterpolatedExpr()
144 */
145 public Expr makeInterpolatedExpr() {
146 String s = this.getStringNoQuotes();
147
148
149 final String wpp = this.insertPercentPee( s, interpolationMap );
150
151 Expr sofar = new ConstantExpr( wpp );
152
153 for ( MapIterator< Integer, CharSequence > it = interpolationMap.iterator( true ); it.hasNext(); ) {
154 final CharSequence cs = it.nextValue();
155 sofar = CommaExpr.make(
156 sofar,
157 new ListExpr(
158 this.parseCharSequence( cs )
159 )
160 );
161 }
162
163
164
165
166
167
168 return new ApplyExpr(
169 new NameExpr( "format" ),
170 sofar
171 );
172 }
173
174 /**
175 * Parses the specified character sequence, returning the expression for
176 * the parsed sequence.
177 *
178 * @param cs the character sequence to parse
179 * @return an Expr for the parsed expression
180 */
181 private Expr parseCharSequence( final CharSequence cs ) {
182 final String interp = cs.toString();
183
184
185 final Parser p = new ParserImpl( "Interpolated String", new StringReader( interp ), true, this.config );
186 final Expr e = p.readStmnts();
187 if ( p.peekToken() != TokenType.EOF ) {
188 throw(
189 Alerts.parse(
190 "Invalid interpolation expression",
191 null
192 ).culprit( "interpolated", interp ).mishap()
193 );
194 }
195 return e;
196 }
197
198 /**
199 * Convenience method for inserting entries in the symbol table.
200 *
201 * @param s symbol
202 * @param t Syntax for symbol
203 */
204 protected static void put( final String s, final Syntax t ) {
205 TABLE.insert( s.intern(), t );
206 }
207
208 /**
209 * @see org.millscript.millscript.syntax.Parser#readAttributeName()
210 */
211 public String readAttributeName() {
212 if ( nextToken() == TokenType.NAME ) {
213 return getAttributeName();
214 } else {
215 throw(
216 Alerts.parse(
217 "Attribute name expected",
218 null
219 ).culprit( "found", getErrorString() ).mishap()
220 );
221 }
222 }
223
224 /**
225 * @see org.millscript.millscript.syntax.Parser#readBlock()
226 */
227 public Expr readBlock() {
228 return new Block( readStmnts() );
229 }
230
231 /**
232 * @see org.millscript.millscript.syntax.Parser#readBlockTo(java.lang.String)
233 */
234 public Expr readBlockTo( final String sym ) {
235 return new Block( readStmntsTo( sym ) );
236 }
237
238 /**
239 * @see org.millscript.millscript.syntax.Parser#readExpr()
240 */
241 public Expr readExpr() {
242 return readExprPrec( 0 );
243 }
244
245 /**
246 * @see org.millscript.millscript.syntax.Parser#readExprComma()
247 */
248 public Expr readExprComma() {
249 return readExprPrec( COMMA_PREC1 );
250 }
251
252 /**
253 * @see org.millscript.millscript.syntax.Parser#readExprPrec(int)
254 */
255 public Expr readExprPrec( final int prec ) {
256 Expr e = readOptExprPrec( prec );
257 if ( e == null ) {
258 throw(
259 Alerts.parse(
260 "Missing expression",
261 "An expression is required here"
262 ).mishap()
263 );
264 }
265 return e;
266 }
267
268 /**
269 * @see org.millscript.millscript.syntax.Parser#readExprTo(java.lang.String)
270 */
271 public Expr readExprTo( final String sym ) {
272 Expr e = readExpr();
273 mustRead( sym );
274 return e;
275 }
276
277 /**
278 * @see org.millscript.millscript.syntax.Parser#readName()
279 */
280 public NameExpr readName() {
281 String name = readSymbol();
282 Syntax syn = TABLE.get( name );
283 if ( syn != null ) {
284
285 throw(
286 Alerts.parse(
287 "Identifier needed",
288 null
289 ).culprit( "found", name ).mishap()
290 );
291 } else {
292 return new NameExpr( name );
293 }
294 }
295
296 /**
297 * @see org.millscript.millscript.syntax.Parser#readOptExpr()
298 */
299 public Expr readOptExpr() {
300 return readOptExprPrec( 0 );
301 }
302
303 /**
304 * @see org.millscript.millscript.syntax.Parser#readOptExprPrec(int)
305 */
306 public Expr readOptExprPrec( final int prec ) {
307
308 String og = getOrigin();
309 int ln = getLineNumber();
310
311 try {
312
313 Expr e = readOptPrimary();
314
315
316 if ( e == null ) {
317 return null;
318 }
319
320
321 for (;;) {
322 TokenType tt = peekToken();
323 if ( tt == TokenType.NAME ) {
324 String sym = getName();
325 Syntax s = TABLE.get( sym );
326
327 if ( s == null || !( s instanceof PostfixSyntaxInterface ) ) {
328 return annotate( e, og, ln );
329 }
330 int q = ((PostfixSyntaxInterface)s).getPrecedence();
331
332
333 if ( q < prec ) {
334 return annotate( e, og, ln );
335 }
336
337 dropToken();
338
339 e = ((PostfixSyntaxInterface)s).postfix( sym, q, e, this );
340 } else if ( tt == TokenType.INTEGER && getInt() < 0 ) {
341
342
343 int num = getInt();
344 dropToken();
345 e =
346 new ApplyExpr(
347 new NameExpr( "-".intern() ),
348 CommaExpr.make( e, new ConstantExpr( IntegerTools.make( -num ) ) )
349 );
350 } else {
351 return annotate( e, og, ln );
352 }
353 }
354 } catch ( Alert ex ) {
355 throw ex.origin( this ).remishap();
356 }
357 }
358
359 /**
360 * @see org.millscript.millscript.syntax.Parser#readOptPrimary()
361 */
362 public Expr readOptPrimary() {
363 TokenType tt = peekToken();
364 if ( tt == TokenType.EOF ) {
365 return null;
366 } else if ( tt == TokenType.STRING ) {
367 dropToken();
368 char ch = getQuoteChar();
369
370
371 if ( this.interpolationMap.isEmtpy() ) {
372
373
374 if ( checkWhere( 'c' ) ) {
375
376
377 return new ConstantExpr( new XmlComment( getStringNoQuotes( 0, 3 ) ) );
378 } else if ( ch == '"' || ch == '#' ) {
379 return new ConstantExpr( getStringNoQuotes().intern() );
380 } else if ( ch == '`' ) {
381 return new ConstantExpr( Atom.make( getStringNoQuotes() ) );
382 } else if ( ch == '\'' ) {
383 String s = getStringNoQuotes();
384 Expr sofar = new SkipExpr();
385 for ( int i = 0; i < s.length(); i++ ) {
386 char c = s.charAt( i );
387 sofar = (
388 CommaExpr.make(
389 sofar,
390 new ConstantExpr( new Character( c ) )
391 )
392 );
393 }
394 return sofar;
395 } else {
396 throw(
397 Alerts.fault( "Impossible character quote" ).mishap()
398 );
399 }
400 } else {
401
402
403 if ( checkWhere( 'c' ) ) {
404
405
406 String s = this.getStringNoQuotes( 0, 3 );
407
408
409 final String wpp = this.insertPercentPee( s, interpolationMap );
410
411 Expr sofar = new ConstantExpr( new XmlComment( wpp ) );
412
413 for ( MapIterator< Integer, CharSequence > it = interpolationMap.iterator( true ); it.hasNext(); ) {
414 final CharSequence cs = it.nextValue();
415 sofar = CommaExpr.make( sofar, new ListExpr( this.parseCharSequence( cs ) ) );
416 }
417
418
419
420 return new ApplyExpr(
421 new NameExpr( "format" ),
422 sofar
423 );
424 } else if ( ch == '"' || ch == '#' ) {
425 return this.makeInterpolatedExpr();
426 } else if ( ch == '`' ) {
427 return new ApplyExpr(
428 new NameExpr( "newSymbol" ),
429 this.makeInterpolatedExpr()
430 );
431 } else if ( ch == '\'' ) {
432 return new ApplyExpr(
433 new NameExpr( "explode" ),
434 this.makeInterpolatedExpr()
435 );
436 } else {
437 throw(
438 Alerts.fault( "Impossible character quote" ).mishap()
439 );
440 }
441 }
442 } else if ( tt == TokenType.TRADITIONAL_REGEX ) {
443
444
445 dropToken();
446 return new ConstantExpr( this.makePattern() );
447 } else if ( tt == TokenType.INTEGER ) {
448 dropToken();
449 return new ConstantExpr( IntegerTools.make( getInt() ) );
450 } else if ( tt == TokenType.NAME ) {
451 String sym = getName();
452 Syntax syntax = TABLE.get( sym );
453 if ( syntax == null ) {
454 dropToken();
455 NameExpr name = new NameExpr( sym );
456 if ( tryRead( "::" ) ) {
457 NameExpr newname = readName();
458 newname.setNickname( name.getName() );
459 return newname;
460 }
461 return name;
462 } else if ( syntax instanceof PrefixSyntaxInterface ) {
463 dropToken();
464 return ((PrefixSyntaxInterface)syntax).prefix( sym, this );
465 } else {
466 return null;
467 }
468 } else {
469 throw(
470 Alerts.fault( "Invalid token type " + tt ).mishap()
471 );
472 }
473 }
474
475 /**
476 * @see org.millscript.millscript.syntax.Parser#readPrimary()
477 */
478 public Expr readPrimary() {
479 Expr e = readOptPrimary();
480 if ( e == null ) {
481 if ( peekToken() == TokenType.EOF ) {
482 throw(
483 Alerts.parse(
484 "Unexpected end of input while reading Primary",
485 null
486 ).mishap()
487 );
488 } else {
489 throw(
490 Alerts.parse(
491 "Unexpected token while reading Simple expression",
492 null
493 ).culprit( "token", getErrorString() ).mishap()
494 );
495 }
496 }
497 return e;
498 }
499
500 /**
501 * @see org.millscript.millscript.syntax.Parser#readStmnts()
502 */
503 public Expr readStmnts() {
504 Expr x = new SkipExpr();
505 for (;;) {
506 Expr y = readOptExprPrec( 0 );
507 if ( y == null ) {
508 return x;
509 } else {
510 x = CommaExpr.make( CheckNoneExpr.make( x ), y );
511 }
512 if ( !tryRead( ";" ) ) {
513 return x;
514 }
515 }
516 }
517
518 /**
519 * @see org.millscript.millscript.syntax.Parser#readStmntsTo(java.lang.String)
520 */
521 public Expr readStmntsTo( final String sym ) {
522 Expr e = readStmnts();
523 mustRead( sym );
524 return e;
525 }
526
527 /**
528 * @see org.millscript.millscript.syntax.Parser#readSymbol()
529 */
530 public String readSymbol() {
531 TokenType tt = nextToken();
532 if ( tt == TokenType.NAME ) {
533 return getName();
534 } else if ( tt == TokenType.EOF ) {
535
536 throw(
537 Alerts.parse(
538 "Unexpected end of file",
539 null
540 ).mishap()
541 );
542 } else {
543
544 throw(
545 Alerts.parse(
546 "Identifier needed",
547 null
548 ).culprit( "found", getErrorString() ).mishap()
549 );
550 }
551 }
552
553 /**
554 * @see org.millscript.millscript.syntax.Parser#readTagName()
555 */
556 public String readTagName() {
557 if ( nextToken() == TokenType.NAME ) {
558 return getTagName();
559 } else {
560 throw(
561 Alerts.parse(
562 "Tag name expected",
563 null
564 ).culprit( "found", getErrorString() ).mishap()
565 );
566 }
567 }
568
569 }