View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2001-2004 Open World Ltd
4   // Copyright (C) 2004 Stephen F. K. Leach
5   // Copyright (C) 2004-2005 Kevin Rogers
6   //
7   // This file is part of MillScript.
8   //
9   // MillScript is free software; you can redistribute it and/or modify it under
10  // the terms of the GNU General Public License as published by the Free
11  // Software Foundation; either version 2 of the License, or (at your option)
12  // any later version.
13  //
14  // MillScript is distributed in the hope that it will be useful, but WITHOUT
15  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
17  // more details.
18  //
19  // You should have received a copy of the GNU General Public License along with
20  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
21  // Place, Suite 330, Boston, MA  02111-1307  USA
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      //    Initializer
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          // For the default parser, there is NO enclosing class
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         // Insert "%p" in all the relevant places, including
148         // changing existing "%" to "%%".
149         final String wpp = this.insertPercentPee( s, interpolationMap );
150         // Start with the control string as the first argument
151         Expr sofar = new ConstantExpr( wpp );
152         // Now add a list for each interpoalated section
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         // Return an application of the format function with the
163         // comment as it's first argument and parsed interpolated
164         // section. e.g.
165         // format( wpp, { e }, { e } )
166         // so "hello \( "there" ) \( 1,2,3 )" would become
167         // format( "hello %p %p", { "there" }, { 1, 2, 3 } );
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         // TODO - It would be great to be able to pass in the current line
184         // number, and a better origin message.
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             //    Ooops - we wanted a plain word.
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         //    Record position in file.
308         String og = getOrigin();
309         int ln = getLineNumber();
310 
311         try {
312             //    Phase 1: read primary.
313             Expr e = readOptPrimary();
314 
315             //    EOF allowed here.
316             if ( e == null ) {
317                 return null;
318             }
319 
320             //    Phase 2: read postfix/infix expressions.
321             for (;;) {
322                 TokenType tt = peekToken();
323                 if ( tt == TokenType.NAME ) {
324                     String sym = getName();
325                     Syntax s = TABLE.get( sym );
326                     //    First check whether the next symbol is marked as postfixable.
327                     if ( s == null || !( s instanceof PostfixSyntaxInterface ) ) {
328                         return annotate( e, og, ln );
329                     }
330                     int q = ((PostfixSyntaxInterface)s).getPrecedence();
331                     //    Now check whether it binder as tightly (or more tightly) than the
332                     //    current precedence level.
333                     if ( q < prec ) {
334                         return annotate( e, og, ln );
335                     }
336                     //    OK, we accept it as a legitimate continuation.
337                     dropToken();
338                     //    Run the postfix factory interface.
339                     e = ((PostfixSyntaxInterface)s).postfix( sym, q, e, this );
340                 } else if ( tt == TokenType.INTEGER && getInt() < 0 ) {
341                     //    We have the yukky case of E-7 i.e. a general
342                     //    expression followed by a small negative number.
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             // We have a branch here to support constant and interpolated
370             // strings
371             if ( this.interpolationMap.isEmtpy() ) {
372                 // This is NOT an interpolated string, so we must construct a
373                 // constant value
374                 if ( checkWhere( 'c' ) ) {
375                     // We're inside an XML comment, so we must get the parsed string
376                     // which contains the comment and the trailing "-->".
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                 // This IS an interpolated string, so we have to construct an
402                 // apply expression. i.e. not a constant
403                 if ( checkWhere( 'c' ) ) {
404                     // We're inside an XML comment, so we must get the parsed string
405                     // which contains the comment and the trailing "-->".
406                     String s = this.getStringNoQuotes( 0, 3 );
407                     // Insert "%p" in all the relevant places, including
408                     // changing existing "%" to "%%".
409                     final String wpp = this.insertPercentPee( s, interpolationMap );
410                     // We start with an XMLComment as the first argument
411                     Expr sofar = new ConstantExpr( new XmlComment( wpp ) );
412                     // Now add a list for each interpoalated section
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                     // Return an application of the format function with the
418                     // comment as it's first argument and parsed interpolated
419                     // section
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             // A traditional regular expression. We just need to make a
444             // constant expression containing the compiled pattern.
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             //    Ooops - we did not want the end of file.
536             throw(
537                 Alerts.parse(
538                     "Unexpected end of file",
539                     null
540                 ).mishap()
541             );
542         } else {
543             //    Ooops - we wanted an identifier.
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 }