/* 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.drools.drl.parser.lang;

import org.antlr.runtime.CommonToken;
import org.antlr.runtime.MismatchedTokenException;
import org.antlr.runtime.MissingTokenException;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.antlr.runtime.UnwantedTokenException;
import org.drools.drl.ast.dsl.AbstractClassTypeDeclarationBuilder;
import org.drools.drl.ast.dsl.AccumulateDescrBuilder;
import org.drools.drl.ast.dsl.AccumulateImportDescrBuilder;
import org.drools.drl.ast.dsl.AnnotatedDescrBuilder;
import org.drools.drl.ast.dsl.AnnotationDescrBuilder;
import org.drools.drl.ast.dsl.AttributeDescrBuilder;
import org.drools.drl.ast.dsl.AttributeSupportBuilder;
import org.drools.drl.ast.dsl.BehaviorDescrBuilder;
import org.drools.drl.ast.dsl.CEDescrBuilder;
import org.drools.drl.ast.dsl.CollectDescrBuilder;
import org.drools.drl.ast.dsl.ConditionalBranchDescrBuilder;
import org.drools.drl.ast.dsl.DeclareDescrBuilder;
import org.drools.drl.ast.dsl.DescrBuilder;
import org.drools.drl.ast.dsl.EntryPointDeclarationDescrBuilder;
import org.drools.drl.ast.dsl.EnumDeclarationDescrBuilder;
import org.drools.drl.ast.dsl.EnumLiteralDescrBuilder;
import org.drools.drl.ast.dsl.EvalDescrBuilder;
import org.drools.drl.ast.dsl.FieldDescrBuilder;
import org.drools.drl.ast.dsl.ForallDescrBuilder;
import org.drools.drl.ast.dsl.FunctionDescrBuilder;
import org.drools.drl.ast.dsl.GlobalDescrBuilder;
import org.drools.drl.ast.dsl.ImportDescrBuilder;
import org.drools.drl.ast.dsl.NamedConsequenceDescrBuilder;
import org.drools.drl.ast.dsl.PackageDescrBuilder;
import org.drools.drl.ast.dsl.ParameterSupportBuilder;
import org.drools.drl.ast.dsl.PatternContainerDescrBuilder;
import org.drools.drl.ast.dsl.PatternDescrBuilder;
import org.drools.drl.ast.dsl.QueryDescrBuilder;
import org.drools.drl.ast.dsl.RuleDescrBuilder;
import org.drools.drl.ast.dsl.TypeDeclarationDescrBuilder;
import org.drools.drl.ast.dsl.UnitDescrBuilder;
import org.drools.drl.ast.dsl.WindowDeclarationDescrBuilder;
import org.drools.drl.ast.dsl.impl.AnnotationDescrBuilderImpl;
import org.drools.drl.ast.descr.AndDescr;
import org.drools.drl.ast.descr.AnnotatedBaseDescr;
import org.drools.drl.ast.descr.AnnotationDescr;
import org.drools.drl.ast.descr.AttributeDescr;
import org.drools.drl.ast.descr.BaseDescr;
import org.drools.drl.ast.descr.ConditionalElementDescr;
import org.drools.drl.ast.descr.EntryPointDeclarationDescr;
import org.drools.drl.ast.descr.EnumDeclarationDescr;
import org.drools.drl.ast.descr.ExistsDescr;
import org.drools.drl.ast.descr.FunctionDescr;
import org.drools.drl.ast.descr.GlobalDescr;
import org.drools.drl.ast.descr.ImportDescr;
import org.drools.drl.ast.descr.NotDescr;
import org.drools.drl.ast.descr.OrDescr;
import org.drools.drl.ast.descr.PackageDescr;
import org.drools.drl.ast.descr.RuleDescr;
import org.drools.drl.ast.descr.TypeDeclarationDescr;
import org.drools.drl.ast.descr.UnitDescr;
import org.drools.drl.ast.descr.WindowDeclarationDescr;
import org.drools.util.StringUtils;
import org.kie.internal.builder.conf.LanguageLevelOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

public class DRL6StrictParser extends AbstractDRLParser implements DRLParser {

    private static final Logger LOG = LoggerFactory.getLogger(DRL6StrictParser.class);

    private final DRL6Expressions exprParser;

    public DRL6StrictParser(TokenStream input) {
        super(input);
        this.exprParser = new DRL6Expressions(input, state, helper);
    }

    protected LanguageLevelOption getLanguageLevel() {
        return LanguageLevelOption.DRL6_STRICT;
    }

    /* ------------------------------------------------------------------------------------------------
     *                         GRAMMAR RULES
     * ------------------------------------------------------------------------------------------------ */

    protected final PackageDescr compilationUnit(PackageDescrBuilder pkg) throws RecognitionException {
        try {
            // package declaration?
            if (input.LA(1) != DRL6Lexer.EOF && helper.validateIdentifierKey(DroolsSoftKeywords.PACKAGE)) {
                String pkgName = packageStatement(pkg);
                pkg.name(pkgName);
                if (state.failed)
                    return pkg.getDescr();

                // unit declaration?
                // this is only allowed immediately after the package declaration
                if (input.LA(1) != DRL6Lexer.EOF && helper.validateIdentifierKey(DroolsSoftKeywords.UNIT)) {
                    unitStatement(pkg);
                }
            }

            // statements
            while (input.LA(1) != DRL6Lexer.EOF) {
                annotations();
                if (state.failed) {
                    return pkg.getDescr();
                }

                int next = input.index();
                if (helper.validateStatement(1)) {
                    statement(pkg);
                    if (state.failed)
                        return pkg.getDescr();

                    if (next == input.index()) {
                        // no token consumed, so, report problem:
                        resyncToNextStatement();
                    }
                } else {
                    resyncToNextStatement();
                }

                if (input.LA(1) == DRL6Lexer.SEMICOLON) {
                    match(input,
                            DRL6Lexer.SEMICOLON,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return pkg.getDescr();
                }

                annotationsCollector.clear();
            }
        } catch (RecognitionException e) {
            helper.reportError(e);
        } catch (Exception e) {
            helper.reportError(e);
        } finally {
            helper.setEnd(pkg);
        }
        return pkg.getDescr();
    }

    private void annotations() {
        while (input.LA(1) == DRL6Lexer.AT && !state.failed) {
            annotation(annotationsCollector);
        }
    }

    private void setAnnotationsOn(AnnotatedDescrBuilder builder) {
        annotationsCollector.setAnnotationsOn(builder);
    }

    private void setAnnotationsOn(BaseDescr annotationsContainer) throws DroolsUnexpectedAnnotationException {
        annotationsCollector.setAnnotationsOn(annotationsContainer);
    }

    private final AnnotationsCollector annotationsCollector = new AnnotationsCollector();

    private class AnnotationsCollector implements AnnotatedDescrBuilder {

        private AnnotatedBaseDescr descr = new AnnotatedBaseDescr();

        @Override
        public AnnotationDescrBuilder newAnnotation(String name) {
            AnnotationDescrBuilder annotation = new AnnotationDescrCreator( name );
            descr.addAnnotation((AnnotationDescr) annotation.getDescr());
            return annotation;
        }

        private class AnnotationDescrCreator extends AnnotationDescrBuilderImpl {
            public AnnotationDescrCreator(String name) {
                super( null, name );
            }
        }

        public void clear() {
            if (!descr.getAnnotations().isEmpty()) {
                descr = new AnnotatedBaseDescr();
            }
        }

        public void setAnnotationsOn(AnnotatedDescrBuilder builder) {
            if (!descr.getAnnotations().isEmpty()) {
                for (AnnotationDescr annDescr : descr.getAnnotations()) {
                    AnnotationDescrBuilder annotation = builder.newAnnotation(annDescr.getName());
                    for (Map.Entry<String, Object> valueEntry : annDescr.getValueMap().entrySet()) {
                        annotation.keyValue(valueEntry.getKey(), valueEntry.getValue());
                    }
                }
                clear();
            }
        }

        public void setAnnotationsOn(BaseDescr annotationsContainer) throws DroolsUnexpectedAnnotationException {
            if (!descr.getAnnotations().isEmpty()) {
                if (annotationsContainer instanceof AnnotatedBaseDescr) {
                    for (AnnotationDescr annotationDescr : descr.getAnnotations()) {
                        ((AnnotatedBaseDescr) annotationsContainer).addAnnotation(annotationDescr);
                    }
                } else {
                    AnnotationDescr annotationDescr = descr.getAnnotations().iterator().next();
                    failUnexpectedAnnotationException(annotationDescr.getName());
                }
                clear();
            }
        }
    }

    private void resyncToNextStatement() {
        helper.reportError(new DroolsMismatchedSetException(helper.getStatementKeywords(),
                input));
        do {
            // error recovery: look for the next statement, skipping all tokens until then
            input.consume();
        } while (input.LA(1) != DRL6Lexer.EOF && !helper.validateStatement(1));
    }

    /**
     * Parses a package statement and returns the name of the package
     * or null if none is defined.
     *
     * packageStatement := PACKAGE qualifiedIdentifier SEMICOLON?
     *
     * @return the name of the package or null if none is defined
     */
    public String packageStatement(PackageDescrBuilder pkg) throws RecognitionException {
        String pkgName = null;

        try {
            helper.start(pkg,
                    PackageDescrBuilder.class,
                    null);

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.PACKAGE,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return pkgName;

            pkgName = qualifiedIdentifier();
            if (state.failed)
                return pkgName;
            if (state.backtracking == 0) {
                helper.setParaphrasesValue(DroolsParaphraseTypes.PACKAGE,
                        pkgName);
            }

            if (input.LA(1) == DRL6Lexer.SEMICOLON) {
                match(input,
                        DRL6Lexer.SEMICOLON,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return pkgName;
            }

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(PackageDescrBuilder.class,
                    pkg);
        }
        return pkgName;
    }

    /**
     * unitStatement := UNIT qualifiedIdentifier SEMICOLON?
     */
    public UnitDescr unitStatement( PackageDescrBuilder pkg ) throws RecognitionException {
        UnitDescrBuilder imp = helper.start( pkg,
                                             UnitDescrBuilder.class,
                                             null );

        try {
            // import
            match(input,
                  DRL6Lexer.ID,
                  DroolsSoftKeywords.UNIT,
                  null,
                  DroolsEditorType.KEYWORD);
            if (state.failed) return null;

            // qualifiedIdentifier
            String target = qualifiedIdentifier();
            if (state.failed)
                return null;

            if (state.backtracking == 0) {
                imp.target( target );
            }

            if (input.LA(1) == DRL6Lexer.SEMICOLON) {
                match(input,
                      DRL6Lexer.SEMICOLON,
                      null,
                      null,
                      DroolsEditorType.SYMBOL);
                if (state.failed) return null;
            }
            return (imp != null) ? imp.getDescr() : null;
        } finally {
            helper.end(ImportDescrBuilder.class,
                       imp);
        }
    }

    /**
     * statement := importStatement
     *           |  globalStatement
     *           |  declare
     *           |  rule
     *           |  ruleAttribute
     *           |  function
     *           |  query
     *           ;
     *
     * @throws org.antlr.runtime.RecognitionException
     */
    public BaseDescr statement(PackageDescrBuilder pkg) throws RecognitionException {
        BaseDescr descr = null;
        try {
            if (helper.validateIdentifierKey(DroolsSoftKeywords.IMPORT)) {
                descr = importStatement(pkg);
                if (state.failed)
                    return descr;
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.GLOBAL)) {
                descr = globalStatement(pkg);
                if (state.failed)
                    return descr;
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DECLARE)) {
                descr = declare(pkg);
                if (state.failed)
                    return descr;
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.RULE)) {
                descr = rule(pkg);
                if (state.failed)
                    return descr;
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.QUERY)) {
                descr = query(pkg);
                if (state.failed)
                    return descr;
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.FUNCTION)) {
                descr = function(pkg);
                if (state.failed)
                    return descr;
            } else if (helper.validateAttribute(1)) {
                descr = attribute(pkg);
                if (state.failed)
                    return descr;
            }
        } catch (RecognitionException e) {
            helper.reportError(e);
        } catch (Exception e) {
            helper.reportError(e);
        }
        return descr;
    }

    /* ------------------------------------------------------------------------------------------------
     *                         IMPORT STATEMENT
     * ------------------------------------------------------------------------------------------------ */

    /**
     * importStatement := IMPORT ((FUNCTION|STATIC)? qualifiedIdentifier ((DOT STAR)?
     *                           |(ACC|ACCUMULATE) qualifiedIdentifier ID)
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public ImportDescr importStatement(PackageDescrBuilder pkg) throws RecognitionException {
        try {
            String kwd;
            if (helper.validateLT(2, kwd = DroolsSoftKeywords.ACC) ||
                    helper.validateLT(2, kwd = DroolsSoftKeywords.ACCUMULATE)) {
                AccumulateImportDescrBuilder imp = helper.start(pkg,
                        AccumulateImportDescrBuilder.class,
                        null);

                try {
                    // import
                    match(input,
                            DRL6Lexer.ID,
                            DroolsSoftKeywords.IMPORT,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;

                    // import accumulate
                    match(input,
                            DRL6Lexer.ID,
                            kwd,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;

                    // qualifiedIdentifier
                    String target = qualifiedIdentifier();
                    if (state.failed)
                        return null;
                    
                    // function name
                    Token id = match(input,
                            DRL6Lexer.ID,
                            null,
                            null,
                            DroolsEditorType.IDENTIFIER);
                    if (state.failed)
                        return null;
                    if (state.backtracking == 0) {
                        imp.target(target).functionName(id.getText());
                    }

                    return (imp != null) ? imp.getDescr() : null;
                } finally {
                    helper.end(AccumulateImportDescrBuilder.class,
                            imp);
                }
            } else {
                ImportDescrBuilder imp = helper.start(pkg,
                        ImportDescrBuilder.class,
                        null);

                try {
                    // import
                    match(input,
                            DRL6Lexer.ID,
                            DroolsSoftKeywords.IMPORT,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;

                    if (helper.validateIdentifierKey(kwd = DroolsSoftKeywords.FUNCTION) ||
                            helper.validateIdentifierKey(kwd = DroolsSoftKeywords.STATIC)) {
                        // function
                        match(input,
                                DRL6Lexer.ID,
                                kwd,
                                null,
                                DroolsEditorType.KEYWORD);
                        if (state.failed)
                            return null;
                    }

                    // qualifiedIdentifier
                    String target = qualifiedIdentifier();
                    if (state.failed)
                        return null;

                    if (input.LA(1) == DRL6Lexer.DOT && input.LA(2) == DRL6Lexer.STAR) {
                        // .*
                        match(input,
                                DRL6Lexer.DOT,
                                null,
                                null,
                                DroolsEditorType.IDENTIFIER);
                        if (state.failed)
                            return null;
                        match(input,
                                DRL6Lexer.STAR,
                                null,
                                null,
                                DroolsEditorType.IDENTIFIER);
                        if (state.failed)
                            return null;
                        target += ".*";
                    }
                    if (state.backtracking == 0)
                        imp.target(target);
                    return (imp != null) ? imp.getDescr() : null;
                } finally {
                    helper.end(ImportDescrBuilder.class,
                            imp);
                }
            }

        } catch (RecognitionException re) {
            reportError(re);
        }
        return null;
    }

    /* ------------------------------------------------------------------------------------------------
     *                         GLOBAL STATEMENT
     * ------------------------------------------------------------------------------------------------ */

    /**
     * globalStatement := GLOBAL type ID
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public GlobalDescr globalStatement(PackageDescrBuilder pkg) throws RecognitionException {
        GlobalDescrBuilder global = null;
        try {
            global = helper.start(pkg,
                    GlobalDescrBuilder.class,
                    null);

            // 'global'
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.GLOBAL,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            // type
            String type = type();
            if (state.backtracking == 0)
                global.type(type);
            if (state.failed)
                return null;

            // identifier
            Token id = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER_TYPE);
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                global.identifier(id.getText());
                helper.setParaphrasesValue(DroolsParaphraseTypes.GLOBAL,
                        id.getText());
            }

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(GlobalDescrBuilder.class,
                    global);
        }
        return (global != null) ? global.getDescr() : null;
    }

    /* ------------------------------------------------------------------------------------------------
     *                         DECLARE STATEMENT
     * ------------------------------------------------------------------------------------------------ */

    /**
     * declare := DECLARE
     *               | (ENTRY-POINT) => entryPointDeclaration
     *               | (WINDOW) => windowDeclaration
     *               | (TRAIT) => typeDeclaration (trait)
     *               | (ENUM) => enumDeclaration
     *               | typeDeclaration (class)
     *            END
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public BaseDescr declare(PackageDescrBuilder pkg) throws RecognitionException {
        BaseDescr declaration = null;
        try {
            DeclareDescrBuilder declare = helper.start(pkg,
                    DeclareDescrBuilder.class,
                    null);

            // 'declare'
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.DECLARE,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (helper.validateIdentifierKey(DroolsSoftKeywords.ENTRY)) {
                // entry point declaration
                declaration = entryPointDeclaration(declare);
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.WINDOW)) {
                // window declaration
                declaration = windowDeclaration(declare);
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.TRAIT)) {
                // trait type declaration
                // 'trait'
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.TRAIT,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;

                declaration = typeDeclaration(declare, true);
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.ENUM)) {
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.ENUM,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;

                declaration = enumDeclaration(declare);
            } else {
                // class type declaration
                declaration = typeDeclaration(declare, false);
            }

        } catch (RecognitionException re) {
            reportError(re);
        }

        return declaration;
    }

    /**
     * entryPointDeclaration := annotation* ENTRY-POINT stringId END
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public EntryPointDeclarationDescr entryPointDeclaration(DeclareDescrBuilder ddb) throws RecognitionException {
        EntryPointDeclarationDescrBuilder declare = null;
        try {
            declare = helper.start(ddb,
                    EntryPointDeclarationDescrBuilder.class,
                    null);

            setAnnotationsOn(declare);

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.ENTRY,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            match(input,
                    DRL6Lexer.MINUS,
                    null,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.POINT,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            String ep = stringId();
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                declare.entryPointId(ep);
            }

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.END,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(EntryPointDeclarationDescrBuilder.class,
                    declare);
        }
        return (declare != null) ? declare.getDescr() : null;
    }

    /**
     * windowDeclaration := annotation* WINDOW ID lhsPatternBind END
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public WindowDeclarationDescr windowDeclaration(DeclareDescrBuilder ddb) throws RecognitionException {
        WindowDeclarationDescrBuilder declare = null;
        try {
            declare = helper.start(ddb,
                    WindowDeclarationDescrBuilder.class,
                    null);

            setAnnotationsOn(declare);

            String window;

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.WINDOW,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            Token id = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return null;
            window = id.getText();

            if (state.backtracking == 0) {
                declare.name(window);
            }

            lhsPatternBind(declare, false);

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.END,
                    null,
                    DroolsEditorType.KEYWORD);

            if (state.failed)
                return null;

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(WindowDeclarationDescrBuilder.class,
                    declare);
        }
        return (declare != null) ? declare.getDescr() : null;
    }

    /*
     * typeDeclaration := annotation* [ENUM] qualifiedIdentifier
     *                         enumerative+
     *                         field*
     *                     END
     *
     * @return
     * @throws RecognitionException
     */
    public EnumDeclarationDescr enumDeclaration(DeclareDescrBuilder ddb) throws RecognitionException {
        EnumDeclarationDescrBuilder declare = null;
        try {
            declare = helper.start(ddb,
                    EnumDeclarationDescrBuilder.class,
                    null);

            setAnnotationsOn(declare);

            // type may be qualified when adding metadata
            String type = qualifiedIdentifier();
            if (state.failed)
                return null;
            if (state.backtracking == 0)
                declare.name(type);

            while (input.LA(1) == DRL6Lexer.ID) {
                int next = input.LA(2);
                if (next == DRL6Lexer.LEFT_PAREN || next == DRL6Lexer.COMMA || next == DRL6Lexer.SEMICOLON) {
                    enumerative(declare);
                    if (state.failed)
                        return null;
                }

                if (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                } else {
                    match(input,
                            DRL6Lexer.SEMICOLON,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    break;
                }
            }

            //boolean qualified = type.indexOf( '.' ) >= 0;
            while ( //! qualified &&
            input.LA(1) == DRL6Lexer.ID && !helper.validateIdentifierKey(DroolsSoftKeywords.END)) {
                // field*
                field(declare);
                if (state.failed)
                    return null;
            }

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.END,
                    null,
                    DroolsEditorType.KEYWORD);

            if (state.failed)
                return null;

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(TypeDeclarationDescrBuilder.class,
                    declare);
        }
        return (declare != null) ? declare.getDescr() : null;
    }

    /**
     * typeDeclaration := annotation* [TYPE] qualifiedIdentifier (EXTENDS qualifiedIdentifier)?
     *                         field*
     *                     END
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public TypeDeclarationDescr typeDeclaration(DeclareDescrBuilder ddb, boolean isTrait) throws RecognitionException {
        TypeDeclarationDescrBuilder declare = null;
        try {
            declare = helper.start(ddb,
                    TypeDeclarationDescrBuilder.class,
                    null);

            setAnnotationsOn(declare);
            declare.setTrait(isTrait);

            if (helper.validateIdentifierKey(DroolsSoftKeywords.TYPE)) {
                // 'type'
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.TYPE,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;
            }

            // type may be qualified when adding metadata
            String type = qualifiedIdentifier();
            if (state.failed)
                return null;
            if (state.backtracking == 0)
                declare.name(type);

            if (helper.validateIdentifierKey(DroolsSoftKeywords.EXTENDS)) {
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.EXTENDS,
                        null,
                        DroolsEditorType.KEYWORD);
                if (!state.failed) {
                    // Going for type includes generics, which is a no-no (JIRA-3040)
                    String superType = qualifiedIdentifier();
                    declare.superType(superType);

                    while (input.LA(1) == DRL6Lexer.COMMA) {

                        match(input,
                                DRL6Lexer.COMMA,
                                null,
                                null,
                                DroolsEditorType.SYMBOL);

                        superType = qualifiedIdentifier();
                        declare.superType(superType);
                    }
                }
            }

            //boolean qualified = type.indexOf( '.' ) >= 0;
            while ( (input.LA(1) == DRL6Lexer.ID || input.LA(1) == DRL6Lexer.AT) &&
                    !helper.validateIdentifierKey(DroolsSoftKeywords.END)) {
                // field*
                field(declare);
                if (state.failed)
                    return null;
            }

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.END,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(TypeDeclarationDescrBuilder.class,
                    declare);
        }
        return (declare != null) ? declare.getDescr() : null;
    }

    /**
     * enumerative := ID ( LEFT_PAREN expression (COMMA expression)* RIGHT_PAREN )?
     */
    private void enumerative(EnumDeclarationDescrBuilder declare) {
        EnumLiteralDescrBuilder literal = null;
        String lit = null;
        try {
            Token enumLit = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER);
            lit = enumLit.getText();
            if (state.failed)
                return;
        } catch (RecognitionException re) {
            reportError(re);
        }

        try {
            literal = helper.start(declare,
                    EnumLiteralDescrBuilder.class,
                    lit);

            if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {

                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;

                boolean more;

                do {
                    int first = input.index();
                    exprParser.conditionalExpression();
                    if (state.failed)
                        return;
                    if (state.backtracking == 0 && input.index() > first) {
                        // expression consumed something
                        String arg = input.toString(first,
                                input.LT(-1).getTokenIndex());
                        literal.constructorArg(arg);
                    }
                    more = input.LA(1) == DRL6Lexer.COMMA;
                    if (more) {
                        match(input,
                                DRL6Lexer.COMMA,
                                null,
                                null,
                                DroolsEditorType.SYMBOL);

                    }

                } while (more);

                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;

            }

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(FieldDescrBuilder.class,
                    literal);
        }
    }

    /**
     * field := annotation* label fieldType (EQUALS_ASSIGN conditionalExpression)? SEMICOLON?
     */
    private void field(AbstractClassTypeDeclarationBuilder declare) {
        annotations();

        FieldDescrBuilder field = null;
        String fname = null;
        try {
            fname = label(DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return;
        } catch (RecognitionException re) {
            reportError(re);
        }

        try {
            field = helper.start(declare,
                    FieldDescrBuilder.class,
                    fname);

            setAnnotationsOn(field);

            // type
            String type = type();
            if (state.failed)
                return;
            if (state.backtracking == 0)
                field.type(type);

            if (input.LA(1) == DRL6Lexer.EQUALS_ASSIGN) {
                // EQUALS_ASSIGN
                match(input,
                        DRL6Lexer.EQUALS_ASSIGN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;

                int first = input.index();
                exprParser.conditionalExpression();
                if (state.failed)
                    return;
                if (state.backtracking == 0 && input.index() > first) {
                    // expression consumed something
                    String value = input.toString(first,
                            input.LT(-1).getTokenIndex());
                    field.initialValue(value);
                }
            }

            if (input.LA(1) == DRL6Lexer.SEMICOLON) {
                match(input,
                        DRL6Lexer.SEMICOLON,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;
            }

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(FieldDescrBuilder.class,
                    field);
        }
    }

    /* ------------------------------------------------------------------------------------------------
     *                         FUNCTION STATEMENT
     * ------------------------------------------------------------------------------------------------ */

    /**
     * function := FUNCTION type? ID parameters(typed) chunk_{_}
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public FunctionDescr function(PackageDescrBuilder pkg) throws RecognitionException {
        FunctionDescrBuilder function = null;
        try {
            function = helper.start(pkg,
                    FunctionDescrBuilder.class,
                    null);

            // 'function'
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.FUNCTION,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (input.LA(1) != DRL6Lexer.ID || input.LA(2) != DRL6Lexer.LEFT_PAREN) {
                // type
                String type = type();
                if (state.failed)
                    return null;
                if (state.backtracking == 0)
                    function.returnType(type);
            }

            // name
            Token id = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                function.name(id.getText());
                helper.setParaphrasesValue(DroolsParaphraseTypes.FUNCTION,
                        "\"" + id.getText() + "\"");
            }

            // arguments
            parameters(function,
                    true);
            if (state.failed)
                return null;

            // body
            String body = chunk(DRL6Lexer.LEFT_CURLY,
                    DRL6Lexer.RIGHT_CURLY,
                    -1);
            if (state.failed)
                return null;
            if (state.backtracking == 0)
                function.body(body);

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(FunctionDescrBuilder.class,
                    function);
        }
        return (function != null) ? function.getDescr() : null;
    }

    /**
     * parameters := LEFT_PAREN ( parameter ( COMMA parameter )* )? RIGHT_PAREN
     * @param statement
     * @param requiresType
     * @throws org.antlr.runtime.RecognitionException
     */
    private void parameters(ParameterSupportBuilder<?> statement,
            boolean requiresType) throws RecognitionException {
        match(input,
                DRL6Lexer.LEFT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return;

        if (input.LA(1) != DRL6Lexer.RIGHT_PAREN) {
            parameter(statement,
                    requiresType);
            if (state.failed)
                return;

            while (input.LA(1) == DRL6Lexer.COMMA) {
                match(input,
                        DRL6Lexer.COMMA,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;

                parameter(statement,
                        requiresType);
                if (state.failed)
                    return;
            }
        }

        match(input,
                DRL6Lexer.RIGHT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return;
    }

    /**
     * parameter := ({requiresType}?=>type)? ID (LEFT_SQUARE RIGHT_SQUARE)*
     * @param statement
     * @param requiresType
     * @throws org.antlr.runtime.RecognitionException
     */
    private void parameter(ParameterSupportBuilder<?> statement,
            boolean requiresType) throws RecognitionException {
        String type = "Object";
        if (requiresType) {
            type = type();
            if (state.failed)
                return;
        }

        int start = input.index();
        match(input,
                DRL6Lexer.ID,
                null,
                null,
                DroolsEditorType.IDENTIFIER);
        if (state.failed)
            return;

        while (input.LA(1) == DRL6Lexer.LEFT_SQUARE) {
            match(input,
                    DRL6Lexer.LEFT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;

            match(input,
                    DRL6Lexer.RIGHT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;
        }
        int end = input.LT(-1).getTokenIndex();

        if (state.backtracking == 0)
            statement.parameter(type,
                    input.toString(start,
                            end));
    }

    /* ------------------------------------------------------------------------------------------------
     *                         QUERY STATEMENT
     * ------------------------------------------------------------------------------------------------ */

    /**
     * query := annotation* QUERY stringId parameters? lhsExpression END
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public RuleDescr query(PackageDescrBuilder pkg) throws RecognitionException {
        QueryDescrBuilder query = null;
        try {
            query = helper.start(pkg,
                    QueryDescrBuilder.class,
                    null);

            setAnnotationsOn(query);

            // 'query'
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.QUERY,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (helper.validateIdentifierKey(DroolsSoftKeywords.WHEN) ||
                    helper.validateIdentifierKey(DroolsSoftKeywords.THEN) ||
                    helper.validateIdentifierKey(DroolsSoftKeywords.END)) {
                failMissingTokenException();
                return null; // in case it is backtracking
            }

            String name = stringId();
            if (state.backtracking == 0)
                query.name(name);
            if (state.failed)
                return null;

            if (state.backtracking == 0) {
                helper.emit(Location.LOCATION_RULE_HEADER);
            }

            if (speculateParameters(true)) {
                // parameters
                parameters(query,
                        true);
                if (state.failed)
                    return null;
                if (state.backtracking == 0) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
                }
            } else if (speculateParameters(false)) {
                // parameters
                parameters(query,
                        false);
                if (state.failed)
                    return null;
                if (state.backtracking == 0) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
                }
            }

            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
            }
            if (input.LA(1) != DRL6Lexer.EOF) {
                lhsExpression(query != null ? query.lhs() : null);
            }

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.END,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            helper.emit(Location.LOCATION_RHS);

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(QueryDescrBuilder.class,
                    query);
        }
        return (query != null) ? query.getDescr() : null;
    }

    private boolean speculateParameters(boolean requiresType) {
        state.backtracking++;
        int start = input.mark();
        try {
            parameters(null,
                    requiresType); // can never throw exception
        } catch (RecognitionException re) {
            System.err.println("impossible: " + re);
            LOG.error("Exception", re);
        }
        boolean success = !state.failed;
        input.rewind(start);
        state.backtracking--;
        state.failed = false;
        return success;
    }

    /* ------------------------------------------------------------------------------------------------
     *                         RULE STATEMENT
     * ------------------------------------------------------------------------------------------------ */

    /**
     * rule := annotation* RULE stringId (EXTENDS stringId)? attributes? lhs? rhs END
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public RuleDescr rule(PackageDescrBuilder pkg) throws RecognitionException {
        RuleDescrBuilder rule = null;
        try {
            rule = helper.start(pkg,
                    RuleDescrBuilder.class,
                    null);

            setAnnotationsOn(rule);

            // 'rule'
            match(input,
                  DRL6Lexer.ID,
                  DroolsSoftKeywords.RULE,
                  null,
                  DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (helper.validateIdentifierKey(DroolsSoftKeywords.WHEN) ||
                    helper.validateIdentifierKey(DroolsSoftKeywords.THEN) ||
                    helper.validateIdentifierKey(DroolsSoftKeywords.END)) {
                failMissingTokenException();
                return null; // in case it is backtracking
            }

            String name = stringId();
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                rule.name(name);
                helper.setParaphrasesValue(DroolsParaphraseTypes.RULE,
                        "\"" + name + "\"");
                helper.emit(Location.LOCATION_RULE_HEADER);
            }

            if (helper.validateIdentifierKey(DroolsSoftKeywords.EXTENDS)) {
                // 'extends'
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.EXTENDS,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;

                String parent = stringId();
                if (state.backtracking == 0)
                    rule.extendsRule(parent);
                if (state.failed)
                    return null;
            }

            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_RULE_HEADER);
            }

            attributes(rule);

            if (helper.validateIdentifierKey(DroolsSoftKeywords.WHEN)) {
                lhs(rule);
            } else {
                // creates an empty LHS
                rule.lhs();
            }

            rhs(rule);

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.END,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            helper.end(RuleDescrBuilder.class,
                    rule);
        }
        return (rule != null) ? rule.getDescr() : null;
    }

    /**
     * stringId := ( ID | STRING )
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    String stringId() throws RecognitionException {
        if (input.LA(1) == DRL6Lexer.ID) {
            Token id = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return null;
            return id.getText();
        } else if (input.LA(1) == DRL6Lexer.STRING) {
            Token id = match(input,
                    DRL6Lexer.STRING,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return null;
            return StringUtils.unescapeJava(safeStripStringDelimiters(id.getText()));
        } else {
            throw new MismatchedTokenException(DRL6Lexer.ID,
                    input);
        }
    }

    /**
     * attributes := (ATTRIBUTES COLON?)? [ attribute ( COMMA? attribute )* ]
     * @param rule
     * @throws org.antlr.runtime.RecognitionException
     */
    void attributes( RuleDescrBuilder rule ) throws RecognitionException {
        if (helper.validateIdentifierKey(DroolsSoftKeywords.ATTRIBUTES)) {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.ATTRIBUTES,
                    null,
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return;

            if (input.LA(1) == DRL6Lexer.COLON) {
                match(input,
                        DRL6Lexer.COLON,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;
            }
        }

        if (helper.validateAttribute(1)) {
            attribute(rule);
            if (state.failed)
                return;

            while (input.LA(1) == DRL6Lexer.COMMA || helper.validateAttribute(1)) {
                if (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return;
                }
                attribute(rule);
                if (state.failed)
                    return;
            }
        }
    }

    /**
     * attribute :=
     *       salience
     *   |   enabled
     *   |   ( NO-LOOP
     *       | AUTO-FOCUS
     *       | LOCK-ON-ACTIVE
     *       | REFRACT
     *       | DIRECT
     *       ) BOOLEAN?
     *   |   ( AGENDA-GROUP
     *       | ACTIVATION-GROUP
     *       | RULEFLOW-GROUP
     *       | DATE-EFFECTIVE
     *       | DATE-EXPIRES
     *       | DIALECT
     *       ) STRING
     *   |   CALENDARS STRING (COMMA STRING)*
     *   |   TIMER ( DECIMAL | chunk_(_) )
     *   |   DURATION ( DECIMAL | chunk_(_) )
     *
     * The above syntax is not quite how this is parsed, because the soft keyword
     * is determined by look-ahead and passed on to one of the x-Attribute methods
     * (booleanAttribute, stringAttribute, stringListAttribute, intOrChunkAttribute)
     * which will actually gobble the tokens.
     *
     * @return
     */
    public AttributeDescr attribute(AttributeSupportBuilder<?> as) {
        AttributeDescr attribute = null;
        try {
            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_RULE_HEADER_KEYWORD);
            }
            if (helper.validateIdentifierKey(DroolsSoftKeywords.SALIENCE)) {
                attribute = salience(as);
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.ENABLED)) {
                attribute = enabled(as);
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.NO) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.LOOP)) {
                attribute = booleanAttribute(as,
                        new String[]{DroolsSoftKeywords.NO, "-", DroolsSoftKeywords.LOOP});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.AUTO) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.FOCUS)) {
                attribute = booleanAttribute(as,
                        new String[]{DroolsSoftKeywords.AUTO, "-", DroolsSoftKeywords.FOCUS});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.LOCK) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.ON) &&
                    helper.validateLT(4,
                            "-") &&
                    helper.validateLT(5,
                            DroolsSoftKeywords.ACTIVE)) {
                attribute = booleanAttribute(as,
                        new String[]{DroolsSoftKeywords.LOCK, "-", DroolsSoftKeywords.ON, "-", DroolsSoftKeywords.ACTIVE});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.REFRACT)) {
                attribute = booleanAttribute(as,
                        new String[]{DroolsSoftKeywords.REFRACT});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DIRECT)) {
                attribute = booleanAttribute(as,
                        new String[]{DroolsSoftKeywords.DIRECT});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.AGENDA) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.GROUP)) {
                attribute = stringAttribute(as,
                        new String[]{DroolsSoftKeywords.AGENDA, "-", DroolsSoftKeywords.GROUP});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.ACTIVATION) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.GROUP)) {
                attribute = stringAttribute(as,
                        new String[]{DroolsSoftKeywords.ACTIVATION, "-", DroolsSoftKeywords.GROUP});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.RULEFLOW) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.GROUP)) {
                attribute = stringAttribute(as,
                        new String[]{DroolsSoftKeywords.RULEFLOW, "-", DroolsSoftKeywords.GROUP});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DATE) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.EFFECTIVE)) {
                attribute = stringAttribute(as,
                        new String[]{DroolsSoftKeywords.DATE, "-", DroolsSoftKeywords.EFFECTIVE});
                if (attribute != null) {
                    attribute.setType(AttributeDescr.Type.DATE);
                } else {
                    throw new RecognitionException();
                }
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DATE) &&
                    helper.validateLT(2,
                            "-") &&
                    helper.validateLT(3,
                            DroolsSoftKeywords.EXPIRES)) {
                attribute = stringAttribute(as,
                        new String[]{DroolsSoftKeywords.DATE, "-", DroolsSoftKeywords.EXPIRES});
                if (attribute != null) {
                    attribute.setType(AttributeDescr.Type.DATE);
                } else {
                    throw new RecognitionException();
                }
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DIALECT)) {
                attribute = stringAttribute(as,
                        new String[]{DroolsSoftKeywords.DIALECT});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.CALENDARS)) {
                attribute = stringListAttribute(as,
                        new String[]{DroolsSoftKeywords.CALENDARS});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.TIMER)) {
                attribute = intOrChunkAttribute(as,
                        new String[]{DroolsSoftKeywords.TIMER});
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DURATION)) {
                attribute = intOrChunkAttribute(as,
                        new String[]{DroolsSoftKeywords.DURATION});
            }
            if (state.backtracking == 0) {
                helper.emit(Location.LOCATION_RULE_HEADER);
            }
        } catch (RecognitionException re) {
            reportError(re);
        }
        return attribute;
    }

    /**
     * salience := SALIENCE conditionalExpression
     * @throws org.antlr.runtime.RecognitionException
     */
    private AttributeDescr salience(AttributeSupportBuilder<?> as) throws RecognitionException {
        AttributeDescrBuilder<?> attribute = null;
        try {
            // 'salience'
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.SALIENCE,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                attribute = helper.start((DescrBuilder<?, ?>) as,
                        AttributeDescrBuilder.class,
                        DroolsSoftKeywords.SALIENCE);
            }

            boolean hasParen = input.LA(1) == DRL6Lexer.LEFT_PAREN;
            int first = input.index();
            if (hasParen) {
                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            }

            String value = conditionalExpression();
            if (state.failed)
                return null;

            if (hasParen) {
                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            }
            if (state.backtracking == 0) {
                if (attribute != null) {
                    if (hasParen) {
                        value = input.toString(first,
                                               input.LT(-1).getTokenIndex());
                    }
                    attribute.value(value);
                    attribute.type(AttributeDescr.Type.EXPRESSION);
                }
            }

        } finally {
            if (attribute != null) {
                helper.end(AttributeDescrBuilder.class,
                        attribute);
            }
        }
        return attribute != null ? attribute.getDescr() : null;
    }

    /**
     * enabled := ENABLED conditionalExpression
     * @throws org.antlr.runtime.RecognitionException
     */
    private AttributeDescr enabled(AttributeSupportBuilder<?> as) throws RecognitionException {
        AttributeDescrBuilder<?> attribute = null;
        try {
            // 'enabled'
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.ENABLED,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                attribute = helper.start((DescrBuilder<?, ?>) as,
                        AttributeDescrBuilder.class,
                        DroolsSoftKeywords.ENABLED);
            }

            boolean hasParen = input.LA(1) == DRL6Lexer.LEFT_PAREN;
            int first = input.index();
            if (hasParen) {
                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            }

            String value = conditionalExpression();
            if (state.failed)
                return null;

            if (hasParen) {
                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            }
            if (state.backtracking == 0) {
                if (attribute != null) {
                    if (hasParen) {
                        value = input.toString(first,
                                               input.LT(-1).getTokenIndex());
                    }
                    attribute.value(value);
                    attribute.type(AttributeDescr.Type.EXPRESSION);
                }
            }

        } finally {
            if (attribute != null) {
                helper.end(AttributeDescrBuilder.class,
                        attribute);
            }
        }
        return attribute != null ? attribute.getDescr() : null;
    }

    /**
     * booleanAttribute := attributeKey (BOOLEAN)?
     * @param key
     * @throws org.antlr.runtime.RecognitionException
     */
    private AttributeDescr booleanAttribute(AttributeSupportBuilder<?> as,
            String[] key) throws RecognitionException {
        AttributeDescrBuilder<?> attribute = null;
        try {
            StringBuilder builder = new StringBuilder();
            for (String k : key) {
                if ("-".equals(k)) {
                    match(input,
                            DRL6Lexer.MINUS,
                            k,
                            null,
                            DroolsEditorType.KEYWORD); // part of the keyword
                    if (state.failed)
                        return null;
                } else {
                    match(input,
                            DRL6Lexer.ID,
                            k,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;
                }
                builder.append(k);
            }
            if (state.backtracking == 0) {
                attribute = helper.start((DescrBuilder<?, ?>) as,
                        AttributeDescrBuilder.class,
                        builder.toString());
            }

            String value = "true";
            if (input.LA(1) == DRL6Lexer.BOOL) {
                Token bool = match(input,
                        DRL6Lexer.BOOL,
                        null,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;
                value = bool.getText();
            }
            if (state.backtracking == 0) {
                if (attribute != null) {
                    attribute.value(value);
                    attribute.type(AttributeDescr.Type.BOOLEAN);
                }
            }
        } finally {
            if (attribute != null) {
                helper.end(AttributeDescrBuilder.class,
                        attribute);
            }
        }
        return attribute != null ? attribute.getDescr() : null;
    }

    /**
     * stringAttribute := attributeKey STRING
     * @param key
     * @throws org.antlr.runtime.RecognitionException
     */
    private AttributeDescr stringAttribute(AttributeSupportBuilder<?> as,
            String[] key) throws RecognitionException {
        AttributeDescrBuilder<?> attribute = null;
        try {
            StringBuilder builder = new StringBuilder();
            for (String k : key) {
                if ("-".equals(k)) {
                    match(input,
                            DRL6Lexer.MINUS,
                            k,
                            null,
                            DroolsEditorType.KEYWORD); // part of the keyword
                    if (state.failed)
                        return null;
                } else {
                    match(input,
                            DRL6Lexer.ID,
                            k,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;
                }
                builder.append(k);
            }
            if (state.backtracking == 0) {
                attribute = helper.start((DescrBuilder<?, ?>) as,
                        AttributeDescrBuilder.class,
                        builder.toString());
            }

            Token value = match(input,
                    DRL6Lexer.STRING,
                    null,
                    null,
                    DroolsEditorType.STRING_CONST);
            if (state.failed)
                return null;
            if (state.backtracking == 0) {
                if (attribute != null) {
                    attribute.value(StringUtils.unescapeJava(safeStripStringDelimiters(value.getText())));
                    attribute.type(AttributeDescr.Type.STRING);
                }
            }
        } finally {
            if (attribute != null) {
                helper.end(AttributeDescrBuilder.class,
                        attribute);
            }
        }
        return attribute != null ? attribute.getDescr() : null;
    }

    /**
     * stringListAttribute := attributeKey STRING (COMMA STRING)*
     * @param key
     * @throws org.antlr.runtime.RecognitionException
     */
    private AttributeDescr stringListAttribute(AttributeSupportBuilder<?> as,
            String[] key) throws RecognitionException {
        AttributeDescrBuilder<?> attribute = null;
        try {
            StringBuilder builder = new StringBuilder();
            for (String k : key) {
                if ("-".equals(k)) {
                    match(input,
                            DRL6Lexer.MINUS,
                            k,
                            null,
                            DroolsEditorType.KEYWORD); // part of the keyword
                    if (state.failed)
                        return null;
                } else {
                    match(input,
                            DRL6Lexer.ID,
                            k,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;
                }
                builder.append(k);
            }
            if (state.backtracking == 0) {
                attribute = helper.start((DescrBuilder<?, ?>) as,
                        AttributeDescrBuilder.class,
                        builder.toString());
            }

            builder = new StringBuilder();
            builder.append("[ ");
            Token value = match(input,
                    DRL6Lexer.STRING,
                    null,
                    null,
                    DroolsEditorType.STRING_CONST);
            if (state.failed)
                return null;
            builder.append(value.getText());

            while (input.LA(1) == DRL6Lexer.COMMA) {
                match(input,
                        DRL6Lexer.COMMA,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
                builder.append(", ");
                value = match(input,
                        DRL6Lexer.STRING,
                        null,
                        null,
                        DroolsEditorType.STRING_CONST);
                if (state.failed)
                    return null;
                builder.append(value.getText());
            }
            builder.append(" ]");
            if (state.backtracking == 0) {
                if (attribute != null) {
                    attribute.value(builder.toString());
                    attribute.type(AttributeDescr.Type.LIST);
                }
            }
        } finally {
            if (attribute != null) {
                helper.end(AttributeDescrBuilder.class,
                        attribute);
            }
        }
        return attribute != null ? attribute.getDescr() : null;
    }

    /**
     * intOrChunkAttribute := attributeKey ( DECIMAL | chunk_(_) )
     * @param key
     * @throws org.antlr.runtime.RecognitionException
     */
    private AttributeDescr intOrChunkAttribute(AttributeSupportBuilder<?> as,
            String[] key) throws RecognitionException {
        AttributeDescrBuilder<?> attribute = null;
        try {
            StringBuilder builder = new StringBuilder();
            for (String k : key) {
                if ("-".equals(k)) {
                    match(input,
                            DRL6Lexer.MINUS,
                            k,
                            null,
                            DroolsEditorType.KEYWORD); // part of the keyword
                    if (state.failed)
                        return null;
                } else {
                    match(input,
                            DRL6Lexer.ID,
                            k,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return null;
                }
                builder.append(k);
            }
            if (state.backtracking == 0) {
                attribute = helper.start((DescrBuilder<?, ?>) as,
                        AttributeDescrBuilder.class,
                        builder.toString());
            }

            if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {
                String value = chunk(DRL6Lexer.LEFT_PAREN,
                        DRL6Lexer.RIGHT_PAREN,
                        -1);
                if (state.failed)
                    return null;
                if (state.backtracking == 0) {
                    if (attribute != null) {
                        attribute.value(safeStripDelimiters(value,
                                                            "(",
                                                            ")"));
                        attribute.type(AttributeDescr.Type.EXPRESSION);
                    }
                }
            } else {
                String value = "";
                if (input.LA(1) == DRL6Lexer.PLUS) {
                    Token sign = match(input,
                            DRL6Lexer.PLUS,
                            null,
                            null,
                            DroolsEditorType.NUMERIC_CONST);
                    if (state.failed)
                        return null;
                    value += sign.getText();
                } else if (input.LA(1) == DRL6Lexer.MINUS) {
                    Token sign = match(input,
                            DRL6Lexer.MINUS,
                            null,
                            null,
                            DroolsEditorType.NUMERIC_CONST);
                    if (state.failed)
                        return null;
                    value += sign.getText();
                }
                Token nbr = match(input,
                        DRL6Lexer.DECIMAL,
                        null,
                        null,
                        DroolsEditorType.NUMERIC_CONST);
                if (state.failed)
                    return null;
                value += nbr.getText();
                if (state.backtracking == 0) {
                    if (attribute != null) {
                        attribute.value(value);
                        attribute.type(AttributeDescr.Type.NUMBER);
                    }
                }
            }
        } finally {
            if (attribute != null) {
                helper.end(AttributeDescrBuilder.class,
                        attribute);
            }
        }
        return attribute != null ? attribute.getDescr() : null;
    }

    /**
     * lhs := WHEN COLON? lhsExpression
     * @param rule
     * @throws org.antlr.runtime.RecognitionException
     */
    void lhs( RuleDescrBuilder rule ) throws RecognitionException {
        match(input,
                DRL6Lexer.ID,
                DroolsSoftKeywords.WHEN,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        if (input.LA(1) == DRL6Lexer.COLON) {
            match(input,
                    DRL6Lexer.COLON,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;
        }

        lhsExpression(rule != null ? rule.lhs() : null);

    }

    /**
     * lhsExpression := lhsOr*
     *
     * @param lhs
     * @throws org.antlr.runtime.RecognitionException
     */
    private void lhsExpression(CEDescrBuilder<?, AndDescr> lhs) throws RecognitionException {
        helper.start(lhs,
                CEDescrBuilder.class,
                null);
        if (state.backtracking == 0) {
            helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
        }
        try {
            while (input.LA(1) != DRL6Lexer.EOF &&
                    !helper.validateIdentifierKey(DroolsSoftKeywords.THEN) &&
                    !helper.validateIdentifierKey(DroolsSoftKeywords.END)) {
                if (state.backtracking == 0) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
                }
                lhsOr(lhs,
                        true);
                if (lhs.getDescr() != null && lhs.getDescr() instanceof ConditionalElementDescr) {
                    ConditionalElementDescr root = lhs.getDescr();
                    BaseDescr[] descrs = root.getDescrs().toArray(new BaseDescr[root.getDescrs().size()]);
                    root.getDescrs().clear();
                    for (int i = 0; i < descrs.length; i++) {
                        root.addOrMerge(descrs[i]);
                    }
                }
                if (state.failed)
                    return;
            }
        } finally {
            helper.end(CEDescrBuilder.class,
                    lhs);
        }
    }

    /**
     * lhsOr := LEFT_PAREN OR lhsAnd+ RIGHT_PAREN
     *        | lhsAnd (OR lhsAnd)*
     *
     * @param ce
     * @param allowOr
     * @throws org.antlr.runtime.RecognitionException
     */
    private BaseDescr lhsOr(final CEDescrBuilder<?, ?> ce,
            boolean allowOr) throws RecognitionException {
        BaseDescr result = null;
        if (allowOr && input.LA(1) == DRL6Lexer.LEFT_PAREN && helper.validateLT(2,
                DroolsSoftKeywords.OR)) {
            // prefixed OR
            CEDescrBuilder<?, OrDescr> or = null;
            if (state.backtracking == 0) {
                or = ce.or();
                result = or.getDescr();
                helper.start(or,
                        CEDescrBuilder.class,
                        null);
            }
            try {
                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;

                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.OR,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;

                if (state.backtracking == 0) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION_AND_OR);
                }
                while (input.LA(1) != DRL6Lexer.RIGHT_PAREN) {
                    lhsAnd(or,
                            allowOr);
                    if (state.failed)
                        return null;
                }

                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            } finally {
                if (state.backtracking == 0) {
                    helper.end(CEDescrBuilder.class,
                            or);
                }
            }
        } else {
            // infix OR

            // create an OR anyway, as if it is not an OR we remove it later
            CEDescrBuilder<?, OrDescr> or = null;
            if (state.backtracking == 0) {
                or = ce.or();
                result = or.getDescr();
                helper.start(or,
                        CEDescrBuilder.class,
                        null);
            }
            try {
                lhsAnd(or,
                        allowOr);
                if (state.failed)
                    return null;

                if (allowOr &&
                        (helper.validateIdentifierKey(DroolsSoftKeywords.OR)
                        ||
                        input.LA(1) == DRL6Lexer.DOUBLE_PIPE)) {
                    while (helper.validateIdentifierKey(DroolsSoftKeywords.OR) ||
                            input.LA(1) == DRL6Lexer.DOUBLE_PIPE) {
                        if (input.LA(1) == DRL6Lexer.DOUBLE_PIPE) {
                            match(input,
                                    DRL6Lexer.DOUBLE_PIPE,
                                    null,
                                    null,
                                    DroolsEditorType.SYMBOL);
                        } else {
                            match(input,
                                    DRL6Lexer.ID,
                                    DroolsSoftKeywords.OR,
                                    null,
                                    DroolsEditorType.KEYWORD);
                        }
                        if (state.failed)
                            return null;

                        if (state.backtracking == 0) {
                            helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION_AND_OR);
                        }

                        lhsAnd(or,
                                allowOr);
                        if (state.failed)
                            return null;
                    }
                } else if (allowOr) {
                    if (state.backtracking == 0) {
                        // if no OR present, then remove it and add children to parent
                        ((ConditionalElementDescr) ce.getDescr()).getDescrs().remove(or.getDescr());
                        for (BaseDescr base : or.getDescr().getDescrs()) {
                            ((ConditionalElementDescr) ce.getDescr()).addDescr(base);
                        }
                        result = ce.getDescr();
                    }
                }
            } finally {
                if (state.backtracking == 0) {
                    helper.end(CEDescrBuilder.class,
                            or);
                }
            }
        }
        return result;
    }

    /**
     * lhsAnd := LEFT_PAREN AND lhsUnary+ RIGHT_PAREN
     *         | lhsUnary (AND lhsUnary)*
     *
     * @param ce
     * @throws org.antlr.runtime.RecognitionException
     */
    private BaseDescr lhsAnd(final CEDescrBuilder<?, ?> ce,
            boolean allowOr) throws RecognitionException {
        BaseDescr result = null;
        if (input.LA(1) == DRL6Lexer.LEFT_PAREN && helper.validateLT(2,
                DroolsSoftKeywords.AND)) {
            // prefixed AND
            CEDescrBuilder<?, AndDescr> and = null;
            if (state.backtracking == 0) {
                and = ce.and();
                result = ce.getDescr();
                helper.start(and,
                        CEDescrBuilder.class,
                        null);
            }
            try {
                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;

                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.AND,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;

                if (state.backtracking == 0) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION_AND_OR);
                }
                while (input.LA(1) != DRL6Lexer.RIGHT_PAREN) {
                    lhsUnary(and,
                            allowOr);
                    if (state.failed)
                        return null;
                }

                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            } finally {
                if (state.backtracking == 0) {
                    helper.end(CEDescrBuilder.class,
                            and);
                }
            }
        } else {
            // infix AND

            // create an AND anyway, since if it is not an AND we remove it later
            CEDescrBuilder<?, AndDescr> and = null;
            if (state.backtracking == 0) {
                and = ce.and();
                result = and.getDescr();
                helper.start(and,
                        CEDescrBuilder.class,
                        null);
            }
            try {
                lhsUnary(and,
                        allowOr);
                if (state.failed)
                    return null;

                if (helper.validateIdentifierKey(DroolsSoftKeywords.AND) ||
                        input.LA(1) == DRL6Lexer.DOUBLE_AMPER) {
                    while (helper.validateIdentifierKey(DroolsSoftKeywords.AND) ||
                            input.LA(1) == DRL6Lexer.DOUBLE_AMPER) {
                        if (input.LA(1) == DRL6Lexer.DOUBLE_AMPER) {
                            match(input,
                                    DRL6Lexer.DOUBLE_AMPER,
                                    null,
                                    null,
                                    DroolsEditorType.SYMBOL);
                        } else {
                            match(input,
                                    DRL6Lexer.ID,
                                    DroolsSoftKeywords.AND,
                                    null,
                                    DroolsEditorType.KEYWORD);
                        }
                        if (state.failed)
                            return null;

                        if (state.backtracking == 0) {
                            helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION_AND_OR);
                        }
                        lhsUnary(and,
                                allowOr);
                        if (state.failed)
                            return null;
                    }
                } else {
                    if (state.backtracking == 0 && and.getDescr().getDescrs().size() < 2) {
                        // if no AND present, then remove it and add children to parent
                        ((ConditionalElementDescr) ce.getDescr()).getDescrs().remove(and.getDescr());
                        for (BaseDescr base : and.getDescr().getDescrs()) {
                            ((ConditionalElementDescr) ce.getDescr()).addDescr(base);
                        }
                        result = ce.getDescr();
                    }
                }
            } finally {
                if (state.backtracking == 0) {
                    helper.end(CEDescrBuilder.class,
                            and);
                }
            }
        }
        return result;
    }

    /**
     * lhsUnary :=
     *           annotation*
     *           ( lhsExists namedConsequence?
     *           | lhsNot namedConsequence?
     *           | lhsEval consequenceInvocation*
     *           | lhsForall
     *           | lhsAccumulate
     *           | LEFT_PAREN lhsOr RIGHT_PAREN namedConsequence?
     *           | lhsPatternBind consequenceInvocation*
     *           )
     *           SEMICOLON?
     *
     * @param ce
     * @return
     */
    private BaseDescr lhsUnary(final CEDescrBuilder<?, ?> ce,
            boolean allowOr) throws RecognitionException {
        annotations();
        BaseDescr result = null;
        if (helper.validateIdentifierKey(DroolsSoftKeywords.EXISTS)) {
            result = lhsExists(ce,
                    allowOr);
            if (helper.validateIdentifierKey(DroolsSoftKeywords.DO)) {
                namedConsequence(ce, null);
            }
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.NOT)) {
            result = lhsNot(ce,
                    allowOr);
            if (helper.validateIdentifierKey(DroolsSoftKeywords.DO)) {
                namedConsequence(ce, null);
            }
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.EVAL)) {
            result = lhsEval(ce);
            for (BaseDescr i = consequenceInvocation(ce); i != null; i = consequenceInvocation(ce))
                ;
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.FORALL)) {
            result = lhsForall(ce);
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.ACCUMULATE) || helper.validateIdentifierKey(DroolsSoftKeywords.ACC)) {
            result = lhsAccumulate(ce);
        } else if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {
            // the order here is very important: this if branch must come before the lhsPatternBind below
            result = lhsParen(ce,
                    allowOr);
            if (helper.validateIdentifierKey(DroolsSoftKeywords.DO)) {
                namedConsequence(ce, null);
            }
        } else if (input.LA(1) == DRL6Lexer.ID || input.LA(1) == DRL6Lexer.QUESTION) {
            result = lhsPatternBind(ce,
                    allowOr);
            for (BaseDescr i = consequenceInvocation(ce); i != null; i = consequenceInvocation(ce))
                ;
        } else {
            failMismatchedTokenException();
        }
        if (input.LA(1) == DRL6Lexer.SEMICOLON) {
            match(input,
                    DRL6Lexer.SEMICOLON,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;
        }

        setAnnotationsOn(result);
        return result;
    }

    /**
     * consequenceInvocation := conditionalBranch | namedConsequence
     *
     * @param ce
     * @return
     */
    private BaseDescr consequenceInvocation(CEDescrBuilder<?, ?> ce) throws RecognitionException {
        BaseDescr result = null;
        if (helper.validateIdentifierKey(DroolsSoftKeywords.IF)) {
            result = conditionalBranch(ce, null);
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.DO)) {
            result = namedConsequence(ce, null);
        }
        return result;
    }

    /**
     * conditionalBranch := IF LEFT_PAREN conditionalExpression RIGHT_PAREN
     *                      ( namedConsequence | breakingNamedConsequence )
     *                      ( ELSE ( namedConsequence | breakingNamedConsequence | conditionalBranch ) )?
     */
    private BaseDescr conditionalBranch(CEDescrBuilder<?, ?> ce, ConditionalBranchDescrBuilder<?> conditionalBranch) throws RecognitionException {
        if (conditionalBranch == null) {
            conditionalBranch = helper.start(ce,
                                             ConditionalBranchDescrBuilder.class,
                                             null);
        }

        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.IF,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            EvalDescrBuilder<?> eval = conditionalBranch.condition();
            if (!parseEvalExpression(eval))
                return null;

            if (helper.validateIdentifierKey(DroolsSoftKeywords.DO)) {
                if (namedConsequence(null, conditionalBranch.consequence()) == null)
                    return null;
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.BREAK)) {
                if (breakingNamedConsequence(null, conditionalBranch.consequence()) == null)
                    return null;
            } else {
                return null;
            }

            if (helper.validateIdentifierKey(DroolsSoftKeywords.ELSE)) {
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.ELSE,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return null;

                ConditionalBranchDescrBuilder<?> elseBranch = conditionalBranch.otherwise();
                if (helper.validateIdentifierKey(DroolsSoftKeywords.DO)) {
                    if (namedConsequence(null, elseBranch.consequence()) == null)
                        return null;
                } else if (helper.validateIdentifierKey(DroolsSoftKeywords.BREAK)) {
                    if (breakingNamedConsequence(null, elseBranch.consequence()) == null)
                        return null;
                } else if (helper.validateIdentifierKey(DroolsSoftKeywords.IF)) {
                    if (conditionalBranch(null, elseBranch) == null)
                        return null;
                } else {
                    return null;
                }
            }
        } finally {
            helper.end(ConditionalBranchDescrBuilder.class,
                    conditionalBranch);
        }
        return conditionalBranch.getDescr();
    }

    /**
     * namedConsequence := DO LEFT_SQUARE ID RIGHT_SQUARE BREAK?
     */
    private BaseDescr namedConsequence(CEDescrBuilder<?, ?> ce, NamedConsequenceDescrBuilder<?> namedConsequence) throws RecognitionException {
        if (namedConsequence == null) {
            namedConsequence = helper.start(ce,
                                            NamedConsequenceDescrBuilder.class,
                                            null);
        }

        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.DO,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            match(input,
                    DRL6Lexer.LEFT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;

            Token label = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;

            namedConsequence.name(label.getText());

            match(input,
                    DRL6Lexer.RIGHT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;
        } finally {
            helper.end(NamedConsequenceDescrBuilder.class,
                    namedConsequence);
        }
        return namedConsequence.getDescr();
    }

    /**
     * breakingNamedConsequence := BREAK LEFT_SQUARE ID RIGHT_SQUARE
     */
    private BaseDescr breakingNamedConsequence(CEDescrBuilder<?, ?> ce, NamedConsequenceDescrBuilder<?> namedConsequence) throws RecognitionException {
        if (namedConsequence == null) {
            namedConsequence = helper.start(ce,
                                            NamedConsequenceDescrBuilder.class,
                                            null);
        }

        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.BREAK,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            match(input,
                    DRL6Lexer.LEFT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;

            Token label = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;

            namedConsequence.name(label.getText());
            namedConsequence.breaking(true);

            match(input,
                    DRL6Lexer.RIGHT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;

        } finally {
            helper.end(NamedConsequenceDescrBuilder.class,
                    namedConsequence);
        }
        return namedConsequence.getDescr();
    }

    /**
     * lhsExists := EXISTS
     *           ( (LEFT_PAREN (or_key|and_key))=> lhsOr  // prevents '((' for prefixed and/or
     *           | LEFT_PAREN lhsOr RIGHT_PAREN
     *           | lhsPatternBind
     *           )
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    protected BaseDescr lhsExists(CEDescrBuilder<?, ?> ce,
            boolean allowOr) throws RecognitionException {
        CEDescrBuilder<?, ExistsDescr> exists = null;

        if (state.backtracking == 0) {
            exists = ce.exists();
            helper.start(exists,
                    CEDescrBuilder.class,
                    null);
        }
        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.EXISTS,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (state.backtracking == 0) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION_EXISTS);
            }
            if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {
                boolean prefixed = helper.validateLT(2,
                        DroolsSoftKeywords.AND) || helper.validateLT(2,
                        DroolsSoftKeywords.OR);

                if (!prefixed) {
                    match(input,
                            DRL6Lexer.LEFT_PAREN,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                }

                lhsOr(exists,
                        allowOr);
                if (state.failed)
                    return null;

                if (!prefixed) {
                    match(input,
                            DRL6Lexer.RIGHT_PAREN,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                }
            } else {

                lhsPatternBind(exists,
                        true);
                if (state.failed)
                    return null;
            }

        } finally {
            if (state.backtracking == 0) {
                helper.end(CEDescrBuilder.class,
                        exists);
            }
        }
        return exists != null ? exists.getDescr() : null;
    }

    /**
     * lhsNot := NOT
     *           ( (LEFT_PAREN (or_key|and_key))=> lhsOr  // prevents '((' for prefixed and/or
     *           | LEFT_PAREN lhsOr RIGHT_PAREN
     *           | lhsPatternBind
     *           )
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    protected BaseDescr lhsNot(CEDescrBuilder<?, ?> ce,
            boolean allowOr) throws RecognitionException {
        CEDescrBuilder<?, NotDescr> not = null;

        if (state.backtracking == 0) {
            not = ce.not();
            helper.start(not,
                    CEDescrBuilder.class,
                    null);
        }

        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.NOT,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (state.backtracking == 0) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION_NOT);
            }
            if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {
                boolean prefixed = helper.validateLT(2,
                        DroolsSoftKeywords.AND) || helper.validateLT(2,
                        DroolsSoftKeywords.OR);

                if (!prefixed) {
                    match(input,
                            DRL6Lexer.LEFT_PAREN,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                }
                if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
                }

                lhsOr(not,
                        allowOr);
                if (state.failed)
                    return null;

                if (!prefixed) {
                    match(input,
                            DRL6Lexer.RIGHT_PAREN,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                }
            } else if (input.LA(1) != DRL6Lexer.EOF) {

                lhsPatternBind(not,
                        true);
                if (state.failed)
                    return null;
            }

        } finally {
            if (state.backtracking == 0) {
                helper.end(CEDescrBuilder.class,
                        not);
            }
        }
        return not != null ? not.getDescr() : null;
    }

    /**
     * lhsForall := FORALL LEFT_PAREN lhsPatternBind+ RIGHT_PAREN
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    protected BaseDescr lhsForall(CEDescrBuilder<?, ?> ce) throws RecognitionException {
        ForallDescrBuilder<?> forall = helper.start(ce,
                ForallDescrBuilder.class,
                null);

        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.FORALL,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            match(input,
                    DRL6Lexer.LEFT_PAREN,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;

            do {
                lhsPatternBind(forall,
                        false);
                if (state.failed)
                    return null;

                if (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                }
            } while (input.LA(1) != DRL6Lexer.EOF && input.LA(1) != DRL6Lexer.RIGHT_PAREN);

            match(input,
                    DRL6Lexer.RIGHT_PAREN,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;
        } finally {
            helper.end(ForallDescrBuilder.class,
                    forall);
        }

        return forall != null ? forall.getDescr() : null;
    }

    /**
     * lhsEval := EVAL LEFT_PAREN conditionalExpression RIGHT_PAREN
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    private BaseDescr lhsEval(CEDescrBuilder<?, ?> ce) throws RecognitionException {
        EvalDescrBuilder<?> eval = null;

        try {
            eval = helper.start(ce,
                    EvalDescrBuilder.class,
                    null);

            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.EVAL,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return null;

            if (!parseEvalExpression(eval))
                return null;

        } catch (RecognitionException e) {
            throw e;
        } finally {
            helper.end(EvalDescrBuilder.class,
                    eval);
        }

        return eval != null ? eval.getDescr() : null;
    }

    private boolean parseEvalExpression(EvalDescrBuilder<?> eval) throws RecognitionException {
        match(input,
                DRL6Lexer.LEFT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return false;

        if (state.backtracking == 0) {
            helper.emit(Location.LOCATION_LHS_INSIDE_EVAL);
        }

        int idx = input.index();
        final String expr;
        try {
            expr = conditionalExpression();
        } catch (RecognitionException e) {
            final Token tempToken = helper.getLastTokenOnList(helper.getEditorInterface().getLast().getContent());
            if (tempToken != null) {
                for (int i = tempToken.getTokenIndex() + 1; i < input.size(); i++) {
                    final Token token = input.get(i);
                    if (token.getType() == DRL6Lexer.EOF) {
                        break;
                    }
                    helper.emit(token, DroolsEditorType.CODE_CHUNK);
                }
            }

            throw e;
        }

        if (state.backtracking == 0) {
            eval.constraint(expr);
        }

        match(input,
                DRL6Lexer.RIGHT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return false;

        helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
        return true;
    }

    /**
     * lhsParen := LEFT_PAREN lhsOr RIGHT_PAREN
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    private BaseDescr lhsParen(CEDescrBuilder<?, ?> ce,
            boolean allowOr) throws RecognitionException {
        match(input,
                DRL6Lexer.LEFT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return null;

        if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
            helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
        }
        BaseDescr descr = lhsOr(ce,
                allowOr);
        if (state.failed)
            return null;

        match(input,
                DRL6Lexer.RIGHT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return null;

        return descr;
    }

    /**
     * lhsPatternBind := label?
     *                ( LEFT_PAREN lhsPattern (OR lhsPattern)* RIGHT_PAREN
     *                | lhsPattern )
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    @SuppressWarnings("unchecked")
    private BaseDescr lhsPatternBind(PatternContainerDescrBuilder<?, ?> ce,
            final boolean allowOr) throws RecognitionException {
        PatternDescrBuilder<?> pattern;
        CEDescrBuilder<?, OrDescr> or = null;
        BaseDescr result = null;

        Token first = input.LT(1);
        pattern = helper.start((DescrBuilder<?, ?>) ce,
                PatternDescrBuilder.class,
                null);
        if (pattern != null) {
            result = pattern.getDescr();
        }

        String label = null;
        boolean isUnification = false;
        if (input.LA(1) == DRL6Lexer.ID && input.LA(2) == DRL6Lexer.COLON && !helper.validateCEKeyword(1)) {
            label = label(DroolsEditorType.IDENTIFIER_PATTERN);
            if (state.failed)
                return null;
        } else if (input.LA(1) == DRL6Lexer.ID && input.LA(2) == DRL6Lexer.UNIFY && !helper.validateCEKeyword(1)) {
            label = unif(DroolsEditorType.IDENTIFIER_PATTERN);
            if (state.failed)
                return null;
            isUnification = true;
        }

        if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {
            try {
                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;

                if (helper.validateCEKeyword(1)) {
                    failMismatchedTokenException();
                    return null; // in case it is backtracking
                }

                lhsPattern(pattern,
                        label,
                        isUnification);
                if (state.failed)
                    return null;

                if (allowOr && helper.validateIdentifierKey(DroolsSoftKeywords.OR) && ce instanceof CEDescrBuilder) {
                    if (state.backtracking == 0) {
                        // this is necessary because of the crappy bind with multi-pattern OR syntax
                        or = ((CEDescrBuilder<DescrBuilder<?, ?>, OrDescr>) ce).or();
                        result = or.getDescr();

                        helper.end(PatternDescrBuilder.class,
                                pattern);
                        helper.start(or,
                                CEDescrBuilder.class,
                                null);
                        // adjust real or starting token:
                        helper.setStart(or,
                                first);

                        // remove original pattern from the parent CE child list:
                        ((ConditionalElementDescr) ce.getDescr()).getDescrs().remove(pattern.getDescr());
                        // add pattern to the OR instead
                        or.getDescr().addDescr(pattern.getDescr());
                    }

                    while (helper.validateIdentifierKey(DroolsSoftKeywords.OR)) {
                        match(input,
                                DRL6Lexer.ID,
                                DroolsSoftKeywords.OR,
                                null,
                                DroolsEditorType.KEYWORD);
                        if (state.failed)
                            return null;

                        pattern = helper.start(or,
                                PatternDescrBuilder.class,
                                null);
                        // new pattern, same binding
                        lhsPattern(pattern,
                                label,
                                isUnification);
                        if (state.failed)
                            return null;

                        helper.end(PatternDescrBuilder.class,
                                pattern);
                    }
                }

                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;

            } finally {
                if (or != null) {
                    helper.end(CEDescrBuilder.class,
                            or);
                } else {
                    helper.end(PatternDescrBuilder.class,
                            pattern);
                }
            }

        } else {
            try {
                lhsPattern(pattern,
                        label,
                        isUnification);
                if (state.failed)
                    return null;

            } finally {
                helper.end(PatternDescrBuilder.class,
                        pattern);
            }
        }

        return result;
    }

    /**
     * lhsAccumulate := (ACCUMULATE|ACC) LEFT_PAREN lhsAnd (COMMA|SEMICOLON)
     *                      accumulateFunctionBinding (COMMA accumulateFunctionBinding)*
     *                      (SEMICOLON constraints)?
     *                  RIGHT_PAREN SEMICOLON?
     *
     * @param ce
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    private BaseDescr lhsAccumulate(PatternContainerDescrBuilder<?, ?> ce) throws RecognitionException {
        PatternDescrBuilder<?> pattern;
        BaseDescr result = null;

        pattern = helper.start((DescrBuilder<?, ?>) ce,
                PatternDescrBuilder.class,
                null);
        if (pattern != null) {
            result = pattern.getDescr();
        }

        try {
            if (state.backtracking == 0) {
                pattern.type("Object[]");
                pattern.isQuery(false);
                // might have to add the implicit bindings as well
            }

            AccumulateDescrBuilder<?> accumulate = helper.start(pattern,
                    AccumulateDescrBuilder.class,
                    null);
            try {
                if (helper.validateIdentifierKey(DroolsSoftKeywords.ACCUMULATE)) {
                    match(input,
                            DRL6Lexer.ID,
                            DroolsSoftKeywords.ACCUMULATE,
                            null,
                            DroolsEditorType.KEYWORD);
                } else {
                    // might be using the short mnemonic
                    match(input,
                            DRL6Lexer.ID,
                            DroolsSoftKeywords.ACC,
                            null,
                            DroolsEditorType.KEYWORD);
                }
                if (state.failed)
                    return null;

                if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_FROM_ACCUMULATE);
                }
                match(input,
                        DRL6Lexer.LEFT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;

                CEDescrBuilder<?, AndDescr> source = accumulate.source();
                try {
                    helper.start(source,
                            CEDescrBuilder.class,
                            null);
                    lhsAnd(source,
                            false);
                    if (state.failed)
                        return null;

                    if (source.getDescr() != null && source.getDescr() instanceof ConditionalElementDescr) {
                        ConditionalElementDescr root = source.getDescr();
                        BaseDescr[] descrs = root.getDescrs().toArray(new BaseDescr[root.getDescrs().size()]);
                        root.getDescrs().clear();
                        for (int i = 0; i < descrs.length; i++) {
                            root.addOrMerge(descrs[i]);
                        }
                    }
                } finally {
                    helper.end(CEDescrBuilder.class,
                            source);
                }

                if (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                } else if (input.LA(-1) != DRL6Lexer.SEMICOLON) {
                    // lhsUnary will consume an optional SEMICOLON, so we need to check if it was consumed already
                    // or if we must fail consuming it now
                    match(input,
                            DRL6Lexer.SEMICOLON,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;
                }

                // accumulate functions
                accumulateFunctionBinding(accumulate);
                if (state.failed)
                    return null;

                while (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;

                    accumulateFunctionBinding(accumulate);
                    if (state.failed)
                        return null;
                }

                if (input.LA(1) == DRL6Lexer.SEMICOLON) {
                    match(input,
                            DRL6Lexer.SEMICOLON,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return null;

                    constraints(pattern);
                }
                match(input,
                        DRL6Lexer.RIGHT_PAREN,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;
            } finally {
                helper.end(AccumulateDescrBuilder.class,
                        accumulate);
                if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
                }
            }
        } finally {
            helper.end(PatternDescrBuilder.class,
                    pattern);
        }

        if (input.LA(1) == DRL6Lexer.SEMICOLON) {
            match(input,
                    DRL6Lexer.SEMICOLON,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return null;
        }
        return result;
    }

    private void failMismatchedTokenException() throws DroolsMismatchedTokenException {
        if (state.backtracking > 0) {
            state.failed = true;
        } else {
            DroolsMismatchedTokenException mte = new DroolsMismatchedTokenException(input.LA(1),
                    input.LT(1).getText(),
                    input);
            input.consume();
            throw mte;
        }
    }

    void failMissingTokenException() throws MissingTokenException {
        if (state.backtracking > 0) {
            state.failed = true;
        } else {
            throw new MissingTokenException(DRL6Lexer.STRING,
                    input,
                    null);
        }
    }

    void failUnexpectedAnnotationException(String annotationName) throws DroolsUnexpectedAnnotationException {
        if (state.backtracking > 0) {
            state.failed = true;
        } else {
            throw new DroolsUnexpectedAnnotationException(input, annotationName);
        }
    }

    /**
     * lhsPattern := xpathPrimary |
     *               ( QUESTION? qualifiedIdentifier
     *                 LEFT_PAREN positionalConstraints? constraints? RIGHT_PAREN
     *                 (OVER patternFilter)? (FROM patternSource)? )
     *
     * @param pattern
     * @param label
     * @param isUnification
     * @throws org.antlr.runtime.RecognitionException
     */
     void lhsPattern(PatternDescrBuilder<?> pattern,
                     String label,
                     boolean isUnification) throws RecognitionException {

         if (label != null && input.LA(1) == DRL6Lexer.DIV) {
             int first = input.index();
             exprParser.xpathPrimary();
             if (state.failed) return;
             int last = input.LT(-1).getTokenIndex();
             String expr = toExpression("", first, last);
             pattern.id( label, isUnification ).constraint( expr );
             return;
         }

        boolean query = false;
        if (input.LA(1) == DRL6Lexer.QUESTION) {
            match(input,
                    DRL6Lexer.QUESTION,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;
            query = true;
        }

        String type = this.qualifiedIdentifier();
        if (state.failed)
            return;

        if (state.backtracking == 0) {
            pattern.type(type);
            pattern.isQuery(query);
            if (label != null) {
                pattern.id(label,
                        isUnification);
            }
        }

        match(input,
                DRL6Lexer.LEFT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return;

        if (input.LA(1) != DRL6Lexer.RIGHT_PAREN && speculatePositionalConstraints()) {
            positionalConstraints(pattern);
        }

        if (input.LA(1) != DRL6Lexer.RIGHT_PAREN) {
            constraints(pattern);
        }

        match(input,
                DRL6Lexer.RIGHT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return;

        if (helper.validateIdentifierKey(DroolsSoftKeywords.OVER)) {
            //           || input.LA( 1 ) == DRL6Lexer.PIPE ) {
            patternFilter(pattern);
        }

        if (helper.validateIdentifierKey(DroolsSoftKeywords.FROM)) {
            patternSource(pattern);
        }

        if (state.backtracking == 0) {
            helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
        }
    }

    /**
     * label := ID COLON
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    String label( DroolsEditorType edType ) throws RecognitionException {
        Token label = match(input,
                DRL6Lexer.ID,
                null,
                null,
                edType);
        if (state.failed)
            return null;

        match(input,
                DRL6Lexer.COLON,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return null;

        return label.getText();
    }

    /**
     * unif := ID UNIFY
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    private String unif(DroolsEditorType edType) throws RecognitionException {
        Token label = match(input,
                DRL6Lexer.ID,
                null,
                null,
                edType);
        if (state.failed)
            return null;

        match(input,
                DRL6Lexer.UNIFY,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return null;

        return label.getText();
    }

    private boolean speculatePositionalConstraints() {
        state.backtracking++;
        int start = input.mark();
        try {
            positionalConstraints(null); // can never throw exception
        } catch (RecognitionException re) {
            System.err.println("impossible: " + re);
            LOG.error("Exception", re);
        }
        boolean success = !state.failed;
        input.rewind(start);
        state.backtracking--;
        state.failed = false;
        return success;
    }

    /**
     * positionalConstraints := constraint (COMMA constraint)* SEMICOLON
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void positionalConstraints(PatternDescrBuilder<?> pattern) throws RecognitionException {
        constraint(pattern,
                true,
                "");
        if (state.failed)
            return;

        while (input.LA(1) == DRL6Lexer.COMMA) {
            match(input,
                    DRL6Lexer.COMMA,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;

            constraint(pattern,
                    true,
                    "");
            if (state.failed)
                return;
        }

        match(input,
                DRL6Lexer.SEMICOLON,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return;
    }

    /**
     * constraints := constraint (COMMA constraint)*
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void constraints(PatternDescrBuilder<?> pattern) throws RecognitionException {
        constraints(pattern, "");
    }

    private void constraints(PatternDescrBuilder<?> pattern, String prefix) throws RecognitionException {
        constraint(pattern,
                false,
                prefix);
        if (state.failed)
            return;

        while (input.LA(1) == DRL6Lexer.COMMA) {
            match(input,
                    DRL6Lexer.COMMA,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);

            if (state.failed)
                return;

            constraint(pattern,
                    false,
                    prefix);
            if (state.failed)
                return;
        }
    }

    /**
     * constraint := nestedConstraint | conditionalOrExpression
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void constraint(PatternDescrBuilder<?> pattern,
            boolean positional,
            String prefix) throws RecognitionException {
        if (speculateNestedConstraint()) {
            nestedConstraint(pattern, prefix);
            return;
        }

        if (state.backtracking == 0) {
            helper.emit(Location.LOCATION_LHS_INSIDE_CONDITION_START);
        }

        int first = input.index();
        exprParser.getHelper().setHasOperator(false); // resetting
        try {
            exprParser.conditionalOrExpression();
        } finally {
            if (state.backtracking == 0) {
                if (input.LA(1) == DRL6Lexer.ID && input.LA(2) == DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_INSIDE_CONDITION_ARGUMENT);
                } else if (input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_INSIDE_CONDITION_END);
                } else if (!lastTokenWasWhiteSpace()) {
                    int location = getCurrentLocation();
                    if (location == Location.LOCATION_LHS_INSIDE_CONDITION_END) {
                        helper.emit(Location.LOCATION_LHS_INSIDE_CONDITION_ARGUMENT);
                    } else if (input.get(input.index()).getType() != DRL6Lexer.EOF) {
                        helper.emit(Location.LOCATION_LHS_INSIDE_CONDITION_START);
                    }
                } else if (getCurrentLocation() == Location.LOCATION_LHS_INSIDE_CONDITION_START &&
                        !exprParser.getHelper().getHasOperator() &&
                        lastTokenWasWhiteSpace() &&
                        input.LA(1) == DRL6Lexer.EOF &&
                        input.LA(-1) == DRL6Lexer.ID) {
                    helper.emit(Location.LOCATION_LHS_INSIDE_CONDITION_OPERATOR);
                }
            }
        }

        if (state.failed)
            return;

        if (state.backtracking == 0 && input.index() > first) {
            // expression consumed something
            int last = input.LT(-1).getTokenIndex();
            String expr = toExpression(prefix, first, last);
            pattern.constraint(expr,
                    positional);
            BaseDescr constrDescr = pattern.getDescr().getDescrs().get(pattern.getDescr().getDescrs().size() - 1);
            constrDescr.setLocation(input.get(first).getLine(),
                    input.get(first).getCharPositionInLine());
            constrDescr.setEndLocation(input.get(last).getLine(),
                    input.get(last).getCharPositionInLine());
            constrDescr.setStartCharacter(((CommonToken) input.get(first)).getStartIndex());
            constrDescr.setEndCharacter(((CommonToken) input.get(last)).getStopIndex());
        }
    }

    private String toExpression(String prefix, int first, int last) {
        String expr = input.toString(first, last);
        if (prefix.length() == 0) {
            return expr;
        }
        StringBuilder sb = new StringBuilder();
        toOrExpression(sb, prefix, expr);
        return sb.toString();
    }

    private void toOrExpression(StringBuilder sb, String prefix, String expr) {
        int start = 0;
        int end = expr.indexOf("||");
        do {
            if (start > 0) {
                sb.append(" || ");
            }
            toAndExpression(sb, prefix, end > 0 ? expr.substring(start, end) : expr.substring(start));
            start = end + 2;
            end = expr.indexOf("||", start);
        } while (start > 1);
    }

    private void toAndExpression(StringBuilder sb, String prefix, String expr) {
        int start = 0;
        int end = expr.indexOf("&&");
        do {
            if (start > 0) {
                sb.append(" && ");
            }
            sb.append(toExpression(prefix, end > 0 ? expr.substring(start, end) : expr.substring(start)));
            start = end + 2;
            end = expr.indexOf("&&", start);
        } while (start > 1);
    }

    private String toExpression(String prefix, String expr) {
        expr = expr.trim();
        int colonPos = expr.indexOf(":");
        return colonPos < 0 ? prefix + expr : expr.substring(0, colonPos + 1) + " " + prefix + expr.substring(colonPos + 1).trim();
    }

    private boolean speculateNestedConstraint() throws RecognitionException {
        return getNestedConstraintPrefixLenght() > 0;
    }

    /**
     * nestedConstraint := ( ID ( DOT | HASH ) )* ID DOT LEFT_PAREN constraints RIGHT_PAREN
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void nestedConstraint(PatternDescrBuilder<?> pattern, String prefix) throws RecognitionException {
        int prefixLenght = getNestedConstraintPrefixLenght();

        int prefixStart = input.index();
        prefix += input.toString(prefixStart, prefixStart + prefixLenght - 2);
        for (int i = 0; i < prefixLenght; i++) {
            input.consume();
        }

        constraints(pattern, prefix);
        match(input,
                DRL6Lexer.RIGHT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
    }

    private int getNestedConstraintPrefixLenght() {
        int cursor = 0;
        int lastToken = input.LA(++cursor);
        while (true) {
            int nextToken = input.LA(++cursor);
            switch (lastToken) {
                case DRL6Lexer.ID:
                    if (nextToken != DRL6Lexer.DOT && nextToken != DRL6Lexer.NULL_SAFE_DOT && nextToken != DRL6Lexer.HASH) {
                        return -1;
                    }
                    break;
                case DRL6Lexer.DOT:
                case DRL6Lexer.NULL_SAFE_DOT:
                    if (nextToken == DRL6Lexer.LEFT_PAREN) {
                        return cursor;
                    }
                case DRL6Lexer.HASH:
                    if (nextToken != DRL6Lexer.ID) {
                        return -1;
                    }
                    break;
                default:
                    return -1;
            }
            lastToken = nextToken;
        }
    }

    private boolean lastTokenWasWhiteSpace() {
        int index = input.index();
        while (index >= 0) {
            int type = input.get(index).getType();
            switch (type) {
                case DRL6Lexer.EOF:
                    index--;
                    break;
                case DRL6Lexer.WS:
                    return true;
                default:
                    return false;
            }
        }
        return false;
    }

    private int getCurrentLocation() {
        LinkedList<DroolsSentence> ei = helper.getEditorInterface();
        LinkedList<?> content = ei.getLast().getContent();
        // the following call is efficient as it points to the tail of the list
        ListIterator<?> listIterator = content.listIterator(content.size());
        while (listIterator.hasPrevious()) {
            Object previous = listIterator.previous();
            if (previous instanceof Integer) {
                return ((Integer) previous).intValue();
            }
        }
        return Location.LOCATION_UNKNOWN;
    }

    /**
     * patternFilter :=   OVER filterDef
     * DISALLOWED:        | ( PIPE filterDef )+
     *
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void patternFilter(PatternDescrBuilder<?> pattern) throws RecognitionException {
        //        if ( input.LA( 1 ) == DRL6Lexer.PIPE ) {
        //            while ( input.LA( 1 ) == DRL6Lexer.PIPE ) {
        //                match( input,
        //                       DRL6Lexer.PIPE,
        //                       null,
        //                       null,
        //                       DroolsEditorType.SYMBOL );
        //                if ( state.failed ) return;
        //
        //                filterDef( pattern );
        //                if ( state.failed ) return;
        //            }
        //        } else {
        match(input,
                DRL6Lexer.ID,
                DroolsSoftKeywords.OVER,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        filterDef(pattern);
        if (state.failed)
            return;
        //        }
    }

    /**
     * filterDef := label ID LEFT_PAREN parameters RIGHT_PAREN
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void filterDef(PatternDescrBuilder<?> pattern) throws RecognitionException {
        BehaviorDescrBuilder<?> behavior = helper.start(pattern,
                BehaviorDescrBuilder.class,
                null);
        try {
            String bName = label(DroolsEditorType.IDENTIFIER_PATTERN);
            if (state.failed)
                return;

            Token subtype = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.IDENTIFIER_PATTERN);
            if (state.failed)
                return;

            if (state.backtracking == 0) {
                behavior.type(bName,
                        subtype.getText());
            }

            List<String> parameters = parameters();
            if (state.failed)
                return;

            if (state.backtracking == 0) {
                behavior.parameters(parameters);
            }
        } finally {
            helper.end(BehaviorDescrBuilder.class,
                    behavior);
        }
    }

    /**
     * patternSource := FROM
     *                ( fromAccumulate
     *                | fromCollect
     *                | fromEntryPoint
     *                | fromWindow
     *                | fromExpression )
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void patternSource(PatternDescrBuilder<?> pattern) throws RecognitionException {
        match(input,
                DRL6Lexer.ID,
                DroolsSoftKeywords.FROM,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        if (state.backtracking == 0) {
            helper.emit(Location.LOCATION_LHS_FROM);
        }

        if (helper.validateIdentifierKey(DroolsSoftKeywords.ACCUMULATE) || helper.validateIdentifierKey(DroolsSoftKeywords.ACC)) {
            fromAccumulate(pattern);
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.COLLECT)) {
            fromCollect(pattern);
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.ENTRY) &&
                helper.validateLT(2,
                        "-") &&
                helper.validateLT(3,
                        DroolsSoftKeywords.POINT)) {
            fromEntryPoint(pattern);
            if (state.failed)
                return;
        } else if (helper.validateIdentifierKey(DroolsSoftKeywords.WINDOW)) {
            fromWindow(pattern);
        } else {
            fromExpression(pattern);
            if (!lastTokenWasWhiteSpace() && input.LA(1) == DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_FROM);
                throw new RecognitionException();
            }
            if (state.failed)
                return;
        }
        if (input.LA(1) == DRL6Lexer.SEMICOLON) {
            match(input,
                    DRL6Lexer.SEMICOLON,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;
        }
    }

    /**
     * fromExpression := conditionalOrExpression
     *
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void fromExpression(PatternDescrBuilder<?> pattern) throws RecognitionException {
        String expr = conditionalOrExpression();
        if (state.failed)
            return;

        if (state.backtracking == 0) {
            pattern.from().expression(expr);
            if (input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
            }
        }
    }

    /**
     * fromEntryPoint := ENTRY-POINT stringId
     *
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void fromEntryPoint(PatternDescrBuilder<?> pattern) throws RecognitionException {
        String ep;

        match(input,
                DRL6Lexer.ID,
                DroolsSoftKeywords.ENTRY,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        match(input,
                DRL6Lexer.MINUS,
                null,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        match(input,
                DRL6Lexer.ID,
                DroolsSoftKeywords.POINT,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        ep = stringId();

        if (state.backtracking == 0) {
            pattern.from().entryPoint(ep);
            if (input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
            }
        }
    }

    /**
     * fromWindow := WINDOW ID
     *
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void fromWindow(PatternDescrBuilder<?> pattern) throws RecognitionException {
        String window;

        match(input,
                DRL6Lexer.ID,
                DroolsSoftKeywords.WINDOW,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        Token id = match(input,
                DRL6Lexer.ID,
                null,
                null,
                DroolsEditorType.IDENTIFIER);
        if (state.failed)
            return;
        window = id.getText();

        if (state.backtracking == 0) {
            pattern.from().window(window);
            if (input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
            }
        }
    }

    /**
     * fromCollect := COLLECT LEFT_PAREN lhsPatternBind RIGHT_PAREN
     *
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void fromCollect(PatternDescrBuilder<?> pattern) throws RecognitionException {
        CollectDescrBuilder<?> collect = helper.start(pattern,
                CollectDescrBuilder.class,
                null);
        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.COLLECT,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return;
            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_FROM_COLLECT);
            }

            match(input,
                    DRL6Lexer.LEFT_PAREN,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;

            lhsPatternBind(collect,
                    false);
            if (state.failed)
                return;

            match(input,
                    DRL6Lexer.RIGHT_PAREN,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;
        } finally {
            helper.end(CollectDescrBuilder.class,
                    collect);
            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
            }
        }
    }

    /**
     * fromAccumulate := ACCUMULATE LEFT_PAREN lhsAnd (COMMA|SEMICOLON)
     *                   ( INIT chunk_(_) COMMA ACTION chunk_(_) COMMA
     *                     ( REVERSE chunk_(_) COMMA)? RESULT chunk_(_)
     *                   | accumulateFunction
     *                   ) RIGHT_PAREN
     *
     * @param pattern
     * @throws org.antlr.runtime.RecognitionException
     */
    private void fromAccumulate(PatternDescrBuilder<?> pattern) throws RecognitionException {
        AccumulateDescrBuilder<?> accumulate = helper.start(pattern,
                AccumulateDescrBuilder.class,
                null);
        try {
            if (helper.validateIdentifierKey(DroolsSoftKeywords.ACCUMULATE)) {
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.ACCUMULATE,
                        null,
                        DroolsEditorType.KEYWORD);
            } else {
                // might be using the short mnemonic
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.ACC,
                        null,
                        DroolsEditorType.KEYWORD);
            }
            if (state.failed)
                return;

            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_FROM_ACCUMULATE);
            }
            match(input,
                    DRL6Lexer.LEFT_PAREN,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;

            CEDescrBuilder<?, AndDescr> source = accumulate.source();
            try {
                helper.start(source,
                        CEDescrBuilder.class,
                        null);
                lhsAnd(source,
                        false);
                if (state.failed)
                    return;

                if (source.getDescr() != null && source.getDescr() instanceof ConditionalElementDescr) {
                    ConditionalElementDescr root = source.getDescr();
                    BaseDescr[] descrs = root.getDescrs().toArray(new BaseDescr[root.getDescrs().size()]);
                    root.getDescrs().clear();
                    for (int i = 0; i < descrs.length; i++) {
                        root.addOrMerge(descrs[i]);
                    }
                }
            } finally {
                helper.end(CEDescrBuilder.class,
                        source);
            }

            if (input.LA(1) == DRL6Lexer.COMMA) {
                match(input,
                        DRL6Lexer.COMMA,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;
            } else if (input.LA(-1) != DRL6Lexer.SEMICOLON) {
                match(input,
                      DRL6Lexer.SEMICOLON,
                      null,
                      null,
                      DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;
            }

            if (helper.validateIdentifierKey(DroolsSoftKeywords.INIT)) {
                // custom code, inline accumulate

                // initBlock
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.INIT,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return;
                if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_FROM_ACCUMULATE_INIT);
                }

                String init = chunk(DRL6Lexer.LEFT_PAREN,
                        DRL6Lexer.RIGHT_PAREN,
                        Location.LOCATION_LHS_FROM_ACCUMULATE_INIT_INSIDE);
                if (state.failed)
                    return;
                if (state.backtracking == 0)
                    accumulate.init(init);

                if (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return;
                }

                // actionBlock
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.ACTION,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return;
                if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_FROM_ACCUMULATE_ACTION);
                }

                String action = chunk(DRL6Lexer.LEFT_PAREN,
                        DRL6Lexer.RIGHT_PAREN,
                        Location.LOCATION_LHS_FROM_ACCUMULATE_ACTION_INSIDE);
                if (state.failed)
                    return;
                if (state.backtracking == 0)
                    accumulate.action(action);

                if (input.LA(1) == DRL6Lexer.COMMA) {
                    match(input,
                            DRL6Lexer.COMMA,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return;
                }

                // reverseBlock
                if (helper.validateIdentifierKey(DroolsSoftKeywords.REVERSE)) {
                    match(input,
                            DRL6Lexer.ID,
                            DroolsSoftKeywords.REVERSE,
                            null,
                            DroolsEditorType.KEYWORD);
                    if (state.failed)
                        return;
                    if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                        helper.emit(Location.LOCATION_LHS_FROM_ACCUMULATE_REVERSE);
                    }

                    String reverse = chunk(DRL6Lexer.LEFT_PAREN,
                            DRL6Lexer.RIGHT_PAREN,
                            Location.LOCATION_LHS_FROM_ACCUMULATE_REVERSE_INSIDE);
                    if (state.failed)
                        return;
                    if (state.backtracking == 0)
                        accumulate.reverse(reverse);

                    if (input.LA(1) == DRL6Lexer.COMMA) {
                        match(input,
                                DRL6Lexer.COMMA,
                                null,
                                null,
                                DroolsEditorType.SYMBOL);
                        if (state.failed)
                            return;
                    }
                }

                // resultBlock
                match(input,
                        DRL6Lexer.ID,
                        DroolsSoftKeywords.RESULT,
                        null,
                        DroolsEditorType.KEYWORD);
                if (state.failed)
                    return;

                if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                    helper.emit(Location.LOCATION_LHS_FROM_ACCUMULATE_RESULT);
                }

                String result = chunk(DRL6Lexer.LEFT_PAREN,
                        DRL6Lexer.RIGHT_PAREN,
                        Location.LOCATION_LHS_FROM_ACCUMULATE_RESULT_INSIDE);
                if (state.failed)
                    return;
                if (state.backtracking == 0)
                    accumulate.result(result);
            } else {
                // accumulate functions
                accumulateFunction(accumulate,
                                   false,
                                   null);
                if (state.failed)
                    return;
            }

            match(input,
                    DRL6Lexer.RIGHT_PAREN,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;
        } finally {
            helper.end(AccumulateDescrBuilder.class,
                    accumulate);
            if (state.backtracking == 0 && input.LA(1) != DRL6Lexer.EOF) {
                helper.emit(Location.LOCATION_LHS_BEGIN_OF_CONDITION);
            }
        }
    }

    /**
     * accumulateFunctionBinding := label accumulateFunction
     * @param accumulate
     * @throws org.antlr.runtime.RecognitionException
     */
    private void accumulateFunctionBinding(AccumulateDescrBuilder<?> accumulate) throws RecognitionException {
        String label = null;
        boolean unif = false;
        if (input.LA(2) == DRL6Lexer.COLON) {
            label = label(DroolsEditorType.IDENTIFIER_VARIABLE);
        } else if (input.LA(2) == DRL6Lexer.UNIFY) {
            label = unif(DroolsEditorType.IDENTIFIER_VARIABLE);
            unif = true;
        }
        accumulateFunction( accumulate,
                            unif,
                            label );
    }

    /**
     * accumulateFunction := label? ID parameters
     * @param accumulate
     * @throws org.antlr.runtime.RecognitionException
     */
    private void accumulateFunction(AccumulateDescrBuilder<?> accumulate,
                                    boolean unif,
                                    String label) throws RecognitionException {
        Token function = match(input,
                DRL6Lexer.ID,
                null,
                null,
                DroolsEditorType.KEYWORD);
        if (state.failed)
            return;

        List<String> parameters = parameters();
        if (state.failed)
            return;

        if (state.backtracking == 0) {
            accumulate.function(function.getText(),
                    label,
                    unif,
                    parameters != null ? parameters.toArray(new String[]{}) : new String[]{});
        }
    }

    /**
     * parameters := LEFT_PAREN (conditionalExpression (COMMA conditionalExpression)* )? RIGHT_PAREN
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    private List<String> parameters() throws RecognitionException {
        match(input,
                DRL6Lexer.LEFT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return null;

        List<String> parameters = new ArrayList<>();
        if (input.LA(1) != DRL6Lexer.EOF && input.LA(1) != DRL6Lexer.RIGHT_PAREN) {
            String param = conditionalExpression();
            if (state.failed)
                return null;
            parameters.add(param);

            while (input.LA(1) == DRL6Lexer.COMMA) {
                match(input,
                        DRL6Lexer.COMMA,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return null;

                param = conditionalExpression();
                if (state.failed)
                    return null;
                parameters.add(param);
            }
        }

        match(input,
                DRL6Lexer.RIGHT_PAREN,
                null,
                null,
                DroolsEditorType.SYMBOL);
        if (state.failed)
            return null;
        return parameters;
    }

    /**
     * rhs := defaultConsequence namedConsequence* (~END)*
     * @param rule
     */
    void rhs( RuleDescrBuilder rule ) {
        defaultConsequence(rule);
        while (input.LA(1) != DRL6Lexer.EOF && helper.validateIdentifierKey(DroolsSoftKeywords.THEN)) {
            namedConsequence(rule);
        }
    }

    /**
     * defaultConsequence := THEN chunk
     * @param rule
     */
    public void defaultConsequence(RuleDescrBuilder rule) {
        try {
            int first = input.index();
            Token t = match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.THEN,
                    null,
                    DroolsEditorType.KEYWORD);
            if (state.failed)
                return;

            if (state.backtracking == 0) {
                rule.getDescr().setConsequenceLocation(t.getLine(),
                        t.getCharPositionInLine());
                helper.emit(Location.LOCATION_RHS);
            }

            String chunk = getConsequenceCode(first);

            // remove the "then" keyword and any subsequent spaces and line breaks
            // keep indentation of 1st non-blank line
            chunk = chunk.replaceFirst("^then\\s*\\r?\\n?",
                    "");
            rule.rhs(chunk);

        } catch (RecognitionException re) {
            reportError(re);
        }
    }

    /**
     * namedConsequence := THEN LEFT_SQUARE ID RIGHT_SQUARE chunk
     * @param rule
     */
    public void namedConsequence(RuleDescrBuilder rule) {
        try {
            match(input,
                    DRL6Lexer.ID,
                    DroolsSoftKeywords.THEN,
                    null,
                    DroolsEditorType.KEYWORD);
            match(input,
                    DRL6Lexer.LEFT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            Token label = match(input,
                    DRL6Lexer.ID,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            int first = input.index();
            match(input,
                    DRL6Lexer.RIGHT_SQUARE,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return;

            String name = label.getText();
            String chunk = getConsequenceCode(first);

            // remove the closing squqre bracket "]" and any subsequent spaces and line breaks
            // keep indentation of 1st non-blank line
            chunk = chunk.replaceFirst("^\\]\\s*\\r?\\n?",
                    "");
            rule.namedRhs(name, chunk);

        } catch (RecognitionException re) {
            reportError(re);
        }
    }

    protected String getConsequenceCode( int first ) {
        while (input.LA(1) != DRL6Lexer.EOF) {
            if (helper.validateIdentifierKey(DroolsSoftKeywords.END)) {
                int next = input.LA(2) == DRL6Lexer.SEMICOLON ? 3 : 2;
                if (input.LA(next) == DRL6Lexer.EOF || input.LA(next) == DRL6Lexer.AT || helper.validateStatement(next)) {
                    break;
                }
            } else if (helper.validateIdentifierKey(DroolsSoftKeywords.THEN)) {
                if (isNextTokenThenCompatible( input.LA( 2 ) ) ) {
                    break;
                }
            }

            helper.emit( input.LT( 1 ), DroolsEditorType.CODE_CHUNK );
            input.consume();
        }
        int last = input.LT(1).getTokenIndex();
        if (last <= first) {
            return "";
        }

        String chunk = input.toString(first,
                last);
        if (chunk.endsWith(DroolsSoftKeywords.END)) {
            chunk = chunk.substring(0,
                    chunk.length() - DroolsSoftKeywords.END.length());
        } else if (chunk.endsWith(DroolsSoftKeywords.THEN)) {
            chunk = chunk.substring(0,
                    chunk.length() - DroolsSoftKeywords.THEN.length());
        }
        return chunk;
    }

    private boolean isNextTokenThenCompatible(int next) {
        return next != DRL6Lexer.LEFT_PAREN &&
               next != DRL6Lexer.RIGHT_PAREN &&
               next != DRL6Lexer.RIGHT_SQUARE &&
               next != DRL6Lexer.COMMA &&
               next != DRL6Lexer.SEMICOLON;
    }

    /* ------------------------------------------------------------------------------------------------
    *                         ANNOTATION
    * ------------------------------------------------------------------------------------------------ */

    /**
     * annotation := fullAnnotation | AT ID chunk_(_)?
     */
    void annotation( AnnotatedDescrBuilder<?> adb ) {
        AnnotationDescrBuilder<?> annotation = null;

        try {
            if (speculateFullAnnotation()) {
                boolean buildState = exprParser.isBuildDescr();
                exprParser.setBuildDescr(true);
                exprParser.fullAnnotation(adb);
                exprParser.setBuildDescr(buildState);
            } else {

                // '@'
                Token at = match(input,
                        DRL6Lexer.AT,
                        null,
                        null,
                        DroolsEditorType.SYMBOL);
                if (state.failed)
                    return;

                // identifier
                String fqn = qualifiedIdentifier();
                if (state.failed)
                    return;

                if (state.backtracking == 0) {
                    annotation = adb.newAnnotation(fqn);
                    helper.setStart(annotation,
                            at);
                }

                try {
                    if (input.LA(1) == DRL6Lexer.LEFT_PAREN) {
                        String value = chunk(DRL6Lexer.LEFT_PAREN,
                                DRL6Lexer.RIGHT_PAREN,
                                -1).trim();
                        if (state.failed)
                            return;
                        if (state.backtracking == 0) {
                            if (annotation != null) {
                                annotation.value(value);
                            } else {
                                throw new RecognitionException();
                            }
                        }
                    }
                } finally {
                    if (state.backtracking == 0) {
                        helper.setEnd(annotation);
                    }
                }
            }

        } catch (RecognitionException re) {
            reportError(re);
        }
    }

    /**
     * Invokes the expression parser, trying to parse the annotation
     * as a full java-style annotation
     *
     * @return true if the sequence of tokens will match the
     *         elementValuePairs() syntax. false otherwise.
     */
    private boolean speculateFullAnnotation() {
        state.backtracking++;
        int start = input.mark();
        try {
            exprParser.fullAnnotation(null);
        } catch (RecognitionException re) {
            System.err.println("impossible: " + re);
            LOG.error("Exception", re);
        }
        boolean success = !state.failed;
        input.rewind(start);
        state.backtracking--;
        state.failed = false;
        return success;

    }

    /* ------------------------------------------------------------------------------------------------
     *                         UTILITY RULES
     * ------------------------------------------------------------------------------------------------ */

    /**
     * Matches a type name
     *
     * type := ID typeArguments? ( DOT ID typeArguments? )* (LEFT_SQUARE RIGHT_SQUARE)*
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public String type() throws RecognitionException {
        String type = "";
        try {
            int first = input.index(), last = first;
            match(input,
                    DRL6Lexer.ID,
                    null,
                    new int[]{DRL6Lexer.DOT, DRL6Lexer.LESS},
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return type;

            if (input.LA(1) == DRL6Lexer.LESS) {
                typeArguments();
                if (state.failed)
                    return type;
            }

            while (input.LA(1) == DRL6Lexer.DOT && input.LA(2) == DRL6Lexer.ID) {
                match(input,
                        DRL6Lexer.DOT,
                        null,
                        new int[]{DRL6Lexer.ID},
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return type;
                match(input,
                        DRL6Lexer.ID,
                        null,
                        new int[]{DRL6Lexer.DOT},
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return type;

                if (input.LA(1) == DRL6Lexer.LESS) {
                    typeArguments();
                    if (state.failed)
                        return type;
                }
            }

            while (input.LA(1) == DRL6Lexer.LEFT_SQUARE && input.LA(2) == DRL6Lexer.RIGHT_SQUARE) {
                match(input,
                        DRL6Lexer.LEFT_SQUARE,
                        null,
                        new int[]{DRL6Lexer.RIGHT_SQUARE},
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return type;
                match(input,
                        DRL6Lexer.RIGHT_SQUARE,
                        null,
                        null,
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return type;
            }
            last = input.LT(-1).getTokenIndex();
            type = input.toString(first,
                    last);
            type = type.replace(" ",
                    "");
        } catch (RecognitionException re) {
            reportError(re);
        }
        return type;
    }

    /**
     * Matches type arguments
     *
     * typeArguments := LESS typeArgument (COMMA typeArgument)* GREATER
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public String typeArguments() throws RecognitionException {
        String typeArguments = "";
        try {
            int first = input.index();
            Token token = match(input,
                    DRL6Lexer.LESS,
                    null,
                    new int[]{DRL6Lexer.QUESTION, DRL6Lexer.ID},
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return typeArguments;

            typeArgument();
            if (state.failed)
                return typeArguments;

            while (input.LA(1) == DRL6Lexer.COMMA) {
                token = match(input,
                        DRL6Lexer.COMMA,
                        null,
                        new int[]{DRL6Lexer.QUESTION, DRL6Lexer.ID},
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return typeArguments;

                typeArgument();
                if (state.failed)
                    return typeArguments;
            }

            token = match(input,
                    DRL6Lexer.GREATER,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return typeArguments;
            typeArguments = input.toString(first,
                    token.getTokenIndex());

        } catch (RecognitionException re) {
            reportError(re);
        }
        return typeArguments;
    }

    /**
     * Matches a type argument
     *
     * typeArguments := QUESTION (( EXTENDS | SUPER ) type )?
     *               |  type
     *               ;
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public String typeArgument() throws RecognitionException {
        String typeArgument = "";
        try {
            int first = input.index(), last = first;
            int next = input.LA(1);
            switch (next) {
                case DRL6Lexer.QUESTION:
                    match(input,
                            DRL6Lexer.QUESTION,
                            null,
                            null,
                            DroolsEditorType.SYMBOL);
                    if (state.failed)
                        return typeArgument;

                    if (helper.validateIdentifierKey(DroolsSoftKeywords.EXTENDS)) {
                        match(input,
                                DRL6Lexer.ID,
                                DroolsSoftKeywords.EXTENDS,
                                null,
                                DroolsEditorType.SYMBOL);
                        if (state.failed)
                            return typeArgument;

                        type();
                        if (state.failed)
                            return typeArgument;
                    } else if (helper.validateIdentifierKey(DroolsSoftKeywords.SUPER)) {
                        match(input,
                                DRL6Lexer.ID,
                                DroolsSoftKeywords.SUPER,
                                null,
                                DroolsEditorType.SYMBOL);
                        if (state.failed)
                            return typeArgument;
                        type();
                        if (state.failed)
                            return typeArgument;
                    }
                    break;
                case DRL6Lexer.ID:
                    type();
                    if (state.failed)
                        return typeArgument;
                    break;
                default:
                    // TODO: raise error
            }
            last = input.LT(-1).getTokenIndex();
            typeArgument = input.toString(first,
                    last);
        } catch (RecognitionException re) {
            reportError(re);
        }
        return typeArgument;
    }

    /**
     * Matches a qualified identifier
     *
     * qualifiedIdentifier := ID ( DOT ID )*
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public String qualifiedIdentifier() throws RecognitionException {
        String qi = "";
        try {
            Token first = match(input,
                    DRL6Lexer.ID,
                    null,
                    new int[]{DRL6Lexer.DOT},
                    DroolsEditorType.IDENTIFIER);
            if (state.failed)
                return qi;

            Token last = first;
            while (input.LA(1) == DRL6Lexer.DOT && input.LA(2) == DRL6Lexer.ID) {
                last = match(input,
                        DRL6Lexer.DOT,
                        null,
                        new int[]{DRL6Lexer.ID},
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return qi;
                last = match(input,
                        DRL6Lexer.ID,
                        null,
                        new int[]{DRL6Lexer.DOT},
                        DroolsEditorType.IDENTIFIER);
                if (state.failed)
                    return qi;
            }
            qi = input.toString(first,
                    last);
            qi = qi.replace(" ",
                    "");
        } catch (RecognitionException re) {
            reportError(re);
        }
        return qi;
    }

    /**
     * Matches a conditional expression
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public String conditionalExpression() throws RecognitionException {
        int first = input.index();
        exprParser.conditionalExpression();
        if (state.failed)
            return null;

        if (state.backtracking == 0 && input.index() > first) {
            // expression consumed something
            String expr = input.toString(first,
                    input.LT(-1).getTokenIndex());
            return expr;
        }
        return null;
    }

    /**
     * Matches a conditional || expression
     *
     * @return
     * @throws org.antlr.runtime.RecognitionException
     */
    public String conditionalOrExpression() throws RecognitionException {
        int first = input.index();
        exprParser.conditionalOrExpression();
        if (state.failed)
            return null;

        if (state.backtracking == 0 && input.index() > first) {
            // expression consumed something
            String expr = input.toString(first,
                    input.LT(-1).getTokenIndex());
            return expr;
        }
        return null;
    }

    /**
     * Matches a chunk started by the leftDelimiter and ended by the rightDelimiter.
     * 
     * @param leftDelimiter
     * @param rightDelimiter
     * @param location
     * @return the matched chunk without the delimiters
     */
    public String chunk(final int leftDelimiter,
            final int rightDelimiter,
            final int location) {
        String chunk = "";
        int first = -1, last = first;
        try {
            match(input,
                    leftDelimiter,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return chunk;
            if (state.backtracking == 0 && location >= 0) {
                helper.emit(location);
            }
            int nests = 0;
            first = input.index();

            while (input.LA(1) != DRL6Lexer.EOF && (input.LA(1) != rightDelimiter || nests > 0)) {
                if (input.LA(1) == rightDelimiter) {
                    nests--;
                } else if (input.LA(1) == leftDelimiter) {
                    nests++;
                }
                input.consume();
            }
            last = input.LT(-1).getTokenIndex();

            for (int i = first; i < last + 1; i++) {
                helper.emit(input.get(i), DroolsEditorType.CODE_CHUNK);
            }

            match(input,
                    rightDelimiter,
                    null,
                    null,
                    DroolsEditorType.SYMBOL);
            if (state.failed)
                return chunk;

        } catch (RecognitionException re) {
            reportError(re);
        } finally {
            if (last >= first) {
                chunk = input.toString(first,
                        last);
            }
        }
        return chunk;
    }

    /* ------------------------------------------------------------------------------------------------
      *                         GENERAL UTILITY METHODS
      * ------------------------------------------------------------------------------------------------ */
    /** 
     *  Match current input symbol against ttype and optionally
     *  check the text of the token against text.  Attempt
     *  single token insertion or deletion error recovery.  If
     *  that fails, throw MismatchedTokenException.
     */
    Token match( TokenStream input,
                 int ttype,
                 String text,
                 int[] follow,
                 DroolsEditorType etype ) throws RecognitionException {
        Token matchedSymbol;
        matchedSymbol = input.LT(1);
        if (input.LA(1) == ttype && (text == null || text.equals(matchedSymbol.getText()))) {
            input.consume();
            state.errorRecovery = false;
            state.failed = false;
            helper.emit(matchedSymbol,
                    etype);
            return matchedSymbol;
        }
        if (state.backtracking > 0) {
            state.failed = true;
            return matchedSymbol;
        }
        matchedSymbol = recoverFromMismatchedToken(input,
                ttype,
                text,
                follow);
        helper.emit(matchedSymbol,
                etype);
        return matchedSymbol;
    }

    /** Attempt to recover from a single missing or extra token.
    *
    *  EXTRA TOKEN
    *
    *  LA(1) is not what we are looking for.  If LA(2) has the right token,
    *  however, then assume LA(1) is some extra spurious token.  Delete it
    *  and LA(2) as if we were doing a normal match(), which advances the
    *  input.
    *
    *  MISSING TOKEN
    *
    *  If current token is consistent with what could come after
    *  ttype then it is ok to "insert" the missing token, else throw
    *  exception For example, Input "i=(3;" is clearly missing the
    *  ')'.  When the parser returns from the nested call to expr, it
    *  will have call chain:
    *
    *    stat -> expr -> atom
    *
    *  and it will be trying to match the ')' at this point in the
    *  derivation:
    *
    *       => ID '=' '(' INT ')' ('+' atom)* ';'
    *                          ^
    *  match() will see that ';' doesn't match ')' and report a
    *  mismatched token error.  To recover, it sees that LA(1)==';'
    *  is in the set of tokens that can follow the ')' token
    *  reference in rule atom.  It can assume that you forgot the ')'.
    */
    protected Token recoverFromMismatchedToken(TokenStream input,
            int ttype,
            String text,
            int[] follow)
            throws RecognitionException {
        RecognitionException e;
        // if next token is what we are looking for then "delete" this token
        if (mismatchIsUnwantedToken(input,
                ttype,
                text)) {
            e = new UnwantedTokenException(ttype,
                    input);
            input.consume(); // simply delete extra token
            reportError(e); // report after consuming so AW sees the token in the exception
            // we want to return the token we're actually matching
            Token matchedSymbol = input.LT(1);
            input.consume(); // move past ttype token as if all were ok
            return matchedSymbol;
        }
        // can't recover with single token deletion, try insertion
        if (mismatchIsMissingToken(input,
                follow)) {
            e = new MissingTokenException(ttype,
                    input,
                    null);
            reportError(e); // report after inserting so AW sees the token in the exception
            return null;
        }
        // even that didn't work; must throw the exception
        if (text != null) {
            e = new DroolsMismatchedTokenException(ttype,
                    text,
                    input);
        } else {
            e = new MismatchedTokenException(ttype,
                    input);
        }
        throw e;
    }

    public boolean mismatchIsUnwantedToken(TokenStream input,
            int ttype,
            String text) {
        return (input.LA(2) == ttype && (text == null || text.equals(input.LT(2).getText())));
    }

    public boolean mismatchIsMissingToken(TokenStream input,
            int[] follow) {
        if (follow == null) {
            // we have no information about the follow; we can only consume
            // a single token and hope for the best
            return false;
        }
        // TODO: implement this error recovery strategy
        return false;
    }

    private String safeStripDelimiters(String value,
            String left,
            String right) {
        if (value != null) {
            value = value.trim();
            if (value.length() >= left.length() + right.length() &&
                    value.startsWith(left) && value.endsWith(right)) {
                value = value.substring(left.length(),
                        value.length() - right.length());
            }
        }
        return value;
    }

    private String safeStripStringDelimiters(String value) {
        if (value != null) {
            value = value.trim();
            if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
                value = value.substring(1,
                        value.length() - 1);
            }
        }
        return value;
    }

}
