/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* 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.kie.dmn.feel.parser.feel11;
import org.antlr.v4.runtime.*;
import org.kie.dmn.api.feel.runtime.events.FEELEvent;
import org.kie.dmn.feel.lang.Type;
import org.kie.dmn.feel.lang.impl.FEELEventListenersManager;
import org.kie.dmn.feel.runtime.events.SyntaxErrorEvent;
import org.kie.dmn.feel.util.Msg;
import java.util.*;
import java.util.regex.Pattern;
public class FEELParser {
private static final List<String> REUSABLE_KEYWORDS = Arrays.asList(
"for", "return", "if", "then", "else", "some", "every", "satisfies", "instance", "of",
"function", "external", "or", "and", "between", "not", "null", "true", "false"
);
private static final Pattern DIGITS_PATTERN = Pattern.compile( "[0-9]*" );
public static FEEL_1_1Parser parse(FEELEventListenersManager eventsManager, String source, Map<String, Type> inputVariableTypes, Map<String, Object> inputVariables) {
ANTLRInputStream input = new ANTLRInputStream(source);
FEEL_1_1Lexer lexer = new FEEL_1_1Lexer( input );
CommonTokenStream tokens = new CommonTokenStream( lexer );
FEEL_1_1Parser parser = new FEEL_1_1Parser( tokens );
parser.setHelper( new ParserHelper( eventsManager ) );
parser.setErrorHandler( new FEELErrorHandler() );
parser.removeErrorListeners(); // removes the error listener that prints to the console
parser.addErrorListener( new FEELParserErrorListener( eventsManager ) );
// pre-loads the parser with symbols
defineVariables( inputVariableTypes, inputVariables, parser );
return parser;
}
/**
* Either namePart is a string of digits, or it must be a valid name itself
*/
public static boolean isVariableNamePartValid( String namePart ) {
return DIGITS_PATTERN.matcher(namePart).matches() || isVariableNameValid(namePart);
}
public static boolean isVariableNameValid( String source ) {
return checkVariableName( source ).isEmpty();
}
public static List<FEELEvent> checkVariableName( String source ) {
if( source == null || source.isEmpty() ) {
return Collections.singletonList( new SyntaxErrorEvent( FEELEvent.Severity.ERROR,
Msg.createMessage( Msg.INVALID_VARIABLE_NAME_EMPTY ),
null,
0,
0,
null ) );
}
ANTLRInputStream input = new ANTLRInputStream(source);
FEEL_1_1Lexer lexer = new FEEL_1_1Lexer( input );
CommonTokenStream tokens = new CommonTokenStream( lexer );
FEEL_1_1Parser parser = new FEEL_1_1Parser( tokens );
parser.setHelper( new ParserHelper() );
parser.setErrorHandler( new FEELErrorHandler() );
FEELParserErrorListener errorChecker = new FEELParserErrorListener( null );
parser.removeErrorListeners(); // removes the error listener that prints to the console
parser.addErrorListener( errorChecker );
FEEL_1_1Parser.NameDefinitionContext nameDef = parser.nameDefinition();
if( ! errorChecker.hasErrors() &&
nameDef != null &&
source.trim().equals( parser.getHelper().getOriginalText( nameDef ) ) ) {
return Collections.emptyList();
}
return errorChecker.getErrors();
}
private static void defineVariables(Map<String, Type> inputVariableTypes, Map<String, Object> inputVariables, FEEL_1_1Parser parser) {
inputVariableTypes.forEach( (name, type) -> {
parser.getHelper().defineVariable( name, type );
} );
inputVariables.forEach( (name, value) -> {
parser.getHelper().defineVariable( name );
if( value instanceof Map ) {
try {
parser.getHelper().pushName( name );
parser.getHelper().pushScope();
defineVariables( Collections.EMPTY_MAP, (Map<String, Object>) value, parser );
} finally {
parser.getHelper().popScope();
parser.getHelper().popName();
}
}
} );
}
public static class FEELErrorHandler extends DefaultErrorStrategy {
@Override
protected void reportFailedPredicate(Parser recognizer, FailedPredicateException e) {
// don't do anything
}
}
public static class FEELParserErrorListener extends BaseErrorListener {
private final FEELEventListenersManager eventsManager;
private List<FEELEvent> errors = null;
public FEELParserErrorListener(FEELEventListenersManager eventsManager) {
this.eventsManager = eventsManager;
}
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
final SyntaxErrorEvent error;
CommonToken token = (CommonToken) offendingSymbol;
if( ((Parser)recognizer).getRuleInvocationStack().contains( "nameDefinition" ) ) {
error = generateInvalidVariableError( offendingSymbol, line, charPositionInLine, e, token );
} else {
error = new SyntaxErrorEvent( FEELEvent.Severity.ERROR,
msg,
e,
line,
charPositionInLine,
offendingSymbol );
}
// if the event manager is set, notify listeners, otherwise store the error in the errors list
if( eventsManager != null ) {
FEELEventListenersManager.notifyListeners( eventsManager , () -> error );
} else {
if( errors == null ) {
errors = new ArrayList<>( );
}
errors.add( error );
}
}
public boolean hasErrors() {
return this.errors != null && ! this.errors.isEmpty();
}
public List<FEELEvent> getErrors() {
return errors;
}
}
private static SyntaxErrorEvent generateInvalidVariableError(Object offendingSymbol, int line, int charPositionInLine, RecognitionException e, CommonToken token) {
String chars = token.getText().length() == 1 ? "character" : "sequence of characters";
if( charPositionInLine == 0 ) {
if( "in".equals( token.getText() ) || REUSABLE_KEYWORDS.contains( token.getText() ) ) {
return new SyntaxErrorEvent(
FEELEvent.Severity.ERROR,
Msg.createMessage( Msg.INVALID_VARIABLE_NAME_START, "keyword", token.getText() ),
e,
line,
charPositionInLine,
offendingSymbol );
} else {
return new SyntaxErrorEvent(
FEELEvent.Severity.ERROR,
Msg.createMessage( Msg.INVALID_VARIABLE_NAME_START, chars, token.getText() ),
e,
line,
charPositionInLine,
offendingSymbol );
}
} else if( "in".equals( token.getText() ) ) {
return new SyntaxErrorEvent( FEELEvent.Severity.ERROR,
Msg.createMessage( Msg.INVALID_VARIABLE_NAME, "keyword", token.getText() ),
e,
line,
charPositionInLine,
offendingSymbol );
} else {
return new SyntaxErrorEvent( FEELEvent.Severity.ERROR,
Msg.createMessage( Msg.INVALID_VARIABLE_NAME, chars, token.getText() ),
e,
line,
charPositionInLine,
offendingSymbol );
}
}
}