| /* |
| $Id$ |
| |
| Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. |
| |
| Redistribution and use of this software and associated documentation |
| ("Software"), with or without modification, are permitted provided |
| that the following conditions are met: |
| |
| 1. Redistributions of source code must retain copyright |
| statements and notices. Redistributions must also contain a |
| copy of this document. |
| |
| 2. Redistributions in binary form must reproduce the |
| above copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| 3. The name "groovy" must not be used to endorse or promote |
| products derived from this Software without prior written |
| permission of The Codehaus. For written permission, |
| please contact info@codehaus.org. |
| |
| 4. Products derived from this Software may not be called "groovy" |
| nor may "groovy" appear in their names without prior written |
| permission of The Codehaus. "groovy" is a registered |
| trademark of The Codehaus. |
| |
| 5. Due credit should be given to The Codehaus - |
| http://groovy.codehaus.org/ |
| |
| THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS |
| ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT |
| NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
| THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
| INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
| OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| */ |
| package org.codehaus.groovy.syntax.parser; |
| |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.control.CompilationFailedException; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.control.messages.SimpleMessage; |
| import org.codehaus.groovy.control.messages.SyntaxErrorMessage; |
| import org.codehaus.groovy.syntax.ReadException; |
| import org.codehaus.groovy.syntax.Reduction; |
| import org.codehaus.groovy.syntax.SyntaxException; |
| import org.codehaus.groovy.syntax.Token; |
| import org.codehaus.groovy.syntax.Types; |
| import org.codehaus.groovy.syntax.UnexpectedTokenException; |
| import org.codehaus.groovy.syntax.CSTNode; |
| |
| /** |
| * Reads the source text and produces a Concrete Syntax Tree. Exceptions |
| * are collected during processing, and parsing will continue for while |
| * possible, in order to report as many problems as possible. |
| * <code>module()</code> is the primary entry point. |
| * |
| * @author Bob McWhirter |
| * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> |
| * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a> |
| */ |
| |
| public class Parser |
| { |
| private SourceUnit controller = null; // The controller to which we report errors |
| private TokenStream tokenStream = null; // Our token source |
| private int nestCount = 1; // Simplifies tracing of nested calls |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // CONSTRUCTION AND DATA ACCESS |
| |
| /** |
| * Sets the <code>Parser</code> to process a <code>TokenStream</code>, |
| * under control of the specified <code>SourceUnit</code>. |
| */ |
| |
| public Parser( SourceUnit controller, TokenStream tokenStream ) |
| { |
| this.controller = controller; |
| this.tokenStream = tokenStream; |
| } |
| |
| |
| /** |
| * Synonym for module(), the primary entry point. |
| */ |
| |
| public Reduction parse() throws CompilationFailedException |
| { |
| try |
| { |
| return module(); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addFatalError( new SyntaxErrorMessage(e) ); |
| } |
| |
| throw new GroovyBugError( "this will never happen" ); |
| } |
| |
| |
| |
| /** |
| * Returns the <code>TokenStream</code> being parsed. |
| */ |
| |
| public TokenStream getTokenStream() |
| { |
| return this.tokenStream; |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // PRODUCTION SUPPORT |
| |
| |
| /** |
| * Eats any optional newlines. |
| */ |
| |
| public void optionalNewlines() throws SyntaxException, CompilationFailedException |
| { |
| while( lt(false) == Types.NEWLINE) |
| { |
| consume( Types.NEWLINE ); |
| } |
| } |
| |
| |
| |
| /** |
| * Eats a required end-of-statement (semicolon or newline) from the stream. |
| * Throws an <code>UnexpectedTokenException</code> if anything else is found. |
| */ |
| |
| public void endOfStatement( boolean allowRightCurlyBrace ) throws SyntaxException, CompilationFailedException |
| { |
| Token next = la( true ); |
| |
| if( next.isA(Types.GENERAL_END_OF_STATEMENT) ) |
| { |
| consume( true ); |
| } |
| else |
| { |
| if( allowRightCurlyBrace ) |
| { |
| if( !next.isA(Types.RIGHT_CURLY_BRACE) ) |
| { |
| error( new int[] { Types.SEMICOLON, Types.NEWLINE, Types.RIGHT_CURLY_BRACE } ); |
| } |
| } |
| else |
| { |
| error( new int[] { Types.SEMICOLON, Types.NEWLINE } ); |
| } |
| } |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>endOfStatement( true )</code>. |
| */ |
| |
| public void endOfStatement() throws SyntaxException, CompilationFailedException |
| { |
| endOfStatement( true ); |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // PRODUCTIONS: PRIMARY STRUCTURES |
| |
| |
| /** |
| * Processes a dotted identifer. Used all over the place. |
| * <p> |
| * Grammar: <pre> |
| * dottedIdentifier = <identifier> ("." <identifier>)* |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * dotted = { "." dotted <identifier> } | <identifier> |
| * </pre> |
| */ |
| |
| public CSTNode dottedIdentifier() throws SyntaxException, CompilationFailedException |
| { |
| CSTNode identifier = consume(Types.IDENTIFIER); |
| |
| while (lt() == Types.DOT) |
| { |
| identifier = consume(Types.DOT).asReduction( identifier, consume(Types.IDENTIFIER) ); |
| } |
| |
| return identifier; |
| } |
| |
| |
| |
| /** |
| * The primary file-level parsing entry point. The returned CST |
| * represents the content in a single class file. Collects most |
| * exceptions and attempts to continue. |
| * <p> |
| * Grammar: <pre> |
| * module = [packageStatement] |
| * (usingStatement)* |
| * (topLevelStatement)* |
| * <eof> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * module = { <null> package imports (topLevelStatement)* } |
| * |
| * package see packageDeclaration() |
| * imports see importStatement() |
| * topLevelStatement see topLevelStatement() |
| * </pre> |
| * |
| */ |
| |
| public Reduction module() throws SyntaxException, CompilationFailedException |
| { |
| Reduction module = Reduction.newContainer(); |
| |
| // |
| // First up, the package declaration |
| |
| // XXX br: this is where i can do macro processing |
| |
| Reduction packageDeclaration = null; |
| |
| if( lt() == Types.KEYWORD_PACKAGE ) |
| { |
| try |
| { |
| packageDeclaration = packageDeclaration(); |
| } |
| |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| recover(); |
| } |
| } |
| |
| if( packageDeclaration == null ) |
| { |
| packageDeclaration = Reduction.EMPTY; |
| } |
| |
| module.add( packageDeclaration ); |
| |
| |
| // |
| // Next, handle import statements |
| |
| Reduction imports = (Reduction)module.add( Reduction.newContainer() ); |
| Object collector; |
| |
| while( lt() == Types.KEYWORD_IMPORT ) |
| { |
| try |
| { |
| imports.add( importStatement() ); |
| } |
| |
| catch( SyntaxException e ) |
| { |
| controller.addError(e); |
| recover(); |
| } |
| } |
| |
| |
| // |
| // With that taken care of, process everything else. |
| |
| while( lt() != Types.EOF ) |
| { |
| try |
| { |
| module.add( topLevelStatement() ); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| recover(); |
| } |
| } |
| |
| return module; |
| } |
| |
| |
| |
| /** |
| * Processes a package declaration. Called by <code>module()</code>. |
| * <p> |
| * Grammar: <pre> |
| * packageDeclaration = "package" dottedIdentifier <eos> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * package = { "package" dottedIdentifier } |
| * |
| * see dottedIdentifier() |
| * </pre> |
| */ |
| |
| public Reduction packageDeclaration() throws SyntaxException, CompilationFailedException |
| { |
| Reduction packageDeclaration = consume(Types.KEYWORD_PACKAGE).asReduction( dottedIdentifier() ); |
| endOfStatement( false ); |
| |
| return packageDeclaration; |
| } |
| |
| |
| |
| /** |
| * Processes an import statement. Called by <code>module()</code>. |
| * <p> |
| * Grammar: <pre> |
| * importStatement = "import" (all|specific) <eos> |
| * |
| * all = package "." (package ".")* "*" |
| * |
| * specific = (package "." (package ".")*)? classes |
| * classes = class ["as" alias] ("," class ["as" alias])* |
| * |
| * package = <identifier> |
| * class = <identifier> |
| * alias = <identifier> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * import = { "import" (package|{}) ({"*"} | clause+) } |
| * |
| * package = { "." package <identifier> } | <identifier> |
| * clause = { <identifier> <alias>? } |
| * </pre> |
| */ |
| |
| public Reduction importStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction importStatement = consume(Types.KEYWORD_IMPORT).asReduction(); |
| |
| // |
| // First, process any package name. |
| |
| CSTNode packageNode = null; |
| if( lt(2) == Types.DOT ) |
| { |
| packageNode = consume(Types.IDENTIFIER).asReduction(); |
| |
| while( lt(3) == Types.DOT ) |
| { |
| packageNode = consume(Types.DOT).asReduction( packageNode ); |
| packageNode.add( consume(Types.IDENTIFIER) ); |
| } |
| |
| consume( Types.DOT ); |
| } |
| |
| if( packageNode == null ) |
| { |
| packageNode = Reduction.EMPTY; |
| } |
| |
| importStatement.add( packageNode ); |
| |
| |
| // |
| // Then process the class list. |
| |
| if( !packageNode.isEmpty() && lt() == Types.STAR ) |
| { |
| importStatement.add( consume(Types.STAR) ); |
| } |
| else |
| { |
| boolean done = false; |
| while( !done ) |
| { |
| Reduction clause = consume(Types.IDENTIFIER).asReduction(); |
| if( lt() == Types.KEYWORD_AS ) |
| { |
| consume( Types.KEYWORD_AS ); |
| clause.add( consume(Types.IDENTIFIER) ); |
| } |
| |
| importStatement.add( clause ); |
| |
| if( lt() == Types.COMMA ) |
| { |
| consume( Types.COMMA ); |
| } |
| else |
| { |
| done = true; |
| } |
| } |
| |
| } |
| |
| // |
| // End the statement and return. |
| |
| endOfStatement( false ); |
| return importStatement; |
| } |
| |
| |
| |
| /** |
| * Processes a top level statement (classes, interfaces, unattached methods, and |
| * unattached code). Called by <code>module()</code>. |
| * <p> |
| * Grammar: <pre> |
| * topLevelStatement |
| * = methodDeclaration |
| * | typeDeclaration |
| * | statement |
| * |
| * typeDeclaration = classDeclaration | interfaceDeclaration |
| * </pre> |
| * <p> |
| * Recognition: <pre> |
| * "def" => methodDeclaration |
| * "synchronized" "(" => synchronizedStatement |
| * modifierList "class" => classDeclaration |
| * modifierList "interface" => interfaceDeclaration |
| * modifierList => <error> |
| * * => statement |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * see methodDeclaration() |
| * see classDeclaration() |
| * see interfaceDeclaration() |
| * see statement() |
| * see synchronizedStatement() |
| * </pre> |
| */ |
| |
| public CSTNode topLevelStatement() throws SyntaxException, CompilationFailedException |
| { |
| CSTNode result = null; |
| |
| // |
| // If it starts "def", it's a method declaration. Methods |
| // declared this way cannot be abstract. Note that "def" |
| // is required because the return type is not, and it would |
| // be very hard to tell the difference between a function |
| // def and a function invokation with closure... |
| |
| if (lt() == Types.KEYWORD_DEF) |
| { |
| consume(); |
| |
| Reduction modifiers = modifierList( false, false ); |
| CSTNode type = optionalDatatype( false, true ); |
| Token identifier = nameDeclaration( false ); |
| |
| result = methodDeclaration(modifiers, type, identifier, false); |
| } |
| |
| else if (lt() == Types.KEYWORD_DEFMACRO) |
| { |
| // XXX add my logic here |
| } |
| |
| // |
| // If it starts "synchronized(", it's a statement. This check |
| // is necessary because "synchronized" is also a class modifier. |
| |
| else if( lt() == Types.KEYWORD_SYNCHRONIZED && lt(2) == Types.LEFT_PARENTHESIS ) |
| { |
| result = synchronizedStatement(); |
| } |
| |
| // |
| // If it starts with a modifier, "class", or "interface", |
| // it's a type declaration. |
| |
| else if( la().isA(Types.DECLARATION_MODIFIER) || la().isA(Types.TYPE_DECLARATION) ) |
| { |
| Reduction modifiers = modifierList( true, true ); |
| |
| switch( lt() ) |
| { |
| case Types.KEYWORD_CLASS: |
| { |
| result = classDeclaration( modifiers ); |
| break; |
| } |
| |
| case Types.KEYWORD_INTERFACE: |
| { |
| result = interfaceDeclaration( modifiers ); |
| break; |
| } |
| |
| default: |
| { |
| error( new int[] { Types.KEYWORD_CLASS, Types.KEYWORD_INTERFACE } ); |
| break; |
| } |
| } |
| } |
| |
| // |
| // Otherwise, it's a statement. |
| |
| else |
| { |
| result = statement(); |
| } |
| |
| return result; |
| } |
| |
| |
| |
| /** |
| * A synomym for <code>topLevelStatement()</code>. |
| */ |
| |
| public CSTNode typeDeclaration() throws SyntaxException, CompilationFailedException |
| { |
| return topLevelStatement(); |
| } |
| |
| |
| |
| /** |
| * Processes the modifiers list that can appear on top- and class-level |
| * method and class-level variable names (public, private, abstract, etc.). |
| * <p> |
| * Grammar: <pre> |
| * modifierList = <modifier>* |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * modifiers = { <null> <modifier>* } |
| * </pre> |
| */ |
| |
| public Reduction modifierList(boolean allowStatic, boolean allowAbstract) throws CompilationFailedException, SyntaxException |
| { |
| Reduction modifiers = Reduction.newContainer(); |
| |
| while( la().isA(Types.DECLARATION_MODIFIER) ) |
| { |
| if( lt() == Types.KEYWORD_ABSTRACT && !allowAbstract) |
| { |
| controller.addError( "keyword 'abstract' not valid in this setting", la() ); |
| } |
| else if (lt() == Types.KEYWORD_STATIC && !allowStatic) |
| { |
| controller.addError( "keyword 'static' not valid in this setting", la() ); |
| } |
| modifiers.add( consume() ); |
| } |
| |
| return modifiers; |
| } |
| |
| |
| |
| /** |
| * Processes a class declaration. Caller has already processed the declaration |
| * modifiers, and passes them in. |
| * <p> |
| * Grammar: <pre> |
| * classDeclaration = <modifier>* "class" <identifier> |
| * ["extends" datatype] |
| * ["implements" datatype (, datatype)*] |
| * typeBody |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * class = { <identifier>:SYNTH_CLASS modifiers extends implements body } |
| * extends = { "extends" datatype } | {} |
| * implements = { "implements" datatype* } | {} |
| * |
| * modifiers see modifierList() |
| * datatype see datatype() |
| * body see typeBody() |
| * </pre> |
| */ |
| |
| public Reduction classDeclaration( Reduction modifiers ) throws SyntaxException, CompilationFailedException |
| { |
| consume( Types.KEYWORD_CLASS ); |
| |
| Reduction classDeclaration = consume(Types.IDENTIFIER).asReduction( modifiers ); |
| classDeclaration.setMeaning( Types.SYNTH_CLASS ); |
| |
| |
| // |
| // Process any extends clause. |
| |
| try |
| { |
| classDeclaration.add( typeList(Types.KEYWORD_EXTENDS, true, 1) ); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| classDeclaration.add( Reduction.EMPTY ); |
| } |
| |
| |
| // |
| // Process any implements clause. |
| |
| try |
| { |
| classDeclaration.add( typeList(Types.KEYWORD_IMPLEMENTS, true, 0) ); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| classDeclaration.add( Reduction.EMPTY ); |
| } |
| |
| |
| // |
| // Process the declaration body. We currently ignore the abstract keyword. |
| |
| classDeclaration.add( typeBody(true, true, false) ); |
| |
| return classDeclaration; |
| } |
| |
| |
| |
| /** |
| * Processes a interface declaration. Caller has already processed the |
| * declaration modifiers, and passes them in. |
| * <p> |
| * Grammar: <pre> |
| * interfaceDeclaration = <modifier>* "interface" <identifier> |
| * ["extends" typeList] |
| * typeBody |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * interface = { <identifier>:SYNTH_INTERFACE modifiers {} extends body } |
| * extends = { "extends" datatype* } | {} |
| * |
| * modifiers see modifierList() |
| * datatype see datatype() |
| * body see typeBody() |
| * </pre> |
| */ |
| |
| public Reduction interfaceDeclaration( Reduction modifiers ) throws SyntaxException, CompilationFailedException |
| { |
| consume( Types.KEYWORD_INTERFACE ); |
| |
| Reduction interfaceDeclaration = consume(Types.IDENTIFIER).asReduction( modifiers, Reduction.EMPTY ); |
| interfaceDeclaration.setMeaning( Types.SYNTH_INTERFACE ); |
| |
| |
| // |
| // Process any extends clause. |
| |
| try |
| { |
| interfaceDeclaration.add( typeList(Types.KEYWORD_EXTENDS, true, 0) ); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| interfaceDeclaration.add( Reduction.EMPTY ); |
| } |
| |
| |
| // |
| // Process the declaration body. All methods must be abstract. |
| // Static methods are not allowed. |
| |
| interfaceDeclaration.add( typeBody(false, true, true) ); |
| return interfaceDeclaration; |
| } |
| |
| |
| |
| /** |
| * Processes a type list, like the ones that occur after "extends" or |
| * implements. If the list is optional, the returned CSTNode will |
| * be empty. |
| * <p> |
| * Grammar: <pre> |
| * typeList = <declarator> datatype (, datatype)* |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * typeList = { <declarator> datatype+ } | {} |
| * |
| * datatype see datatype() |
| * </pre> |
| */ |
| |
| public Reduction typeList(int declarator, boolean optional, int limit) throws SyntaxException, CompilationFailedException |
| { |
| Reduction typeList = null; |
| |
| if( lt() == declarator ) |
| { |
| typeList = consume(declarator).asReduction(); |
| |
| // |
| // Loop, reading one datatype at a time. On error, attempt |
| // recovery until the end of the clause is found. |
| |
| while( limit == 0 || typeList.children() < limit ) |
| { |
| // |
| // Try for a list entry, and correct if missing |
| |
| try |
| { |
| if( typeList.children() > 0) |
| { |
| consume( Types.COMMA ); |
| } |
| |
| typeList.add( datatype(false) ); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| recover( Types.TYPE_LIST_TERMINATORS ); |
| } |
| |
| // |
| // Check if we have reached the end point. It is |
| // done at the bottom of the loop to ensure that there |
| // is at least one datatype in the list |
| |
| if( !la().isA(Types.COMMA) ) |
| { |
| break; |
| } |
| } |
| } |
| |
| else |
| { |
| if (optional) |
| { |
| typeList = Reduction.EMPTY; |
| } |
| else |
| { |
| error( declarator ); |
| } |
| } |
| |
| return typeList; |
| } |
| |
| |
| |
| /** |
| * Processes the body of an interface or class. |
| * <p> |
| * Grammar: <pre> |
| * typeBody = "{" typeBodyStatement* "}" |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * body = { <null> typeBodyStatement* } |
| * |
| * typeBodyStatement see typeBodyStatement() |
| * </pre> |
| */ |
| |
| public Reduction typeBody(boolean allowStatic, boolean allowAbstract, boolean requireAbstract) throws SyntaxException, CompilationFailedException |
| { |
| Reduction body = Reduction.newContainer(); |
| |
| consume( Types.LEFT_CURLY_BRACE ); |
| |
| while( lt() != Types.EOF && lt() != Types.RIGHT_CURLY_BRACE ) |
| { |
| try |
| { |
| body.add( typeBodyStatement(allowStatic, allowAbstract, requireAbstract) ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError(e); |
| recover(); |
| } |
| } |
| |
| consume( Types.RIGHT_CURLY_BRACE ); |
| |
| return body; |
| } |
| |
| |
| |
| /** |
| * Processes a single entry in the the body of an interface or class. |
| * Valid objects are constructors, methods, properties, static initializers, |
| * and inner classes or interfaces. |
| * <p> |
| * Grammar: <pre> |
| * typeBodyStatement |
| * = staticInitializer |
| * | classDeclaration |
| * | interfaceDeclaration |
| * | propertyDeclaration |
| * | methodDeclaration |
| * |
| * staticInitializer = ("static" "{" statement* "}") |
| * </pre> |
| * <p> |
| * Recognition: <pre> |
| * "static" "{" => staticInitializer |
| * modifierList "class" => classDeclaration |
| * modifierList "interface" => interfaceDeclaration |
| * modifierList ["property"] optionalDatatype identifier "(" => methodDeclaration |
| * modifierList ["property"] optionalDatatype identifier ("="|";"|"\n"|"}" => propertyDeclaration |
| * * => <error> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * see classDeclaration() |
| * see interfaceDeclaration() |
| * see methodDeclaration() |
| * see propertyDeclaration() |
| * </pre> |
| */ |
| |
| public Reduction typeBodyStatement(boolean allowStatic, boolean allowAbstract, boolean requireAbstract) throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = null; |
| |
| // |
| // As "static" can be both a modifier and a static initializer, we |
| // handle the static initializer first. |
| |
| if( lt() == Types.KEYWORD_STATIC && lt(2) == Types.LEFT_CURLY_BRACE ) |
| { |
| if (!allowStatic) |
| { |
| controller.addError( "static initializers not valid in this context", la() ); |
| } |
| |
| Reduction modifiers = modifierList( true, false ); |
| Token identifier = Token.NULL; |
| statement = methodDeclaration( modifiers, Reduction.EMPTY, identifier, false ); |
| } |
| |
| // |
| // Otherwise, it is a property, constructor, method, class, or interface. |
| |
| else |
| { |
| Reduction modifiers = modifierList( allowStatic, allowAbstract ); |
| |
| // |
| // Check for inner types |
| |
| if( lt() == Types.KEYWORD_CLASS ) |
| { |
| statement = classDeclaration( modifiers ); |
| } |
| |
| else if( lt() == Types.KEYWORD_INTERFACE ) |
| { |
| statement = interfaceDeclaration( modifiers ); |
| } |
| |
| // |
| // Otherwise, it is a property, constructor, or method. |
| |
| else |
| { |
| // |
| // Ignore any property keyword, if present (it's deprecated) |
| |
| if( lt() == Types.KEYWORD_PROPERTY ) |
| { |
| consume(); |
| } |
| |
| // |
| // All processing here is whitespace sensitive, in order |
| // to be consistent with the way "def" functions work (due |
| // to the optionality of the semicolon). One of the |
| // consequences is that the left parenthesis of a |
| // method declaration /must/ appear on the same line. |
| |
| while( lt(true) == Types.NEWLINE) |
| { |
| consume( Types.NEWLINE ); |
| } |
| |
| // |
| // We don't yet know about void, so we err on the side of caution |
| |
| CSTNode type = optionalDatatype( true, true ); |
| Token identifier = nameDeclaration( true ); |
| |
| switch( lt(true) ) |
| { |
| case Types.LEFT_PARENTHESIS : |
| { |
| // |
| // We require abstract if specified on call or the |
| // "abstract" modifier was supplied. |
| |
| boolean methodIsAbstract = requireAbstract; |
| |
| if( !methodIsAbstract ) |
| { |
| for( int i = 1; i < modifiers.size(); i++ ) |
| { |
| if( modifiers.get(i).getMeaning() == Types.KEYWORD_ABSTRACT ) |
| { |
| methodIsAbstract = true; |
| break; |
| } |
| } |
| } |
| |
| statement = methodDeclaration( modifiers, type, identifier, methodIsAbstract ); |
| break; |
| } |
| |
| case Types.EQUAL: |
| case Types.SEMICOLON: |
| case Types.NEWLINE: |
| case Types.RIGHT_CURLY_BRACE: |
| case Types.EOF: |
| statement = propertyDeclaration( modifiers, type, identifier ); |
| break; |
| |
| default: |
| error( new int[] { Types.LEFT_PARENTHESIS, Types.EQUAL, Types.SEMICOLON, Types.NEWLINE, Types.RIGHT_CURLY_BRACE } ); |
| } |
| } |
| } |
| |
| return statement; |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>typeBodyStatement( true, true, false )</code>. |
| */ |
| |
| public Reduction bodyStatement() throws SyntaxException, CompilationFailedException |
| { |
| return typeBodyStatement( true, true, false ); |
| } |
| |
| |
| |
| /** |
| * Processes a name that is valid for declarations. Newlines can be made |
| * significant, if required for disambiguation. |
| * <p> |
| * Grammar: <pre> |
| * nameDeclaration = <identifier> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * name = <identifier> |
| * </pre> |
| */ |
| |
| protected Token nameDeclaration( boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| return consume( Types.IDENTIFIER, significantNewlines ); |
| } |
| |
| |
| |
| /** |
| * Processes a reference to a declared name. Newlines can be made significant, |
| * if required for disambiguation. |
| * <p> |
| * Grammar: <pre> |
| * nameReference = <identifier> | <various keywords> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * name = <identifier> |
| * </pre> |
| */ |
| |
| protected Token nameReference( boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| |
| Token token = la( significantNewlines ); |
| if( !token.canMean(Types.IDENTIFIER) ) |
| { |
| error( Types.IDENTIFIER ); |
| } |
| |
| consume(); |
| token.setMeaning( Types.IDENTIFIER ); |
| |
| return token; |
| |
| } |
| |
| |
| |
| /** |
| * Processes an optional data type marker (for a parameter, method return type, |
| * etc.). Newlines can be made significant, if required for disambiguation. |
| * <p> |
| * Grammar: <pre> |
| * optionalDatatype = datatype? (?=<identifier>) |
| * </pre>h |
| * <p> |
| * CST: <pre> |
| * result = datatype | {} |
| * |
| * see datatype() |
| * </pre> |
| */ |
| |
| protected CSTNode optionalDatatype( boolean significantNewlines, boolean allowVoid ) throws SyntaxException, CompilationFailedException |
| { |
| CSTNode type = Reduction.EMPTY; |
| Token next = la(significantNewlines); |
| |
| // |
| // If the next token is an identifier, it could be an untyped |
| // variable/method name. If it is followed by another identifier, |
| // we'll assume type. Otherwise, we'll attempt a datatype and |
| // restore() the stream if there is a problem. |
| |
| if( next.isA(Types.IDENTIFIER) ) |
| { |
| if( lt(2, significantNewlines) == Types.IDENTIFIER ) |
| { |
| type = datatype( allowVoid ); |
| } |
| else |
| { |
| getTokenStream().checkpoint(); |
| |
| try |
| { |
| type = datatype( allowVoid ); |
| if( lt(significantNewlines) != Types.IDENTIFIER ) |
| { |
| throw new Exception(); |
| } |
| } |
| catch( Exception e ) |
| { |
| getTokenStream().restore(); |
| type = Reduction.EMPTY; |
| } |
| } |
| } |
| |
| // |
| // If it is a primitive type name, it must be a datatype. If void |
| // is present but not allowed, it is an error, and we let datatype() |
| // catch it. |
| |
| else if( next.isA(Types.PRIMITIVE_TYPE) ) |
| { |
| type = datatype( allowVoid ); |
| } |
| |
| return type; |
| } |
| |
| |
| |
| |
| /** |
| * Processes a class/interface property, including the optional initialization |
| * clause. The modifiers, type, and identifier have already been identified |
| * by the caller, and are passed in. |
| * <p> |
| * Grammar: <pre> |
| * propertyDeclaration = (modifierList optionalDatatype nameDeclaration ["=" expression]) <eos> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * property = { <identifier>:SYNTH_PROPERTY modifierList optionalDatatype expression? } |
| * |
| * see modifierList() |
| * see optionalDatatype() |
| * see expression() |
| * </pre> |
| */ |
| |
| public Reduction propertyDeclaration( Reduction modifiers, CSTNode type, Token identifier ) throws SyntaxException, CompilationFailedException |
| { |
| Reduction property = identifier.asReduction( modifiers, type ); |
| property.setMeaning( Types.SYNTH_PROPERTY ); |
| |
| if( lt() == Types.EQUAL ) |
| { |
| consume(); |
| property.add( expression() ); |
| } |
| |
| endOfStatement(); |
| return property; |
| } |
| |
| |
| |
| /** |
| * Processes a class/interface method. The modifiers, type, and identifier have |
| * already been identified by the caller, and are passed in. If <code>emptyOnly</code> |
| * is set, no method body will be allowed. |
| * <p> |
| * Grammar: <pre> |
| * methodDeclaration = modifierList optionalDatatype identifier |
| * "(" parameterDeclarationList ")" |
| * [ "throws" typeList ] |
| * ( statementBody | <eos> ) |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * method = { <identifier>:SYNTH_METHOD modifierList optionalDatatype |
| * parameterDeclarationList throwsClause statementBody } |
| * |
| * throwsClause = { "throws" datatype+ } | {} |
| * |
| * see modifierList() |
| * see optionalDatatype() |
| * see parameterDeclarationList() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| public Reduction methodDeclaration( Reduction modifiers, CSTNode type, Token identifier, boolean emptyOnly) throws SyntaxException, CompilationFailedException |
| { |
| Reduction method = identifier.asReduction( modifiers, type ); |
| method.setMeaning( Types.SYNTH_METHOD ); |
| |
| // |
| // Process the parameter list |
| |
| consume(Types.LEFT_PARENTHESIS); |
| method.add( parameterDeclarationList() ); |
| consume(Types.RIGHT_PARENTHESIS); |
| |
| // |
| // Process the optional "throws" clause |
| |
| try |
| { |
| method.add( typeList( Types.KEYWORD_THROWS, true, 0 ) ); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| method.add( Reduction.EMPTY ); |
| } |
| |
| // |
| // And the body. If it isn't supposed to be there, report the |
| // error, but process it anyway, for the point of recovering. |
| |
| CSTNode body = null; |
| |
| if( emptyOnly ) |
| { |
| if( lt() == Types.LEFT_CURLY_BRACE ) |
| { |
| controller.addError( "abstract and interface methods cannot have a body", la() ); |
| } |
| else |
| { |
| body = Reduction.EMPTY; |
| endOfStatement(); |
| } |
| |
| } |
| |
| if( body == null ) |
| { |
| body = statementBody(true); |
| } |
| |
| method.add( body ); |
| |
| |
| return method; |
| } |
| |
| |
| |
| /** |
| * Processes a parameter declaration list, which can occur on methods and closures. |
| * It loops as long as it finds a comma as the next token. |
| * <p> |
| * Grammar: <pre> |
| * parameterDeclarationList |
| * = (parameterDeclaration ("," parameterDeclaration)* ("," parameterDeclaration "=" expression)* )? |
| * | (parameterDeclaration "=" expression ("," parameterDeclaration "=" expression)* )? |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * parameters = { <null> parameter* } |
| * parameter = { <identifier>:SYNTH_PARAMETER_DECLARATION optionalDatatype default? } |
| * default = expression |
| * </pre> |
| */ |
| |
| protected Reduction parameterDeclarationList() throws SyntaxException, CompilationFailedException |
| { |
| Reduction list = Reduction.newContainer(); |
| |
| boolean expectDefaults = false; |
| while( la().isA(Types.TYPE_NAME) ) // TYPE_NAME includes <identifier>, and so does double duty |
| { |
| |
| // |
| // Get the declaration |
| |
| Reduction parameter = (Reduction)list.add( parameterDeclaration() ); |
| |
| // |
| // Process any default parameter (it is required on every parameter |
| // after the first occurrance). |
| |
| if( expectDefaults || lt() == Types.EQUAL ) |
| { |
| expectDefaults = true; |
| consume( Types.EQUAL ); |
| |
| parameter.add( expression() ); |
| } |
| |
| // |
| // Check if we are done. |
| |
| if( lt() == Types.COMMA ) |
| { |
| consume( Types.COMMA ); |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| return list; |
| } |
| |
| |
| |
| /** |
| * Processes a single parameter declaration, which can occur on methods and closures. |
| * <p> |
| * Grammar: <pre> |
| * parameterDeclaration = optionalDatatype nameDeclaration |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * parameter = { <identifier>:SYNTH_PARAMETER_DECLARATION optionalDatatype } |
| * |
| * see optionalDatatype() |
| * </pre> |
| */ |
| |
| protected Reduction parameterDeclaration() throws SyntaxException, CompilationFailedException |
| { |
| CSTNode type = optionalDatatype( false, false ); |
| Reduction parameter = nameDeclaration( false ).asReduction( type ); |
| parameter.setMeaning( Types.SYNTH_PARAMETER_DECLARATION ); |
| |
| return parameter; |
| } |
| |
| |
| |
| /** |
| * Processes a datatype specification. For reasons of disambiguation, |
| * the array marker ([]) must never be on a separate line from the |
| * base datatype. |
| * <p> |
| * Grammar: <pre> |
| * datatype = scalarDatatype ( "[" "]" )* |
| * |
| * scalarDatatype = dottedIdentifier | "void" | "int" | ... |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * datatype = { "[" datatype } | scalar |
| * scalar = dottedIdentifier | primitive |
| * primitive = "void" | "int" | ... |
| * |
| * see dottedIdentifier() |
| * </pre> |
| */ |
| |
| protected CSTNode datatype( boolean allowVoid ) throws SyntaxException, CompilationFailedException |
| { |
| CSTNode datatype = scalarDatatype(allowVoid); |
| |
| while( lt(true) == Types.LEFT_SQUARE_BRACKET ) |
| { |
| datatype = consume(Types.LEFT_SQUARE_BRACKET).asReduction( datatype ); |
| consume( Types.RIGHT_SQUARE_BRACKET ); |
| } |
| |
| return datatype; |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>datatype( true )</code>. |
| */ |
| |
| protected CSTNode datatype() throws SyntaxException, CompilationFailedException |
| { |
| return datatype(true); |
| } |
| |
| |
| |
| /** |
| * Processes a scalar datatype specification. |
| * <p> |
| * Grammar: <pre> |
| * scalarDatatype = dottedIdentifier | "void" | "int" | ... |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * scalar = dottedIdentifier | primitive |
| * primitive = "void" | "int" | ... |
| * |
| * see dottedIdentifier() |
| * </pre> |
| */ |
| |
| protected CSTNode scalarDatatype( boolean allowVoid ) throws SyntaxException, CompilationFailedException |
| { |
| CSTNode datatype = null; |
| |
| if( la().isA(allowVoid ? Types.PRIMITIVE_TYPE : Types.CREATABLE_PRIMITIVE_TYPE) ) |
| { |
| datatype = consume(); |
| } |
| else |
| { |
| datatype = dottedIdentifier(); |
| } |
| |
| return datatype; |
| } |
| |
| |
| |
| /** |
| * Processes the body of a complex statement (like "if", "for", etc.). |
| * Set <code>requireBraces</code> if the body must not be just a single |
| * statement. |
| * <p> |
| * Grammar: <pre> |
| * statementBody = ("{" statement* "}") |
| * | statement |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * complex = { "{" statement* } |
| * simple = statement |
| * |
| * see statement() |
| * </pre> |
| */ |
| |
| protected CSTNode statementBody( boolean requireBraces ) throws SyntaxException, CompilationFailedException |
| { |
| CSTNode body = null; |
| |
| if (lt() == Types.LEFT_CURLY_BRACE) |
| { |
| Token brace = consume( Types.LEFT_CURLY_BRACE ); |
| brace.setMeaning( Types.SYNTH_BLOCK ); |
| |
| body = statementsUntilRightCurly(); |
| body.set( 0, brace ); |
| |
| consume( Types.RIGHT_CURLY_BRACE ); |
| } |
| else |
| { |
| if( requireBraces ) |
| { |
| error( Types.LEFT_CURLY_BRACE ); |
| } |
| else |
| { |
| body = statement(); |
| } |
| } |
| |
| return body; |
| } |
| |
| |
| |
| /** |
| * Reads statements until a "}" is met. |
| * <p> |
| * Grammar: <pre> |
| * statementsUntilRightCurly = statement* (?= "}") |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statements = { <null> statement* } |
| * </pre> |
| */ |
| |
| protected Reduction statementsUntilRightCurly( ) throws SyntaxException, CompilationFailedException |
| { |
| Reduction block = Reduction.newContainer(); |
| |
| while( lt() != Types.EOF && lt() != Types.RIGHT_CURLY_BRACE ) |
| { |
| try |
| { |
| block.add( statement() ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover(); |
| } |
| } |
| |
| |
| return block; |
| } |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // PRODUCTIONS: STATEMENTS |
| |
| |
| /** |
| * Processes a single statement. Statements include: loop constructs, branch |
| * constructs, flow control constructs, exception constructs, expressions of |
| * a variety of types, and pretty much anything you can put inside a method. |
| * <p> |
| * Grammar: <pre> |
| * statement = (label ":")? bareStatement |
| * bareStatement = (emptyStatement|basicStatement|blockStatement) |
| * |
| * basicStatement = forStatement |
| * | whileStatement |
| * | doStatement |
| * | continueStatement |
| * | breakStatement |
| * | ifStatement |
| * | tryStatement |
| * | throwStatement |
| * | synchronizedStatement |
| * | switchStatement |
| * | returnStatement |
| * | assertStatement |
| * | expression <eos> |
| * |
| * label = <identifier> |
| * blockStatement = "{" statement* "}" |
| * emptyStatement = ";" |
| * </pre> |
| * <p> |
| * Recognition: <pre> |
| * ";" => emptyStatement |
| * <keyword> => <keyword>Statement |
| * "{" => expression, then: |
| * if it is a closureExpression and has no parameters => blockStatement |
| * |
| * * => expression |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * labelled = { <identifier>:SYNTH_LABEL bareStatement } |
| * bareStatement = emptyStatement | blockStatement | basicStatement |
| * emptyStatement = { "{" } |
| * blockStatement = { "{" statement* } |
| * |
| * see forStatement() |
| * see whileStatement() |
| * see doStatement() |
| * see continueStatement() |
| * see breakStatement() |
| * see ifStatement() |
| * see tryStatement() |
| * see throwStatement() |
| * see synchronizedStatement() |
| * see switchStatement() |
| * see returnStatement() |
| * see assertStatement() |
| * see expression() |
| * </pre> |
| */ |
| |
| protected CSTNode statement( boolean allowUnlabelledBlocks ) throws SyntaxException, CompilationFailedException |
| { |
| CSTNode statement = null; |
| |
| // |
| // Check for and grab any label for the statement |
| |
| CSTNode label = null; |
| if( lt() == Types.IDENTIFIER && lt(2) == Types.COLON ) |
| { |
| label = consume( Types.IDENTIFIER ).asReduction(); |
| label.setMeaning( Types.SYNTH_LABEL ); |
| |
| consume( Types.COLON ); |
| } |
| |
| // |
| // Process the statement |
| |
| switch( lt() ) |
| { |
| case Types.KEYWORD_ASSERT: |
| { |
| statement = assertStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_BREAK: |
| { |
| statement = breakStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_CONTINUE: |
| { |
| statement = continueStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_IF: |
| { |
| statement = ifStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_RETURN: |
| { |
| statement = returnStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_SWITCH: |
| { |
| statement = switchStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_SYNCHRONIZED: |
| { |
| statement = synchronizedStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_THROW: |
| { |
| statement = throwStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_TRY: |
| { |
| statement = tryStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_FOR: |
| { |
| statement = forStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_DO: |
| { |
| statement = doWhileStatement(); |
| break; |
| } |
| |
| case Types.KEYWORD_WHILE: |
| { |
| statement = whileStatement(); |
| break; |
| } |
| |
| case Types.SEMICOLON: |
| { |
| statement = consume().asReduction(); |
| statement.setMeaning( Types.SYNTH_BLOCK ); |
| break; |
| } |
| |
| case Types.LEFT_CURLY_BRACE: |
| { |
| |
| // |
| // Bare blocks are no longer generally supported, due to the ambiguity |
| // with closures. Further, closures and blocks can look identical |
| // until after parsing, so we process first as a closure expression, |
| // then, if the expression is a parameter-less, bare closure, rebuild |
| // it as a block (which generally requires a label). Joy. |
| |
| statement = expression(); |
| if( statement.isA(Types.SYNTH_CLOSURE) ) |
| { |
| if( !statement.get(1).hasChildren() ) |
| { |
| Reduction block = statement.getRoot().asReduction(); |
| block.setMeaning( Types.SYNTH_BLOCK ); |
| block.addChildrenOf( statement.get(2) ); |
| |
| if( label == null && !allowUnlabelledBlocks ) |
| { |
| controller.addError( "groovy does not support anonymous blocks; please add a label", statement.getRoot() ); |
| } |
| |
| statement = block; |
| } |
| } |
| else |
| { |
| // |
| // It's a closure expression, and must be a statement |
| |
| endOfStatement(); |
| } |
| |
| break; |
| } |
| |
| default: |
| { |
| try |
| { |
| statement = expression(); |
| endOfStatement(); |
| } |
| catch (SyntaxException e) |
| { |
| controller.addError(e); |
| recover(); |
| } |
| } |
| } |
| |
| |
| // |
| // Wrap the statement in the label, if necessary. |
| |
| if( label != null ) |
| { |
| label.add( statement ); |
| statement = label; |
| } |
| |
| return statement; |
| } |
| |
| |
| |
| /** |
| * Synonym for <code>statement( false )</code>. |
| */ |
| |
| protected CSTNode statement( ) throws SyntaxException, CompilationFailedException |
| { |
| return statement( false ); |
| } |
| |
| |
| |
| /** |
| * Processes an assert statement. |
| * <p> |
| * Grammar: <pre> |
| * assertStatement = "assert" expression (":" expression) <eos> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * assert = { "assert" expression expression? } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction assertStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume( Types.KEYWORD_ASSERT ).asReduction( expression() ); |
| |
| if( lt() == Types.COLON ) |
| { |
| consume( Types.COLON ); |
| statement.add( expression() ); |
| } |
| |
| endOfStatement(); |
| |
| return statement; |
| } |
| |
| |
| |
| /** |
| * Processes a break statement. We require the label on the same line. |
| * <p> |
| * Grammar: <pre> |
| * breakStatement = "break" label? <eos> |
| * |
| * label = <identifier> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statement = { "break" label? } |
| * label = <identifier> |
| * </pre> |
| */ |
| |
| protected Reduction breakStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_BREAK).asReduction(); |
| if( lt(true) == Types.IDENTIFIER ) |
| { |
| statement.add( consume() ); |
| } |
| |
| endOfStatement(); |
| return statement; |
| |
| } |
| |
| |
| |
| /** |
| * Processes a continue statement. We require the label on the same line. |
| * <p> |
| * Grammar: <pre> |
| * continueStatement = "continue" label? <eos> |
| * |
| * label = <identifier> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statement = { "continue" label? } |
| * label = <identifier> |
| * </pre> |
| */ |
| |
| protected Reduction continueStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_CONTINUE).asReduction(); |
| if( lt(true) == Types.IDENTIFIER ) |
| { |
| statement.add( consume() ); |
| } |
| |
| endOfStatement(); |
| return statement; |
| |
| } |
| |
| |
| |
| /** |
| * Processes a throw statement. |
| * <p> |
| * Grammar: <pre> |
| * throwStatement = "throw" expression <eos> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statement = { "throw" expression } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction throwStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_THROW).asReduction( expression() ); |
| endOfStatement(); |
| return statement; |
| |
| } |
| |
| |
| |
| /** |
| * Processes an if statement. |
| * <p> |
| * Grammar: <pre> |
| * ifStatement = ifClause elseIfClause* elseClause? |
| * |
| * ifClause = "if" "(" expression ")" statementBody |
| * elseIfClause = "else" "if" "(" expression ")" statementBody |
| * elseClause = "else" statementBody |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * if = { "if" expression statementBody else? } |
| * else = if | { "else" statementBody } |
| * |
| * see expression() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| protected Reduction ifStatement() throws SyntaxException, CompilationFailedException |
| { |
| // |
| // Process the if clause |
| |
| Reduction statement = consume(Types.KEYWORD_IF).asReduction(); |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| |
| try |
| { |
| statement.add( expression() ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover( Types.RIGHT_PARENTHESIS ); |
| } |
| |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| statement.add( statementBody(false) ); |
| |
| |
| // |
| // If the else clause is present: |
| // if it is an else if, recurse |
| // otherwise, build the else node directly. |
| |
| if( lt() == Types.KEYWORD_ELSE ) |
| { |
| if( lt(2) == Types.KEYWORD_IF ) |
| { |
| consume( Types.KEYWORD_ELSE ); |
| statement.add( ifStatement() ); |
| } |
| else |
| { |
| Reduction last = (Reduction)statement.add( consume(Types.KEYWORD_ELSE).asReduction() ); |
| last.add( statementBody(false) ); |
| } |
| } |
| |
| return statement; |
| } |
| |
| |
| |
| /** |
| * Processes a return statement. Any expression must start on the same line |
| * as the "return". |
| * <p> |
| * Grammar: <pre> |
| * returnStatement = "return" expression? <eos> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statement = { "return" expression? } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction returnStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_RETURN).asReduction(); |
| |
| if( !la(true).isA(Types.ANY_END_OF_STATEMENT) ) |
| { |
| statement.add( expression() ); |
| } |
| |
| endOfStatement(); |
| return statement; |
| |
| } |
| |
| |
| |
| /** |
| * Processes a switch statement. |
| * <p> |
| * Grammar: <pre> |
| * switchStatment = "switch" "(" expression ")" "{" switchBody "}" |
| * |
| * switchBody = caseSet* |
| * caseSet = (("case" expression ":")+ | ("default" ":")) statement+ |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * switch = { "switch" expression case* } |
| * case = { "case" expression statement* } |
| * | { "default" statement* } |
| * |
| * see expression() |
| * see statement() |
| * </pre> |
| */ |
| |
| protected Reduction switchStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_SWITCH).asReduction(); |
| consume( Types.LEFT_PARENTHESIS ); |
| statement.add( expression() ); |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| // |
| // Process the switch body. Labels can be pretty much anything, |
| // but we'll duplicate-check for default. |
| |
| consume( Types.LEFT_CURLY_BRACE ); |
| |
| boolean defaultFound = false; |
| while( lt() == Types.KEYWORD_CASE || lt() == Types.KEYWORD_DEFAULT ) |
| { |
| // |
| // Read the label |
| |
| Reduction caseBlock = null; |
| if( lt() == Types.KEYWORD_CASE ) |
| { |
| caseBlock = consume( Types.KEYWORD_CASE ).asReduction( expression() ); |
| } |
| else if( lt() == Types.KEYWORD_DEFAULT ) |
| { |
| if( defaultFound ) |
| { |
| controller.addError( "duplicate default entry in switch", la() ); |
| } |
| |
| caseBlock = consume( Types.KEYWORD_DEFAULT ).asReduction(); |
| defaultFound = true; |
| } |
| else |
| { |
| error( new int[] { Types.KEYWORD_DEFAULT, Types.KEYWORD_CASE } ); |
| recover( Types.SWITCH_ENTRIES ); |
| } |
| |
| consume( Types.COLON ); |
| |
| |
| // |
| // Process the statements, if any |
| |
| boolean first = true; |
| while( !la().isA(Types.SWITCH_BLOCK_TERMINATORS) ) |
| { |
| caseBlock.add( statement(first) ); |
| first = false; |
| } |
| |
| statement.add( caseBlock ); |
| } |
| |
| consume( Types.RIGHT_CURLY_BRACE ); |
| |
| return statement; |
| } |
| |
| |
| |
| /** |
| * Processes a synchronized statement. |
| * <p> |
| * Grammar: <pre> |
| * synchronizedStatement = "synchronized" "(" expression ")" statementBody |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statement = { "synchronized" expression statementBody } |
| * |
| * see expression() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| protected Reduction synchronizedStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_SYNCHRONIZED).asReduction(); |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| statement.add( expression() ); |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| statement.add( statementBody(true) ); |
| |
| return statement; |
| |
| } |
| |
| |
| |
| /** |
| * Processes a try statement. |
| * <p> |
| * Grammar: <pre> |
| * tryStatement = "try" statementBody catchClause* finallyClause? |
| * |
| * catchClause = "catch" "(" datatype identifier ")" statementBody |
| * finallyClause = "finally" statementBody |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * try = { "try" statementBody catches finally } |
| * |
| * catches = { <null> catch* } |
| * |
| * catch = { "catch" datatype <identifier> statementBody } |
| * |
| * finally = {} | statementBody |
| * |
| * see datatype() |
| * see identifier() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| protected Reduction tryStatement() throws SyntaxException, CompilationFailedException |
| { |
| |
| // |
| // Set up the statement with the try clause |
| |
| Reduction statement = consume(Types.KEYWORD_TRY).asReduction(); |
| statement.add( statementBody(true) ); |
| |
| |
| // |
| // Process the catch clauses |
| |
| Reduction catches = (Reduction)statement.add( Reduction.newContainer() ); |
| while( lt() == Types.KEYWORD_CATCH ) |
| { |
| try |
| { |
| Reduction catchBlock = (Reduction)catches.add( consume(Types.KEYWORD_CATCH).asReduction() ); |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| try |
| { |
| catchBlock.add( datatype(false) ); |
| catchBlock.add( nameDeclaration(false) ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover( Types.RIGHT_PARENTHESIS ); |
| } |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| catchBlock.add( statementBody(true) ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover(); |
| } |
| } |
| |
| // |
| // Process the finally clause, if available. |
| |
| if( lt() == Types.KEYWORD_FINALLY ) |
| { |
| consume( Types.KEYWORD_FINALLY ); |
| statement.add( statementBody(true) ); |
| } |
| else |
| { |
| statement.add( Reduction.EMPTY ); |
| } |
| |
| return statement; |
| } |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // PRODUCTIONS: LOOP STATEMENTS |
| |
| |
| /** |
| * Processes a for statement. |
| * <p> |
| * Grammar: <pre> |
| * forStatement = "for" "(" normal | each ")" statementBody |
| * |
| * normal = multi ";" expression ";" multi |
| * multi = (expression ["," expression]*) |
| * |
| * each = optionalDatatype nameDeclaration ("in"|":") expression |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * for = { "for" header statementBody } |
| * |
| * header = normal | each |
| * each = { ("in"|":") optionalDatatype nameDeclaration expression } |
| * |
| * normal = { <null> init test incr } |
| * init = { <null> expression* } |
| * test = expression |
| * incr = { <null> expression* } |
| * |
| * see expression() |
| * see nameDeclaration() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| protected Reduction forStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume( Types.KEYWORD_FOR ).asReduction(); |
| |
| // |
| // The for loop is a little tricky. There are three forms, |
| // and the first two can't be processed with expression(). |
| // In order to avoid complications, we are going to checkpoint() |
| // the stream before processing optionalDatatype(), then restore |
| // it if we need to use expression(). |
| // |
| // Anyway, after processing the optionalDatatype(), if KEYWORD_IN |
| // or a COLON is at la(2), it's an each loop. Otherwise, it's the |
| // standard for loop. |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| |
| getTokenStream().checkpoint(); |
| |
| Reduction header = null; |
| CSTNode datatype = optionalDatatype( false, false ); |
| |
| if( lt(2) == Types.KEYWORD_IN || lt(2) == Types.COLON ) |
| { |
| Token name = nameDeclaration( false ); |
| header = consume().asReduction( datatype, name, expression() ); |
| } |
| else |
| { |
| getTokenStream().restore(); |
| header = Reduction.newContainer(); |
| |
| Reduction init = Reduction.newContainer(); |
| while( lt() != Types.SEMICOLON && lt() != Types.EOF ) |
| { |
| init.add( expression() ); |
| |
| if( lt() != Types.SEMICOLON ) |
| { |
| consume( Types.COMMA ); |
| } |
| } |
| |
| consume( Types.SEMICOLON ); |
| |
| header.add( init ); |
| |
| |
| // |
| // Next up, a single expression is the test clause, followed |
| // by a semicolon. |
| |
| header.add( expression() ); |
| consume( Types.SEMICOLON ); |
| |
| |
| // |
| // Finally, the increment section is a (possibly empty) comma- |
| // separated list of expressions followed by the RIGHT_PARENTHESIS. |
| |
| Reduction incr = (Reduction)header.add( Reduction.newContainer() ); |
| |
| while( lt() != Types.RIGHT_PARENTHESIS && lt() != Types.EOF ) |
| { |
| incr.add( expression() ); |
| |
| if( lt() != Types.RIGHT_PARENTHESIS ) |
| { |
| consume( Types.COMMA ); |
| } |
| } |
| } |
| |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| statement.add( header ); |
| statement.add( statementBody(false) ); |
| |
| return statement; |
| } |
| |
| |
| |
| /** |
| * Processes a do ... while statement. |
| * <p> |
| * Grammar: <pre> |
| * doWhileStatement = "do" statementBody "while" "(" expression ")" <eos> |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * do = { "do" statementBody expression } |
| * |
| * see expression() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| protected Reduction doWhileStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_DO).asReduction(); |
| statement.add( statementBody(false) ); |
| consume( Types.KEYWORD_WHILE ); |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| try |
| { |
| statement.add( expression() ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover( Types.RIGHT_PARENTHESIS ); |
| } |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| return statement; |
| |
| } |
| |
| |
| |
| /** |
| * Processes a while statement. |
| * <p> |
| * Grammar: <pre> |
| * whileStatement = "while" "(" expression ")" statementBody |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * while = { "while" expression statementBody } |
| * |
| * see expression() |
| * see statementBody() |
| * </pre> |
| */ |
| |
| protected Reduction whileStatement() throws SyntaxException, CompilationFailedException |
| { |
| Reduction statement = consume(Types.KEYWORD_WHILE).asReduction(); |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| |
| try |
| { |
| statement.add( expression() ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover( Types.RIGHT_PARENTHESIS ); |
| } |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| statement.add( statementBody(false) ); |
| return statement; |
| |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // PRODUCTIONS: EXPRESSIONS |
| |
| |
| /** |
| * Processes a single (sub-)expression into a CSTNode. No assumption is |
| * made about what follows the expression. |
| * <p> |
| * Note that the expression parser is rather stupid, in that it cannot |
| * resolve names. Therefore it is little more than a pre-filter, removing |
| * statements that can't possibly be right, but leaving everything that |
| * might be right for semantic analysis by the <code>Analyzer</code> (which |
| * has access to the symbol table. There was some thought given to eliminating |
| * the CSTs and going right to ASTs, but that option was rejected because |
| * inner classes mean that class name resolution won't work before parsing |
| * is complete. |
| */ |
| |
| protected CSTNode expression( ) throws SyntaxException, CompilationFailedException |
| { |
| // int id = nestCount++; |
| // System.out.println( "ENTERING EXPRESSION " + id ); |
| |
| ExpressionStack stack = new ExpressionStack( this ); |
| CSTNode expression = null; |
| |
| boolean bareMode = false; |
| |
| MAIN_LOOP: do |
| { |
| // |
| // Valid at the start of an (sub)expression, a typed variable declaration |
| // is handled separately. It has the form |
| |
| // |
| // In the SHIFT phase, we move stuff onto the stack that can have |
| // multiple meanings and/or precedence issues, and leave the interpretation |
| // for a later REDUCE. No lookahead is used. When structures are found that |
| // have a consistent form, we use LL techniques (the new operator, for instance). |
| |
| Token next = la(stack); |
| int type = next.getMeaningAs( EXPRESSION_SHIFT_HANDLERS ); |
| |
| // System.out.println( "expression() status:" ); |
| // System.out.println( stack.toString() ); |
| // System.out.println( "next: " ); |
| // System.out.println( next.toString() ); |
| // System.out.println( la(2).toString() ); |
| |
| SHIFT: switch( type ) |
| { |
| case Types.GSTRING_START: |
| { |
| if( stack.topIsAnExpression() ) |
| { |
| error( "gstring cannot directly follow another expression" ); |
| } |
| |
| stack.push( gstring() ); |
| break; |
| } |
| |
| |
| case Types.CREATABLE_PRIMITIVE_TYPE: |
| { |
| stack.shiftIf( stack.atStartOfExpression(), "type name not valid in this context" ); |
| break; |
| } |
| |
| |
| case Types.SIMPLE_EXPRESSION: |
| { |
| |
| // |
| // Method parameters don't make it here (see REDUCE)... |
| |
| stack.shiftUnlessTopIsAnExpression( "literal cannot directly follow another expression" ); |
| break; |
| } |
| |
| |
| case Types.KEYWORD_IDENTIFIER: |
| { |
| if( stack.top().isA(Types.DEREFERENCE_OPERATOR) && stack.topIsAnOperator() ) |
| { |
| la().setMeaning( Types.IDENTIFIER ); |
| stack.shift(); |
| } |
| else |
| { |
| error( "not valid as an identifier in this context" ); |
| } |
| break; |
| } |
| |
| |
| case Types.ASSIGNMENT_OPERATOR: |
| { |
| stack.shiftIf( stack.topIsAModifiableExpression(), "left-hand-side of assignment must be modifiable" ); |
| break; |
| } |
| |
| |
| case Types.PREFIX_OR_INFIX_OPERATOR: |
| { |
| if( stack.topIsAnOperator(0, true) ) |
| { |
| Types.makePrefix( next, false ); |
| } |
| stack.shift( ); |
| break; |
| } |
| |
| |
| case Types.PREFIX_OPERATOR: |
| { |
| Types.makePrefix( next, false ); |
| stack.shift( ); |
| break; |
| } |
| |
| |
| case Types.QUESTION: |
| case Types.INFIX_OPERATOR: |
| { |
| stack.shiftIfTopIsAnExpression( "infix operators may only follow expressions" ); |
| break; |
| } |
| |
| |
| case Types.LEFT_PARENTHESIS: |
| { |
| // |
| // Method calls don't make it here (see REDUCE). It is |
| // either a sub-expression or a cast. |
| |
| boolean condition = stack.atStartOfExpression() || (stack.topIsAnOperator() && !stack.top().isA(Types.DEREFERENCE_OPERATOR)); |
| stack.shiftIf( condition, "sub-expression not valid at this position" ); |
| break; |
| } |
| |
| |
| |
| case Types.LEFT_CURLY_BRACE: |
| { |
| if( stack.atStartOfExpression() || stack.topIsAnOperator() || stack.top().isA(Types.SYNTH_METHOD_CALL) ) |
| { |
| stack.push( closureExpression() ); |
| } |
| else |
| { |
| error( "closure not valid in this context" ); |
| } |
| break; |
| } |
| |
| |
| case Types.LEFT_SQUARE_BRACKET: |
| { |
| boolean isMap = false, insist = false; |
| if( stack.topIsAnExpression() ) |
| { |
| insist = true; |
| } |
| stack.push( listOrMapExpression(isMap, insist) ); |
| break; |
| } |
| |
| |
| case Types.KEYWORD_NEW: |
| { |
| if( stack.atStartOfExpression() || stack.topIsAnOperator() ) |
| { |
| stack.push( newExpression() ); |
| } |
| else |
| { |
| error( "new can follow the start of an expression or another operator" ); |
| } |
| break; |
| } |
| |
| |
| case Types.KEYWORD_INSTANCEOF: |
| { |
| stack.shiftIf( stack.topIsAnExpression(), "instanceof may only follow an expression" ); |
| break; |
| } |
| |
| |
| default: |
| { |
| |
| // |
| // All other operators are caught during REDUCE, so if it makes |
| // it here, it's either the end of the expression, or an error. |
| |
| if( stack.size() == 1 && stack.topIsAnExpression() ) |
| { |
| break MAIN_LOOP; // <<< FLOW CONTROL <<<<<<<<< |
| } |
| else |
| { |
| error(); |
| } |
| } |
| |
| |
| } |
| |
| |
| |
| // |
| // In the REDUCE phase, we try to find ways to convert several stack |
| // elements (and maybe one lookahead token) into a single expression. |
| // We retry the REDUCE as long as it succeeds. Note that reductions |
| // are ONLY possible when the top of the stack is an expression. |
| |
| boolean checkAgain = false, skipPatterns = false; |
| CSTNode top0 = null, top1 = null, top2 = null; |
| int nextPrecedence = 0, top1Precedence = 0; |
| |
| REDUCE: do |
| { |
| if( !stack.topIsAnExpression() && !ExpressionSupport.isAPotentialTypeName(stack.top(), false) ) |
| { |
| break; |
| } |
| |
| |
| // |
| // We reduce at most once per iteration, so we collect info here. |
| |
| checkAgain = false; |
| skipPatterns = false; |
| |
| top0 = stack.top(); |
| top1 = stack.top(1); |
| top2 = stack.top(2); |
| |
| next = la( stack ); |
| |
| // System.out.println( "expression() stack for reduce: " + stack ); |
| // System.out.println( "expression() next token for reduce: " + next ); |
| |
| nextPrecedence = Types.getPrecedence( next.getMeaning(), false ); |
| top1Precedence = Types.getPrecedence( top1.getMeaning(), false ); |
| |
| |
| |
| //--------------------------------------------------------------- |
| // UNUSUAL STUFF FIRST |
| |
| |
| // |
| // Not really an operator at all, if top1 is a "(" and next is an ")", |
| // we should reduce. Extra processing is needed because the "(" is not |
| // the type of an expression. |
| |
| if( top1.isA(Types.LEFT_PARENTHESIS) ) |
| { |
| if( next.isA(Types.RIGHT_PARENTHESIS) ) |
| { |
| consume(); |
| |
| // |
| // To simplify things, cast operators MUST appear on the same line |
| // as the start of their operands. Without name lookup, we can't |
| // be sure that even things that look like casts are, but we assume |
| // they are and let later phases correct, where necessary. |
| |
| next = la(true); // XXX the precludes is true for GString. Seems wrong |
| boolean castPrecluded = next.isA(Types.NEWLINE) || next.isA(Types.PRECLUDES_CAST_OPERATOR); |
| |
| if( ExpressionSupport.isAPotentialTypeName(top0, false) && !castPrecluded ) |
| { |
| CSTNode name = stack.pop(); |
| Reduction cast = ((Token)stack.pop()).asReduction( name ); |
| cast.setMeaning( Types.SYNTH_CAST ); |
| stack.push( cast ); |
| } |
| else |
| { |
| CSTNode subexpression = stack.pop(); |
| stack.pop(); |
| stack.push( subexpression ); |
| } |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| else |
| { |
| skipPatterns = true; |
| } |
| } |
| |
| |
| // |
| // Highest precedence: "new". If it is preceeded on the stack by |
| // a ".", what preceeds the "." is the context for the new, and |
| // we'll have to do some rewriting.... Note that SHIFT will only |
| // shift a "new" if it is preceeded by nothing or an operator, |
| // and it will only shift a "." if it is preceeded by an expression. |
| // Therefore, we can assume any preceeding "." is an operator. |
| |
| if( top0.isA(Types.KEYWORD_NEW) && !top0.isAnExpression() ) |
| { |
| top0.markAsExpression(); |
| |
| if( top1.isA(Types.DOT) ) |
| { |
| CSTNode theNew = stack.pop(); |
| CSTNode theDot = stack.pop(); |
| CSTNode context = stack.pop(); |
| |
| theNew.set( 1, context ); |
| stack.push( theNew ); |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| } |
| |
| |
| // |
| // Not unusual, but handled here to simplify precendence handling for |
| // the rest of the unusual stuff: dereference operators are left-associative. |
| |
| if( top1.isA(Types.DEREFERENCE_OPERATOR) && !top0.hasChildren() ) |
| { |
| stack.reduce( 3, 1, true ); |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| |
| // |
| // Next precedence, array offsets. Because we allow lists and ranges |
| // and such inside list expressions, all lists will have been processed |
| // to a SYNTH_LISTH during SHIFT. Here we do some rewriting, where |
| // necessary. Empty array offsets are only allowed on types, and we |
| // run the appropriate conversion in that case. |
| |
| if( top0.isA(Types.SYNTH_LIST) && top1.isAnExpression() || ExpressionSupport.isAPotentialTypeName(top1, false) ) |
| { |
| // |
| // Empty list is an array type |
| |
| if( !top0.hasChildren() ) |
| { |
| boolean typePreceeds = ExpressionSupport.isAPotentialTypeName(top1, false); |
| boolean potentialCast = top2.isA(Types.LEFT_PARENTHESIS); |
| boolean potentialDecl = top2.isA(Types.LEFT_PARENTHESIS) || top2.isA(Types.UNKNOWN); |
| boolean classReference = next.isA(Types.DOT) && la(2).isA(Types.KEYWORD_CLASS); |
| if( !(typePreceeds && (potentialCast || potentialDecl || classReference)) ) |
| { |
| error( "empty square brackets are only valid on type names" ); |
| } |
| |
| // |
| // Okay, we have an array type. We now convert the list and |
| // expression to an array type, and slurp any further dimensions |
| // off the lookahead. |
| |
| Reduction array = stack.pop().asReduction(); |
| array.setMeaning( Types.LEFT_SQUARE_BRACKET ); |
| array.add( stack.pop() ); |
| |
| while( lt(true) == Types.LEFT_SQUARE_BRACKET ) |
| { |
| array = consume( Types.LEFT_SQUARE_BRACKET ).asReduction( array ); |
| consume( Types.RIGHT_SQUARE_BRACKET ); |
| } |
| |
| |
| // |
| // One last decision: variable type declaration, or |
| // cast, or class reference... |
| |
| if( classReference ) |
| { |
| CSTNode reference = consume(Types.DOT).asReduction(array, consume(Types.KEYWORD_CLASS)); |
| reference.markAsExpression(); |
| stack.push( reference ); |
| |
| } |
| else if( lt(true) == Types.IDENTIFIER && lt(2) == Types.EQUAL ) |
| { |
| stack.push( variableDeclarationExpression(array) ); |
| } |
| else if( stack.top().isA(Types.LEFT_PARENTHESIS) && la(true).isA(Types.RIGHT_PARENTHESIS) ) |
| { |
| CSTNode cast = ((Token)stack.pop()).asReduction( array ); |
| cast.setMeaning( Types.SYNTH_CAST ); |
| stack.push( cast ); |
| consume( Types.RIGHT_PARENTHESIS ); |
| } |
| else |
| { |
| error( "found array type where none expected" ); |
| } |
| } |
| |
| |
| // |
| // Non-empty list is an offset (probably) |
| |
| else |
| { |
| CSTNode list = stack.pop(); |
| CSTNode base = stack.pop(); |
| |
| Reduction result = ((Token)list.get(0)).dup().asReduction(); |
| result.setMeaning( Types.LEFT_SQUARE_BRACKET ); |
| result.add( base ); |
| |
| if( list.children() == 1 ) |
| { |
| result.add( list.get(1) ); |
| } |
| else |
| { |
| result.add( list ); |
| } |
| |
| result.markAsExpression(); |
| stack.push( result ); |
| } |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| |
| } |
| |
| |
| |
| // |
| // Next precedence: typed variable declarations. If the top of stack |
| // isAPotentialTypeName(), la(true) is an identifier, and la(2) is |
| // an "=", it's a declaration. |
| |
| if( la(true).isA(Types.IDENTIFIER) && lt(2) == Types.EQUALS && ExpressionSupport.isAPotentialTypeName(top0, false) ) |
| { |
| stack.push( variableDeclarationExpression(stack.pop()) ); |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| // |
| // Before getting to method call handling proper, we should check for any |
| // pending bookkeeping. If the top of stack is a closure and the element |
| // before it is a method call, the closure is either one of its parameters |
| // or an error. |
| |
| if( top1.isA(Types.SYNTH_METHOD_CALL) && top0.isA(Types.SYNTH_CLOSURE) ) |
| { |
| CSTNode parameters = top1.get(2); |
| |
| int last = parameters.size() - 1; |
| if( last > 0 && parameters.get(last).isA(Types.SYNTH_CLOSURE) ) |
| { |
| error( "you may only pass one closure to a method implicitly" ); |
| } |
| |
| parameters.add( stack.pop() ); |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| // |
| // Next precedence: method calls and typed declarations. If the top of stack |
| // isInvokable() and la(stack) is an "(", an "{", or a simple expression, it's |
| // a method call. We leave the closure for the next SHIFT. |
| |
| if( ExpressionSupport.isInvokable(top0) && (next.isA(Types.LEFT_CURLY_BRACE) || la(true).isA(Types.METHOD_CALL_STARTERS)) ) |
| { |
| // System.out.println( "making a method call of " + top0 ); |
| |
| CSTNode name = stack.pop(); |
| |
| Reduction method = null; |
| switch( next.getMeaning() ) |
| { |
| case Types.LEFT_PARENTHESIS: |
| method = consume().asReduction(); |
| method.add( name ); |
| method.add( la().isA(Types.RIGHT_PARENTHESIS) ? Reduction.newContainer() : parameterList() ); |
| consume( Types.RIGHT_PARENTHESIS ); |
| break; |
| |
| case Types.LEFT_CURLY_BRACE: |
| method = Token.newSymbol( Types.LEFT_PARENTHESIS, next.getStartLine(), next.getStartColumn() ).asReduction(); |
| method.add( name ); |
| method.add( Reduction.newContainer() ); |
| break; |
| |
| default: |
| method = Token.newSymbol( Types.LEFT_PARENTHESIS, next.getStartLine(), next.getStartColumn() ).asReduction(); |
| method.add( name ); |
| method.add( parameterList() ); |
| break; |
| } |
| |
| method.setMeaning( Types.SYNTH_METHOD_CALL ); |
| method.markAsExpression(); |
| |
| stack.push( method ); |
| |
| if( lt() != Types.LEFT_CURLY_BRACE ) |
| { |
| checkAgain = true; |
| } |
| |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| // |
| // Handle postfix operators next. We have to check for acceptable |
| // precedence before doing it. All the higher precedence reductions |
| // have already been checked. |
| |
| if( next.isA(Types.POSTFIX_OPERATOR) && stack.topIsAnExpression() ) |
| { |
| if( !ExpressionSupport.isAVariable(stack.top()) ) |
| { |
| error( "increment/decrement operators can only be applied to variables" ); |
| } |
| |
| Types.makePostfix( next, true ); |
| |
| stack.shift(); |
| stack.reduce( 2, 0, true ); |
| |
| checkAgain = true; |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| // |
| // The ternary operator will be seen twice. The first reduction is |
| // infix when there is a ":" on lookahed. The second reduction is |
| // prefix when there is a lower-precedence operator on lookahed. |
| // The ternary operator is right-associative. Note that |
| // Types.getPrecedence() on a ternary operator returns 10. |
| |
| if( top1.isA(Types.QUESTION) ) |
| { |
| boolean reduce = false; |
| |
| if( la().isA(Types.COLON) ) |
| { |
| if( top1.hasChildren() ) |
| { |
| error( "ternary operator can have only three clauses" ); |
| } |
| |
| consume(); |
| stack.reduce( 3, 1, false ); |
| checkAgain = true; |
| } |
| else if( Types.getPrecedence(next.getMeaning(), false) < 10 ) |
| { |
| stack.reduce( 2, 1, false ); |
| stack.top().setMeaning( Types.SYNTH_TERNARY ); |
| checkAgain = true; |
| } |
| |
| |
| continue; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------- |
| // PATTERN STUFF SECOND |
| |
| |
| // |
| // Note that because of the loop control above, we get here only if none |
| // of the above options matched. |
| // |
| // So, everything else we handle generically: top1 will be an operator, and |
| // will be reduced or not with top0 and possibly top2, depending on the |
| // cardinality and associativity of the operator, and the type of la(). |
| |
| if( skipPatterns || !ExpressionSupport.isAnOperator(top1, false) ) |
| { |
| break; // <<< LOOP CONTROL <<<<<<<<< |
| } |
| |
| |
| switch( top1.getMeaningAs(EXPRESSION_REDUCE_HANDLERS) ) |
| { |
| // |
| // Prefix increment/decrement operators aren't always valid, so we |
| // handle the separately from the other prefix operators. |
| |
| case Types.PREFIX_PLUS_PLUS: |
| case Types.PREFIX_MINUS_MINUS: |
| { |
| if( nextPrecedence < top1Precedence ) |
| { |
| if( !ExpressionSupport.isAVariable(stack.top()) ) |
| { |
| error( "increment/decrement operators can only be applied to variables" ); |
| } |
| |
| stack.reduce( 2, 1, true ); |
| checkAgain = true; |
| } |
| |
| break; |
| } |
| |
| |
| // |
| // All other prefix operators. They are all right-associative. |
| |
| case Types.PURE_PREFIX_OPERATOR: |
| { |
| if( nextPrecedence < top1Precedence ) |
| { |
| stack.reduce( 2, 1, true ); |
| checkAgain = true; |
| } |
| |
| break; |
| } |
| |
| |
| // |
| // Handle the assignment operators. They are all right-associative. |
| |
| case Types.ASSIGNMENT_OPERATOR: |
| { |
| if( nextPrecedence < top1Precedence ) |
| { |
| stack.reduce( 3, 1, true ); |
| checkAgain = true; |
| } |
| |
| break; |
| } |
| |
| |
| // |
| // Handle the instenceof keyword. The rhs has to be a potential type. |
| |
| case Types.KEYWORD_INSTANCEOF: |
| { |
| if( nextPrecedence < top1Precedence ) |
| { |
| if( !ExpressionSupport.isAPotentialTypeName(top0, false) ) |
| { |
| error( "instanceof right-hand side must be a valid type name" ); |
| } |
| |
| stack.reduce( 3, 1, true ); |
| checkAgain = true; |
| } |
| |
| break; |
| } |
| |
| |
| // |
| // Handle all other infix operators. They are all left-associative. |
| |
| case Types.INFIX_OPERATOR: |
| { |
| if( nextPrecedence <= top1Precedence ) |
| { |
| stack.reduce( 3, 1, true ); |
| checkAgain = true; |
| } |
| |
| break; |
| } |
| |
| |
| // |
| // Anything else in top1 is an bug. |
| |
| default: |
| { |
| throw new GroovyBugError( "found unexpected token during REDUCE [" + top1.getMeaning() + "]" ); |
| } |
| } |
| |
| } while( checkAgain ); |
| |
| } while( true ); |
| |
| |
| if( stack.size() == 1 && stack.topIsAnExpression() ) |
| { |
| expression = stack.pop(); |
| } |
| else |
| { |
| error( "expression incomplete" ); |
| } |
| |
| |
| // System.out.println( "EXITING EXPRESSION " + id ); |
| return expression; |
| } |
| |
| |
| private static final int EXPRESSION_SHIFT_HANDLERS[] = { |
| Types.GSTRING_START |
| , Types.CREATABLE_PRIMITIVE_TYPE |
| , Types.SIMPLE_EXPRESSION |
| , Types.KEYWORD_IDENTIFIER |
| , Types.ASSIGNMENT_OPERATOR |
| , Types.PREFIX_OR_INFIX_OPERATOR |
| , Types.PREFIX_OPERATOR |
| , Types.QUESTION |
| , Types.INFIX_OPERATOR |
| , Types.LEFT_PARENTHESIS |
| , Types.LEFT_CURLY_BRACE |
| , Types.LEFT_SQUARE_BRACKET |
| , Types.KEYWORD_NEW |
| , Types.KEYWORD_INSTANCEOF |
| }; |
| |
| private static final int EXPRESSION_REDUCE_HANDLERS[] = { |
| Types.PREFIX_PLUS_PLUS |
| , Types.PREFIX_MINUS_MINUS |
| , Types.PURE_PREFIX_OPERATOR |
| , Types.ASSIGNMENT_OPERATOR |
| , Types.KEYWORD_INSTANCEOF |
| , Types.INFIX_OPERATOR |
| }; |
| |
| |
| |
| /** |
| * Processes a typed variable declaration. Without the type, it's a |
| * assignment expression instead (no comma support). The datatype |
| * has already been identified and is passed in. |
| * <p> |
| * Grammar: <pre> |
| * variableDeclarationExpression |
| * = datatype (nameDeclaration "=" expression) |
| * ("," nameDeclaration "=" expression)* |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * statement = { :<SYNTH_VARIABLE_DECLARATION> datatype declaration+ } |
| * declaration = { <identifier> expression } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction variableDeclarationExpression( CSTNode datatype ) throws SyntaxException, CompilationFailedException |
| { |
| Reduction expression = ((Token)datatype.get(0)).dup().asReduction( datatype ); // done for line number on SYNTH |
| expression.setMeaning( Types.SYNTH_VARIABLE_DECLARATION ); |
| |
| boolean done = false; |
| do |
| { |
| try |
| { |
| Reduction declaration = (Reduction)expression.add( nameDeclaration(false).asReduction() ); |
| consume( Types.EQUAL ); |
| declaration.add( expression() ); |
| } |
| catch( SyntaxException e ) |
| { |
| controller.addError( e ); |
| recover( Types.ANY_END_OF_STATEMENT ); |
| } |
| |
| if( lt() == Types.COMMA ) |
| { |
| consume( Types.COMMA ); |
| } |
| else |
| { |
| done = true; |
| } |
| |
| } while( !done ); |
| |
| |
| return expression; |
| } |
| |
| |
| |
| /** |
| * Processes a GString. |
| * <p> |
| * Grammar: <pre> |
| * gstring = (<text>? "$" "{" expression "}" <text>?)* |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * gstring = { <full-text>:SYNTH_GSTRING (segment|expression)* } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction gstring() throws SyntaxException, CompilationFailedException |
| { |
| // int id = nestCount++; |
| // System.out.println( "ENTERING GSTRING " + id ); |
| |
| Reduction data = Reduction.newContainer(); |
| |
| consume( Types.GSTRING_START ); |
| |
| while( lt() != Types.GSTRING_END && lt() != Types.EOF ) |
| { |
| switch( lt() ) |
| { |
| case Types.STRING: |
| data.add( consume() ); |
| break; |
| |
| case Types.GSTRING_EXPRESSION_START: |
| consume(); |
| data.add( expression() ); |
| consume( Types.GSTRING_EXPRESSION_END ); |
| break; |
| |
| default: |
| throw new GroovyBugError( "gstring found invalid token: " + la() ); |
| } |
| } |
| |
| Reduction complete = consume( Types.GSTRING_END ).asReduction(); |
| complete.addChildrenOf( data ); |
| |
| complete.setMeaning( Types.SYNTH_GSTRING ); |
| |
| // System.out.println( "EXITING GSTRING " + id ); |
| return complete; |
| } |
| |
| |
| |
| |
| /** |
| * Processes a NON-EMPTY parameter list, as supplied on either a method invokation or |
| * a closure invokation. Reads parameters until something that doesn't belong |
| * is found. |
| * <p> |
| * Grammar: <pre> |
| * parameterList = (regular "," named) | named |
| * regular = parameter ("," parameter)* |
| * named = nameReference ":" parameter ("," nameReference ":" parameter)* |
| * |
| * parameter = expression |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * parameterList = { <null> regular* named* } |
| * regular = expression |
| * named = { ":" <identifier> expression } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction parameterList() throws SyntaxException, CompilationFailedException |
| { |
| // int id = nestCount++; |
| // System.out.println( "ENTERING PARAMETER LIST " + id ); |
| |
| Reduction list = Reduction.newContainer(); |
| Reduction named = null; |
| |
| boolean done = false; |
| |
| do |
| { |
| if( la().canMean(Types.IDENTIFIER) && la(2).isA(Types.COLON) ) |
| { |
| if( named == null ) |
| { |
| named = Token.newPlaceholder(Types.SYNTH_MAP).asReduction(); |
| list.add( named ); |
| } |
| |
| Token name = nameReference(false); |
| name.setMeaning( Types.STRING ); |
| |
| named.add( consume(Types.COLON).asReduction(name, expression()) ); |
| } |
| else |
| { |
| list.add( expression() ); |
| } |
| |
| |
| if( lt() == Types.COMMA ) |
| { |
| consume(); |
| } |
| else |
| { |
| done = true; |
| } |
| |
| |
| } while( !done ); |
| |
| // System.out.println( "EXITING PARAMETER LIST " + id ); |
| return list; |
| } |
| |
| |
| |
| /** |
| * Processes a "new" expression. Handles optional constructors, array |
| * initializations, closure arguments, and anonymous classes. In order |
| * to support anonymous classes, anonymous closures are not allowed. |
| * <p> |
| * Grammar: <pre> |
| * newExpression = "new" scalarDatatype (array|init)? |
| * array = ( "[" expression "]" )+ | ( ("[" "]")+ newArrayInitializer ) |
| * init = "(" parameterList? ")" (typeBody | closureExpression)? |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * new = { "new" arrayType dimensions newArrayInitializer? } |
| * | { "new" scalarDataype (parameterList|{<null>}) typeBody? } |
| * |
| * arrayType = { "{" (arrayType | scalarDatatype) } |
| * dimensions = { <null> expression+ } | {} |
| * |
| * see expression() |
| * see scalarDatatype() |
| * see typeBody() |
| * </pre> |
| */ |
| |
| protected Reduction newExpression() throws SyntaxException, CompilationFailedException |
| { |
| // int id = nestCount++; |
| // System.out.println( "ENTERING NEW " + id ); |
| |
| Reduction expression = consume(Types.KEYWORD_NEW).asReduction(); |
| CSTNode scalarType = scalarDatatype(false); |
| |
| if( lt(true) == Types.LEFT_SQUARE_BRACKET ) |
| { |
| // |
| // First up, figure out the actual type and any |
| // stated dimensions. |
| |
| boolean implicit = (lt(2) == Types.RIGHT_SQUARE_BRACKET); |
| Reduction dimensions = implicit ? Reduction.EMPTY : Reduction.newContainer(); |
| int count = 0; |
| CSTNode arrayType = scalarType; |
| |
| while( lt(true) == Types.LEFT_SQUARE_BRACKET ) |
| { |
| arrayType = consume(Types.LEFT_SQUARE_BRACKET).asReduction( arrayType ); |
| count++; |
| |
| if( !implicit ) |
| { |
| dimensions.add( expression() ); |
| } |
| |
| consume(Types.RIGHT_SQUARE_BRACKET); |
| } |
| |
| expression.add( arrayType ); |
| expression.add( dimensions ); |
| |
| // |
| // If implicit, there must be initialization data |
| |
| if( implicit ) |
| { |
| expression.add( tupleExpression(0, count) ); |
| } |
| |
| } |
| |
| else |
| { |
| expression.add( scalarType ); |
| |
| |
| // |
| // Process the constructor call |
| |
| Reduction parameters = null; |
| |
| consume( Types.LEFT_PARENTHESIS ); |
| parameters = (lt() == Types.RIGHT_PARENTHESIS ? Reduction.newContainer() : parameterList()); |
| consume( Types.RIGHT_PARENTHESIS ); |
| |
| expression.add( parameters ); |
| |
| |
| // |
| // If a "{" follows, it's a class body or a closure... |
| |
| if( lt() == Types.LEFT_CURLY_BRACE ) |
| { |
| if( lt(2) == Types.PIPE || lt(2) == Types.DOUBLE_PIPE ) |
| { |
| parameters.add( closureExpression() ); |
| } |
| else |
| { |
| expression.add( typeBody(true, false, false) ); |
| } |
| } |
| } |
| |
| // System.out.println( "EXITING NEW " + id ); |
| return expression; |
| } |
| |
| |
| |
| /** |
| * Processes a "new" array initializer expression. |
| * <p> |
| * Grammar: <pre> |
| * tupleExpression = "{" (tupleExpression | (expression ("," expression))? "}" |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * initializer = { "{":SYNTH_TUPLE (initializer*|expression*) } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction tupleExpression( int level, int depth ) throws SyntaxException, CompilationFailedException |
| { |
| Reduction data = consume(Types.LEFT_CURLY_BRACE).asReduction(); |
| data.setMeaning( Types.SYNTH_TUPLE ); |
| |
| if( lt() != Types.RIGHT_CURLY_BRACE ) |
| { |
| int child = level + 1; |
| boolean leaf = (child == depth); |
| |
| do |
| { |
| data.add( leaf ? expression() : tupleExpression(child, depth) ); |
| |
| } while( lt() == Types.COMMA && (consume() != null) ); |
| } |
| |
| consume( Types.RIGHT_CURLY_BRACE ); |
| |
| return data; |
| } |
| |
| |
| |
| /** |
| * Processes a closure expression. |
| * <p> |
| * Grammar: <pre> |
| * closureExpression = "{" parameters statement* "}" |
| * parameters = ("|" parameterDeclarationList "|")? |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * initializer = { "{":SYNTH_CLOSURE parameters statements } |
| * parameters = parameterDeclarationList | { <null> } |
| * statements = { <null> statement* } |
| * |
| * see parameterDeclarationList() |
| * see statement() |
| * </pre> |
| */ |
| |
| protected Reduction closureExpression( ) throws SyntaxException, CompilationFailedException |
| { |
| // int id = nestCount++; |
| // System.out.println( "ENTERING CLOSURE EXPRESSION " + id ); |
| |
| Reduction closure = consume(Types.LEFT_CURLY_BRACE).asReduction(); |
| closure.setMeaning( Types.SYNTH_CLOSURE ); |
| boolean specified = (lt() == Types.PIPE) || (lt() == Types.DOUBLE_PIPE); |
| |
| // |
| // DEPRECATED: the old syntax for parameters had a | only |
| // at the end of the parameter list. The new syntax has |
| // two pipes or none. For now, we attempt to support the |
| // old syntax. It can mistake a variable declaration |
| // for a parameter declaration, though, so it may cause more |
| // trouble than it's worth. This if() and the one below |
| // (also marked) should be removed before v1.0. |
| |
| if( !specified ) |
| { |
| getTokenStream().checkpoint(); |
| CSTNode type = optionalDatatype( true, false ); |
| if( lt() == Types.IDENTIFIER && (lt(2) == Types.PIPE || lt(2) == Types.COMMA) ) |
| { |
| specified = true; |
| } |
| |
| getTokenStream().restore(); |
| } |
| |
| |
| // |
| // If the parameter list is specified, process it. |
| |
| if( specified ) |
| { |
| if( lt() == Types.DOUBLE_PIPE ) |
| { |
| consume( Types.DOUBLE_PIPE ); |
| closure.add( Reduction.newContainer() ); |
| } |
| else |
| { |
| // |
| // DEPRECATED: further support for note above, this consume() |
| // should not be conditional after the above code is removed. |
| |
| if( lt() == Types.PIPE ) |
| { |
| consume(Types.PIPE); |
| } |
| |
| closure.add( parameterDeclarationList() ); |
| consume(Types.PIPE); |
| } |
| } |
| else |
| { |
| closure.add( Reduction.newContainer() ); |
| } |
| |
| |
| // |
| // Finally, process the statements. |
| |
| closure.add( statementsUntilRightCurly() ); |
| consume( Types.RIGHT_CURLY_BRACE ); |
| |
| // System.out.println( "EXITING CLOSURE EXPRESSION " + id ); |
| return closure; |
| } |
| |
| |
| |
| /** |
| * Processes a list or map expression. |
| * <p> |
| * Grammar: <pre> |
| * listOrMapExpression = list | map |
| * |
| * list = "[" (expression ("," expression)*)? "]" |
| * |
| * map = "[" (":" | mapping+) "]" |
| * mapping = expression ":" expression |
| * </pre> |
| * <p> |
| * CST: <pre> |
| * list = { "[":SYNTH_LIST expression* } |
| * map = { "[":SYNTH_MAP mapping* } |
| * mapping = { ":" expression expression } |
| * |
| * see expression() |
| * </pre> |
| */ |
| |
| protected Reduction listOrMapExpression( boolean isMap, boolean insist ) throws SyntaxException, CompilationFailedException |
| { |
| Reduction expression = consume(Types.LEFT_SQUARE_BRACKET).asReduction(); |
| expression.setMeaning( Types.SYNTH_LIST ); |
| |
| if( lt() == Types.COLON ) |
| { |
| if( !isMap && insist ) |
| { |
| error( "expected list" ); |
| } |
| |
| isMap = true; |
| expression.setMeaning( Types.SYNTH_MAP ); |
| consume(); |
| if( lt() != Types.RIGHT_SQUARE_BRACKET ) |
| { |
| error( "expected empty map" ); |
| } |
| } |
| |
| |
| // |
| // Process the data. On the first one, check if we are |
| // processing a map. We assume not going in, as the empty |
| // map isn't relevant... |
| |
| boolean done = (lt() == Types.RIGHT_SQUARE_BRACKET); |
| |
| while( !done ) |
| { |
| CSTNode element = expression(); |
| |
| if( !insist ) |
| { |
| insist = true; |
| if( lt() == Types.COLON ) |
| { |
| isMap = true; |
| expression.setMeaning(Types.SYNTH_MAP); |
| } |
| } |
| |
| if( isMap ) |
| { |
| element = consume(Types.COLON).asReduction( element, expression() ); |
| } |
| |
| expression.add( element ); |
| |
| if( lt() == Types.COMMA ) { consume(); } else { done = true; } |
| } |
| |
| consume(Types.RIGHT_SQUARE_BRACKET); |
| |
| return expression; |
| } |
| |
| |
| |
| /** |
| * Synonym for <code>listOrMapExpression( false, false )</code>. |
| */ |
| |
| protected Reduction listOrMapExpression( ) throws SyntaxException, CompilationFailedException |
| { |
| return listOrMapExpression( false, false ); |
| } |
| |
| |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // ERROR REPORTING |
| |
| |
| /** |
| * Reports an error assembled from parts. |
| */ |
| |
| protected UnexpectedTokenException error( Token found, int[] expectedTypes, boolean throwIt, String comment ) throws SyntaxException |
| { |
| UnexpectedTokenException e = new UnexpectedTokenException( found, expectedTypes, comment ); |
| |
| if( throwIt ) |
| { |
| throw e; |
| } |
| |
| return e; |
| } |
| |
| |
| /** |
| * Reports an error by generating and optionally throwing an |
| * <code>UnexpectedTokenException</code>. |
| */ |
| |
| protected UnexpectedTokenException error( int[] expectedTypes, boolean throwIt, int k, String comment ) throws SyntaxException, CompilationFailedException |
| { |
| return error( la(k), expectedTypes, throwIt, comment ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>error( expectedTypes, throwIt, k, null )</code>. |
| */ |
| |
| protected UnexpectedTokenException error( int[] expectedTypes, boolean throwIt, int k ) throws SyntaxException, CompilationFailedException |
| { |
| return error( expectedTypes, throwIt, k, null ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>error( expectedTypes, true, 1, null )</code>. |
| */ |
| |
| protected void error( int[] expectedTypes ) throws SyntaxException, CompilationFailedException |
| { |
| throw error( expectedTypes, false, 1, null ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>error( null, true, 1, null )</code>. |
| */ |
| |
| protected void error() throws SyntaxException, CompilationFailedException |
| { |
| throw error( null, true, 1, null ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>error( null, true, 1, comment )</code>. |
| */ |
| |
| protected void error( String comment ) throws SyntaxException, CompilationFailedException |
| { |
| throw error( null, true, 1, comment ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>error( found, null, true, comment )</code>. |
| */ |
| |
| protected void error( Token found, String comment ) throws SyntaxException |
| { |
| throw error( found, null, true, comment ); |
| } |
| |
| |
| |
| /** |
| * A scalar synonym of <code>error( expectedTypes )</code>. |
| */ |
| |
| protected void error( int expectedType ) throws SyntaxException, CompilationFailedException |
| { |
| error( new int[] { expectedType } ); |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // ERROR RECOVERY |
| |
| |
| /** |
| * Attempts to recover from an error by discarding input until a |
| * known token is found. It further guarantees that /at least/ |
| * one token will be eaten. |
| */ |
| |
| public void recover( int[] safe, boolean ignoreNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| Token leading = la( ignoreNewlines ); |
| |
| while( true ) |
| { |
| Token next = la( ignoreNewlines ); |
| if( next.isA(Types.EOF) || next.isOneOf(safe) ) |
| { |
| break; |
| } |
| else |
| { |
| consume( ignoreNewlines ); |
| } |
| } |
| |
| if( la(ignoreNewlines) == leading ) |
| { |
| consume( ignoreNewlines ); |
| } |
| } |
| |
| |
| |
| /** |
| * A scalar version of <code>recover( int[], boolean )</code>. |
| */ |
| |
| public void recover( int safe, boolean ignoreNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| Token leading = la( ignoreNewlines ); |
| |
| while( true ) |
| { |
| Token next = la( ignoreNewlines ); |
| if( next.isA(Types.EOF) || next.isA(safe) ) |
| { |
| break; |
| } |
| else |
| { |
| consume( ignoreNewlines ); |
| } |
| } |
| |
| if( la(ignoreNewlines) == leading ) |
| { |
| consume( ignoreNewlines ); |
| } |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>recover( safe, false )</code>. |
| */ |
| |
| public void recover( int[] safe ) throws SyntaxException, CompilationFailedException |
| { |
| recover( safe, false ); |
| } |
| |
| |
| |
| /** |
| * A synonm for the scalar <code>recover( safe, false )</code>. |
| */ |
| |
| public void recover( int safe ) throws SyntaxException, CompilationFailedException |
| { |
| recover( safe, false ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>recover( Types.ANY_END_OF_STATMENT, true )</code>. |
| */ |
| |
| public void recover( ) throws SyntaxException, CompilationFailedException |
| { |
| recover( Types.ANY_END_OF_STATEMENT, true ); |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // TOKEN LOOKAHEAD |
| |
| |
| /** |
| * Returns (without consuming) the next kth token in the underlying |
| * token stream. You can make newlines significant as needed. |
| * Returns Token.EOF on end of stream. k is counted from 1. |
| */ |
| |
| protected Token la( int k, boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| Token token = Token.NULL; |
| |
| // |
| // Count down on k while counting up on streamK. |
| // NOTE: k starting at less than 1 is a mistake... |
| // This routine will reliably NOT return Token.NULL |
| // /unless/ it is actually in the stream. |
| |
| try |
| { |
| int streamK = 1; |
| while( k > 0 && token.getMeaning() != Types.EOF ) |
| { |
| token = getTokenStream().la( streamK ); |
| streamK += 1; |
| |
| if( token == null ) |
| { |
| token = Token.EOF; |
| } |
| else if( token.getMeaning() == Types.NEWLINE ) |
| { |
| if( significantNewlines ) |
| { |
| k -= 1; |
| } |
| } |
| else |
| { |
| k -= 1; |
| } |
| } |
| } |
| catch( ReadException e ) |
| { |
| controller.addFatalError( new SimpleMessage(e.getMessage()) ); |
| } |
| |
| return token; |
| } |
| |
| |
| |
| /** |
| * Synonym for <code>la( k, false )</code>. |
| */ |
| |
| protected Token la( int k ) throws SyntaxException, CompilationFailedException |
| { |
| return la( k, false ); |
| } |
| |
| |
| |
| /** |
| * Synonym for <code>la( 1, significantNewlines )</code>. |
| */ |
| |
| protected Token la( boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| return la( 1, significantNewlines ); |
| } |
| |
| |
| |
| /** |
| * Synonym for <code>la( 1, false )</code>. |
| */ |
| |
| protected Token la() throws SyntaxException, CompilationFailedException |
| { |
| return la( 1, false ); |
| } |
| |
| |
| |
| /** |
| * Special <code>la()</code> used by the expression parser. It will get the next token |
| * in the current statement. If the next token is past a line boundary and might be |
| * the start of the next statement, it won't cross the line to get it. |
| */ |
| |
| protected Token la( ExpressionStack stack ) throws SyntaxException, CompilationFailedException |
| { |
| Token next = la(); |
| |
| if( stack.canComplete() && next.isA(Types.UNSAFE_OVER_NEWLINES) ) |
| { |
| if( la(true).getMeaning() == Types.NEWLINE ) |
| { |
| next = la(true); |
| } |
| } |
| |
| return next; |
| } |
| |
| |
| |
| |
| /** |
| * Returns the meaning of the <code>la( k, significantNewlines )</code> token. |
| */ |
| |
| protected int lt( int k, boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| return la(k, significantNewlines).getMeaning(); |
| } |
| |
| |
| |
| /** |
| * Returns the meaning of the <code>la( k )</code> token. |
| */ |
| |
| protected int lt( int k ) throws SyntaxException, CompilationFailedException |
| { |
| return la(k).getMeaning(); |
| } |
| |
| |
| |
| /** |
| * Returns the meaning of the <code>la( significantNewlines )</code> token. |
| */ |
| |
| protected int lt( boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| return la(significantNewlines).getMeaning(); |
| } |
| |
| |
| |
| /** |
| * Returns the meaning of the <code>la()</code> token. |
| */ |
| |
| protected int lt() throws SyntaxException, CompilationFailedException |
| { |
| return la().getMeaning(); |
| } |
| |
| |
| |
| |
| //--------------------------------------------------------------------------- |
| // TOKEN CONSUMPTION |
| |
| |
| /** |
| * Consumes (and returns) the next token if it is of the specified type. |
| * If <code>significantNewlines</code> is set, newlines will not automatically |
| * be consumed; otherwise they will. Throws <code>UnexpectedTokenException</code> |
| * if the type doesn't match. |
| */ |
| |
| protected Token consume( int type, boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| try |
| { |
| if( !la(significantNewlines).isA(type) ) |
| { |
| error( type ); |
| } |
| |
| if( !significantNewlines ) |
| { |
| while( lt(true) == Types.NEWLINE ) |
| { |
| getTokenStream().consume(Types.NEWLINE); |
| } |
| } |
| |
| return getTokenStream().consume(type); |
| } |
| catch( ReadException e ) |
| { |
| controller.addFatalError( new SimpleMessage(e.getMessage()) ); |
| } |
| |
| throw new GroovyBugError( "this should never happen" ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>consume( type, false )</code>. If type is Types.NEWLINE, |
| * equivalent to <code>consume( Types.NEWLINE, true )</code>. |
| */ |
| |
| protected Token consume( int type ) throws SyntaxException, CompilationFailedException |
| { |
| return consume( type, type == Types.NEWLINE ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>consume( Types.ANY, false )</code>. |
| */ |
| |
| protected Token consume() throws SyntaxException, CompilationFailedException |
| { |
| return consume( lt(), false ); |
| } |
| |
| |
| |
| /** |
| * A synonym for <code>consume( Types.ANY, significantNewlines )</code>. |
| * If you pass true, it will consume exactly the next token from the |
| * stream. |
| */ |
| |
| protected Token consume( boolean significantNewlines ) throws SyntaxException, CompilationFailedException |
| { |
| return consume( lt(significantNewlines), significantNewlines ); |
| } |
| |
| } |