Java tutorial
/* * 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 java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonToken; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.DefaultErrorStrategy; import org.antlr.v4.runtime.FailedPredicateException; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.kie.dmn.api.feel.runtime.events.FEELEvent; import org.kie.dmn.feel.lang.FEELProfile; import org.kie.dmn.feel.lang.Scope; import org.kie.dmn.feel.lang.Type; import org.kie.dmn.feel.lang.impl.FEELEventListenersManager; import org.kie.dmn.feel.lang.types.BuiltInTypeSymbol; import org.kie.dmn.feel.runtime.FEELFunction; import org.kie.dmn.feel.runtime.events.SyntaxErrorEvent; import org.kie.dmn.feel.util.Msg; 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, Collection<FEELFunction> additionalFunctions, List<FEELProfile> profiles) { CharStream input = CharStreams.fromString(source); FEEL_1_1Lexer lexer = new FEEL_1_1Lexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); FEEL_1_1Parser parser = new FEEL_1_1Parser(tokens); ParserHelper parserHelper = new ParserHelper(eventsManager); additionalFunctions.forEach(f -> parserHelper.getSymbolTable().getBuiltInScope().define(f.getSymbol())); parser.setHelper(parserHelper); 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, Scope scope) { if (DIGITS_PATTERN.matcher(namePart).matches()) { return true; } if (REUSABLE_KEYWORDS.contains(namePart)) { return scope.followUp(namePart, true); } return 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)); } CharStream input = CharStreams.fromString(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.NameDefinitionWithEOFContext nameDef = parser.nameDefinitionWithEOF(); // be sure to align below parser.getRuleInvocationStack().contains("nameDefinition... if (!errorChecker.hasErrors() && nameDef != null && source.trim().equals(parser.getHelper().getOriginalText(nameDef))) { return Collections.emptyList(); } return errorChecker.getErrors(); } public static void defineVariables(Map<String, Type> inputVariableTypes, Map<String, Object> inputVariables, FEEL_1_1Parser parser) { inputVariableTypes.forEach((name, type) -> { parser.getHelper().defineVariable(name, type); if (type.getName() != null) { parser.getHelper().getSymbolTable().getGlobalScope() .define(new BuiltInTypeSymbol(type.getName(), 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; final int tokenIndex = token.getTokenIndex(); final Parser parser = (Parser) recognizer; if (parser.getRuleInvocationStack().contains("nameDefinitionWithEOF")) { error = generateInvalidVariableError(offendingSymbol, line, charPositionInLine, e, token); } else if ("}".equals(token.getText()) && tokenIndex > 1 && ":".equals(parser.getTokenStream().get(tokenIndex - 1).getText())) { error = new SyntaxErrorEvent(FEELEvent.Severity.ERROR, Msg.createMessage(Msg.MISSING_EXPRESSION, parser.getTokenStream().get(tokenIndex - 2).getText()), e, line, charPositionInLine, offendingSymbol); } 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 if ("}".equals(token.getText()) && e != null && e.getRecognizer() instanceof Parser && ((Parser) e.getRecognizer()).getRuleInvocationStack().contains("key")) { return new SyntaxErrorEvent(FEELEvent.Severity.ERROR, Msg.createMessage(Msg.MISSING_EXPRESSION, e.getCtx().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); } } }