From ad0d4a6e7d3220f6eed2759c1a35ac849129db17 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Wed, 9 May 2018 20:54:22 +0200 Subject: [PATCH] Add yang-xpath-impl This adds an antlr-based XPath parser, which results in yang-xpath-api expressions. The grammar file (xpath.g4) is taken from https://github.com/antlr/grammars-v4/blob/master/xpath/xpath.g4 at commit a3ff48932b0168d93d184a988fa40723b60f6c27. JIRA: YANGTOOLS-877 Change-Id: Ic7cfdb2d8d132577840f910786f72ed75b17c79e Signed-off-by: Robert Varga --- common/artifacts/pom.xml | 12 + .../features-yangtools-experimental/pom.xml | 6 + features/odl-yangtools-exp-xpath-impl/pom.xml | 49 ++ features/pom.xml | 1 + yang/pom.xml | 1 + .../yang/xpath/api/YangLocationPath.java | 13 +- .../yang/xpath/api/YangNaryExpr.java | 9 + .../yang/xpath/api/YangNumberExpr.java | 1 - yang/yang-xpath-impl/pom.xml | 106 +++ .../src/main/antlr/instanceIdentifier.g4 | 89 +++ yang/yang-xpath-impl/src/main/antlr/xpath.g4 | 262 +++++++ .../xpath/impl/AntlrYangXPathExpression.java | 62 ++ .../xpath/impl/BigDecimalXPathParser.java | 92 +++ .../yang/xpath/impl/DoubleXPathParser.java | 79 ++ .../yangtools/yang/xpath/impl/Functions.java | 298 +++++++ .../impl/InstanceIdentifierLiteralExpr.java | 28 + .../yang/xpath/impl/QNameLiteralExpr.java | 35 + .../yang/xpath/impl/QNameSupport.java | 40 + .../yang/xpath/impl/XPathParser.java | 735 ++++++++++++++++++ .../yang/xpath/impl/package-info.java | 15 + .../yang/xpath/impl/XPathParserTest.java | 103 +++ 21 files changed, 2034 insertions(+), 2 deletions(-) create mode 100644 features/odl-yangtools-exp-xpath-impl/pom.xml create mode 100644 yang/yang-xpath-impl/pom.xml create mode 100644 yang/yang-xpath-impl/src/main/antlr/instanceIdentifier.g4 create mode 100644 yang/yang-xpath-impl/src/main/antlr/xpath.g4 create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/AntlrYangXPathExpression.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/BigDecimalXPathParser.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/DoubleXPathParser.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/Functions.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierLiteralExpr.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameLiteralExpr.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameSupport.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParser.java create mode 100644 yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/package-info.java create mode 100644 yang/yang-xpath-impl/src/test/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParserTest.java diff --git a/common/artifacts/pom.xml b/common/artifacts/pom.xml index 554944b6af..10e7733600 100644 --- a/common/artifacts/pom.xml +++ b/common/artifacts/pom.xml @@ -211,6 +211,11 @@ yang-xpath-api 0.1.10-SNAPSHOT + + org.opendaylight.yangtools + yang-xpath-impl + 0.1.10-SNAPSHOT + org.opendaylight.yangtools @@ -340,6 +345,13 @@ xml features + + org.opendaylight.yangtools + odl-yangtools-exp-xpath-impl + 0.1.10-SNAPSHOT + xml + features + diff --git a/features/features-yangtools-experimental/pom.xml b/features/features-yangtools-experimental/pom.xml index 8e6047a498..23de881d86 100644 --- a/features/features-yangtools-experimental/pom.xml +++ b/features/features-yangtools-experimental/pom.xml @@ -54,5 +54,11 @@ features xml + + org.opendaylight.yangtools + odl-yangtools-exp-xpath-impl + features + xml + diff --git a/features/odl-yangtools-exp-xpath-impl/pom.xml b/features/odl-yangtools-exp-xpath-impl/pom.xml new file mode 100644 index 0000000000..4c164cc2d1 --- /dev/null +++ b/features/odl-yangtools-exp-xpath-impl/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + + org.opendaylight.odlparent + single-feature-parent + 3.1.3 + + + + org.opendaylight.yangtools + odl-yangtools-exp-xpath-impl + 0.1.10-SNAPSHOT + feature + OpenDaylight :: Yangtools :: Experimental :: XPath API implementation + + + + + org.opendaylight.yangtools + yangtools-artifacts + 2.0.10-SNAPSHOT + import + pom + + + + + + + org.opendaylight.yangtools + odl-yangtools-exp-xpath-api + xml + features + + + org.opendaylight.yangtools + yang-xpath-impl + + + diff --git a/features/pom.xml b/features/pom.xml index 21f386ea5d..252218b13f 100644 --- a/features/pom.xml +++ b/features/pom.xml @@ -41,6 +41,7 @@ odl-exp-objcache odl-yangtools-exp-xpath-api + odl-yangtools-exp-xpath-impl features-yangtools-experimental diff --git a/yang/pom.xml b/yang/pom.xml index f94618d137..8144d320e1 100644 --- a/yang/pom.xml +++ b/yang/pom.xml @@ -41,6 +41,7 @@ yang-xpath-api + yang-xpath-impl yang-model-validator diff --git a/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangLocationPath.java b/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangLocationPath.java index be9351f96f..b3de1ee777 100644 --- a/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangLocationPath.java +++ b/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangLocationPath.java @@ -46,7 +46,7 @@ public class YangLocationPath implements YangExpr { @Override public final String toString() { - return addToStringAttributes(MoreObjects.toStringHelper(this)).toString(); + return addToStringAttributes(MoreObjects.toStringHelper(Step.class)).toString(); } protected ToStringHelper addToStringAttributes(final ToStringHelper helper) { @@ -406,4 +406,15 @@ public class YangLocationPath implements YangExpr { final YangLocationPath other = (YangLocationPath) obj; return isAbsolute() == other.isAbsolute() && getSteps().equals(other.getSteps()); } + + @Override + public final String toString() { + final ToStringHelper helper = MoreObjects.toStringHelper(YangLocationPath.class); + helper.add("absolute", isAbsolute()); + final List steps = getSteps(); + if (!steps.isEmpty()) { + helper.add("steps", steps); + } + return helper.toString(); + } } diff --git a/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNaryExpr.java b/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNaryExpr.java index a59705c7a6..fe513a8adb 100644 --- a/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNaryExpr.java +++ b/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNaryExpr.java @@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.xpath.api; import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.Nullable; @@ -46,4 +47,12 @@ public abstract class YangNaryExpr implements YangExpr { final YangNaryExpr other = (YangNaryExpr) obj; return getOperator().equals(other.getOperator()) && expressions.equals(other.expressions); } + + @Override + public final String toString() { + return MoreObjects.toStringHelper(YangNaryExpr.class) + .add("operator", getOperator()) + .add("expressions", getExpressions()) + .toString(); + } } diff --git a/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNumberExpr.java b/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNumberExpr.java index 85deadb77d..96f6bd7051 100644 --- a/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNumberExpr.java +++ b/yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNumberExpr.java @@ -83,7 +83,6 @@ public abstract class YangNumberExpr, N extends N } } - private static final long serialVersionUID = 1L; YangNumberExpr() { diff --git a/yang/yang-xpath-impl/pom.xml b/yang/yang-xpath-impl/pom.xml new file mode 100644 index 0000000000..e91cc91597 --- /dev/null +++ b/yang/yang-xpath-impl/pom.xml @@ -0,0 +1,106 @@ + + + + + + + org.opendaylight.odlparent + bundle-parent + 3.1.3 + + + + 4.0.0 + org.opendaylight.yangtools + yang-xpath-impl + 0.1.10-SNAPSHOT + bundle + ${project.artifactId} + YANG XPath parser + + + + + org.opendaylight.yangtools + yangtools-artifacts + 2.0.10-SNAPSHOT + import + pom + + + + + + + com.google.guava + guava + + + org.opendaylight.yangtools + concepts + + + org.opendaylight.yangtools + yang-common + + + org.opendaylight.yangtools + yang-xpath-api + + + + org.antlr + antlr4-runtime + + + org.mockito + mockito-core + + + junit + junit + test + + + ch.qos.logback + logback-classic + test + + + + + + + org.antlr + antlr4-maven-plugin + + + + antlr4 + + + + + src/main/antlr + ${project.build.directory}/generated-sources/antlr/org/opendaylight/yangtools/yang/xpath/impl + false + false + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + checkstyle.violationSeverity=error + + + + + + diff --git a/yang/yang-xpath-impl/src/main/antlr/instanceIdentifier.g4 b/yang/yang-xpath-impl/src/main/antlr/instanceIdentifier.g4 new file mode 100644 index 0000000000..eea2ee43ec --- /dev/null +++ b/yang/yang-xpath-impl/src/main/antlr/instanceIdentifier.g4 @@ -0,0 +1,89 @@ +grammar instanceIdentifier; + +@header { +package org.opendaylight.yangtools.yang.xpath.impl; +} + +/* + * YANG 1.1 instance-identifier grammar, as defined in + * https://tools.ietf.org/html/rfc7950#section-9.13 + * + * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +instanceIdentifier : ('/' pathArgument)+ + ; + +pathArgument : nodeIdentifier predicate? + ; + +nodeIdentifier : Identifier ':' Identifier + ; + +predicate : keyPredicate+ + | leafListPredicate + | pos + ; + +keyPredicate : '[' WSP? keyPredicateExpr WSP? ']' + ; + +keyPredicateExpr : nodeIdentifier eqQuotedString + ; + +leafListPredicate : '[' WSP? leafListPredicateExpr WSP? ']' + ; + +leafListPredicateExpr : '.' eqQuotedString + ; + +// Common tail of leafListPredicateExpr and keyPredicateExpr +eqQuotedString : WSP? '=' WSP? quotedString + ; + +pos : '[' WSP? PositiveIntegerValue WSP? ']' + ; + +quotedString : '\'' STRING '\'' + | '"' STRING '"' + ; + +Identifier : [a-zA-Z][a-zA-Z0-9_\-.]* + ; + +PositiveIntegerValue : [1-9][0-9]* + ; + +STRING : YANGCHAR+ + ; + +WSP : [ \t]+ + ; + +fragment +YANGCHAR : '\t'..'\n' + | '\r' + | '\u0020'..'\uD7FF' + | '\uE000'..'\uFDCF' + | '\uFDF0'..'\uFFFD' + | '\u{10000}'..'\u{1FFFD}' + | '\u{20000}'..'\u{2FFFD}' + | '\u{30000}'..'\u{3FFFD}' + | '\u{40000}'..'\u{4FFFD}' + | '\u{50000}'..'\u{5FFFD}' + | '\u{60000}'..'\u{6FFFD}' + | '\u{70000}'..'\u{7FFFD}' + | '\u{80000}'..'\u{8FFFD}' + | '\u{90000}'..'\u{9FFFD}' + | '\u{A0000}'..'\u{AFFFD}' + | '\u{B0000}'..'\u{BFFFD}' + | '\u{C0000}'..'\u{CFFFD}' + | '\u{D0000}'..'\u{DFFFD}' + | '\u{E0000}'..'\u{EFFFD}' + | '\u{F0000}'..'\u{FFFFD}' + | '\u{100000}'..'\u{10FFFD}' + ; + diff --git a/yang/yang-xpath-impl/src/main/antlr/xpath.g4 b/yang/yang-xpath-impl/src/main/antlr/xpath.g4 new file mode 100644 index 0000000000..1b948673a4 --- /dev/null +++ b/yang/yang-xpath-impl/src/main/antlr/xpath.g4 @@ -0,0 +1,262 @@ +grammar xpath; + +@header { +package org.opendaylight.yangtools.yang.xpath.impl; +} + +/* +XPath 1.0 grammar. Should conform to the official spec at +http://www.w3.org/TR/1999/REC-xpath-19991116. The grammar +rules have been kept as close as possible to those in the +spec, but some adjustmewnts were unavoidable. These were +mainly removing left recursion (spec seems to be based on +LR), and to deal with the double nature of the '*' token +(node wildcard and multiplication operator). See also +section 3.7 in the spec. These rule changes should make +no difference to the strings accepted by the grammar. + +Written by Jan-Willem van den Broek +Version 1.0 + +Do with this code as you will. +*/ +/* + Ported to Antlr4 by Tom Everett +*/ + + +main : expr + ; + +locationPath + : relativeLocationPath + | absoluteLocationPathNoroot + ; + +absoluteLocationPathNoroot + : '/' relativeLocationPath + | '//' relativeLocationPath + ; + +relativeLocationPath + : step (('/'|'//') step)* + ; + +step : axisSpecifier nodeTest predicate* + | abbreviatedStep + ; + +axisSpecifier + : AxisName '::' + | '@'? + ; + +nodeTest: nameTest + | NodeType '(' ')' + | 'processing-instruction' '(' Literal ')' + ; + +predicate + : '[' expr ']' + ; + +abbreviatedStep + : '.' + | '..' + ; + +expr : orExpr + ; + +primaryExpr + : variableReference + | '(' expr ')' + | Literal + | Number + | functionCall + ; + +functionCall + : functionName '(' ( expr ( ',' expr )* )? ')' + ; + +unionExprNoRoot + : pathExprNoRoot ('|' unionExprNoRoot)? + | '/' '|' unionExprNoRoot + ; + +pathExprNoRoot + : locationPath + | filterExpr (('/'|'//') relativeLocationPath)? + ; + +filterExpr + : primaryExpr predicate* + ; + +orExpr : andExpr ('or' andExpr)* + ; + +andExpr : equalityExpr ('and' equalityExpr)* + ; + +equalityExpr + : relationalExpr (('='|'!=') relationalExpr)* + ; + +relationalExpr + : additiveExpr (('<'|'>'|'<='|'>=') additiveExpr)* + ; + +additiveExpr + : multiplicativeExpr (('+'|'-') multiplicativeExpr)* + ; + +multiplicativeExpr + : unaryExprNoRoot (('*'|'div'|'mod') multiplicativeExpr)? + | '/' (('div'|'mod') multiplicativeExpr)? + ; + +unaryExprNoRoot + : '-'* unionExprNoRoot + ; + +qName : nCName (':' nCName)? + ; + +// Does not match NodeType, as per spec. +functionName + : nCName ':' nCName + | NCName + | AxisName + ; + +variableReference + : '$' qName + ; + +nameTest: '*' + | nCName ':' '*' + | qName + ; + +nCName : NCName + | AxisName + | NodeType + ; + +NodeType: 'comment' + | 'text' + | 'processing-instruction' + | 'node' + ; + +Number : Digits ('.' Digits?)? + | '.' Digits + ; + +fragment +Digits : ('0'..'9')+ + ; + +AxisName: 'ancestor' + | 'ancestor-or-self' + | 'attribute' + | 'child' + | 'descendant' + | 'descendant-or-self' + | 'following' + | 'following-sibling' + | 'namespace' + | 'parent' + | 'preceding' + | 'preceding-sibling' + | 'self' + ; + + + PATHSEP + :'/'; + ABRPATH + : '//'; + LPAR + : '('; + RPAR + : ')'; + LBRAC + : '['; + RBRAC + : ']'; + MINUS + : '-'; + PLUS + : '+'; + DOT + : '.'; + MUL + : '*'; + DOTDOT + : '..'; + AT + : '@'; + COMMA + : ','; + PIPE + : '|'; + LESS + : '<'; + MORE_ + : '>'; + LE + : '<='; + GE + : '>='; + COLON + : ':'; + CC + : '::'; + APOS + : '\''; + QUOT + : '"'; + +Literal : '"' ~'"'* '"' + | '\'' ~'\''* '\'' + ; + +Whitespace + : (' '|'\t'|'\n'|'\r')+ ->skip + ; + +NCName : NCNameStartChar NCNameChar* + ; + +fragment +NCNameStartChar + : 'A'..'Z' + | '_' + | 'a'..'z' + | '\u00C0'..'\u00D6' + | '\u00D8'..'\u00F6' + | '\u00F8'..'\u02FF' + | '\u0370'..'\u037D' + | '\u037F'..'\u1FFF' + | '\u200C'..'\u200D' + | '\u2070'..'\u218F' + | '\u2C00'..'\u2FEF' + | '\u3001'..'\uD7FF' + | '\uF900'..'\uFDCF' + | '\uFDF0'..'\uFFFD' +// Unfortunately, java escapes can't handle this conveniently, +// as they're limited to 4 hex digits. TODO. +// | '\U010000'..'\U0EFFFF' + ; + +fragment +NCNameChar + : NCNameStartChar | '-' | '.' | '0'..'9' + | '\u00B7' | '\u0300'..'\u036F' + | '\u203F'..'\u2040' + ; + + diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/AntlrYangXPathExpression.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/AntlrYangXPathExpression.java new file mode 100644 index 0000000000..fa663d2ded --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/AntlrYangXPathExpression.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import static java.util.Objects.requireNonNull; + +import javax.xml.xpath.XPathExpressionException; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.xpath.api.YangExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath; +import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression; + +final class AntlrYangXPathExpression implements YangXPathExpression { + private final QNameSupport qnameSupport; + private final YangExpr rootExpr; + private final String origStr; + + AntlrYangXPathExpression(final QNameSupport qnameSupport, final YangExpr rootExpr, final String origStr) { + this.qnameSupport = requireNonNull(qnameSupport); + this.rootExpr = requireNonNull(rootExpr); + this.origStr = requireNonNull(origStr); + } + + @Override + public YangExpr getRootExpr() { + return rootExpr; + } + + @Override + public QName interpretAsQName(final YangLiteralExpr expr) throws XPathExpressionException { + // We are eagerly interpreting PrefixedName-compliant strings, hence they have a specific subclass + if (expr instanceof QNameLiteralExpr) { + return ((QNameLiteralExpr) expr).getQName(); + } + + try { + // Deal with UnprefixedNames by interpreting them in implicit namespace + return qnameSupport.createQName(expr.getLiteral()); + } catch (IllegalArgumentException e) { + throw new XPathExpressionException(e); + } + } + + @Override + public YangLocationPath interpretAsInstanceIdentifier(final YangLiteralExpr expr) throws XPathExpressionException { + if (expr instanceof InstanceIdentifierLiteralExpr) { + return YangLocationPath.of(true, ((InstanceIdentifierLiteralExpr)expr).getSteps()); + } + throw new XPathExpressionException("Invalid instance-identifier " + expr); + } + + @Override + public String toString() { + return origStr; + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/BigDecimalXPathParser.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/BigDecimalXPathParser.java new file mode 100644 index 0000000000..37a6131cbb --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/BigDecimalXPathParser.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.function.Function; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator; +import org.opendaylight.yangtools.yang.xpath.api.YangBooleanConstantExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangNumberExpr.YangBigDecimal; + +final class BigDecimalXPathParser extends XPathParser { + private static final YangBigDecimal ZERO = YangBigDecimal.of(BigDecimal.ZERO); + private static final YangBigDecimal ONE = YangBigDecimal.of(BigDecimal.ONE); + private static final YangBigDecimal TEN = YangBigDecimal.of(BigDecimal.TEN); + + BigDecimalXPathParser(final QNameModule implicitNamespace, final Function prefixes) { + super(implicitNamespace, prefixes); + } + + @Override + YangBigDecimal createNumber(final String str) { + switch (str) { + case "0": + return ZERO; + case "1": + return ONE; + case "10": + return TEN; + default: + return YangBigDecimal.of(new BigDecimal(str)); + } + } + + @Override + YangBigDecimal negateNumber(final YangBigDecimal number) { + return YangBigDecimal.of(number.getNumber().negate()); + } + + @Override + Optional simplifyNumbers(final YangBinaryOperator operator, final YangBigDecimal left, + final YangBigDecimal right) { + final BigDecimal l = left.getNumber(); + final BigDecimal r = right.getNumber(); + + final BigDecimal result; + switch (operator) { + case DIV: + result = l.divide(r); + break; + case EQUALS: + return of(l.equals(r)); + case GT: + return of(l.compareTo(r) > 0); + case GTE: + return of(l.compareTo(r) >= 0); + case LT: + return of(l.compareTo(r) < 0); + case LTE: + return of(l.compareTo(r) <= 0); + case MINUS: + result = l.subtract(r); + break; + case MOD: + result = l.remainder(r); + break; + case MUL: + result = l.multiply(r); + break; + case NOT_EQUALS: + return of(!l.equals(r)); + case PLUS: + result = l.add(r); + break; + default: + throw new IllegalStateException("Unhandled operator " + operator); + } + + return Optional.of(YangBigDecimal.of(result)); + } + + private static Optional of(final boolean value) { + return Optional.of(YangBooleanConstantExpr.of(value)); + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/DoubleXPathParser.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/DoubleXPathParser.java new file mode 100644 index 0000000000..40e459c6bb --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/DoubleXPathParser.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import java.util.Optional; +import java.util.function.Function; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator; +import org.opendaylight.yangtools.yang.xpath.api.YangBooleanConstantExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangNumberExpr.YangDouble; + +final class DoubleXPathParser extends XPathParser { + + DoubleXPathParser(final QNameModule implicitNamespace, final Function prefixes) { + super(implicitNamespace, prefixes); + } + + @Override + YangDouble createNumber(final String str) { + return YangDouble.of(Double.parseDouble(str)); + } + + @Override + YangDouble negateNumber(final YangDouble number) { + return YangDouble.of(-number.getValue()); + } + + @Override + Optional simplifyNumbers(final YangBinaryOperator operator, final YangDouble left, + final YangDouble right) { + final double l = left.getValue(); + final double r = right.getValue(); + + final double result; + switch (operator) { + case DIV: + result = l / r; + break; + case EQUALS: + return of(l == r); + case GT: + return of(l > r); + case GTE: + return of(l >= r); + case LT: + return of(l < r); + case LTE: + return of(l <= r); + case MINUS: + result = l - r; + break; + case MOD: + result = l % r; + break; + case MUL: + result = l * r; + break; + case NOT_EQUALS: + return of(l != r); + case PLUS: + result = l + r; + break; + default: + throw new IllegalStateException("Unhandled operator " + operator); + } + + return Optional.of(YangDouble.of(result)); + } + + private static Optional of(final boolean value) { + return Optional.of(YangBooleanConstantExpr.of(value)); + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/Functions.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/Functions.java new file mode 100644 index 0000000000..dc673978c9 --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/Functions.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.List; +import org.opendaylight.yangtools.yang.xpath.api.YangBooleanConstantExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangFunction; +import org.opendaylight.yangtools.yang.xpath.api.YangFunctionCallExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangNumberExpr; + +final class Functions { + private static final YangFunctionCallExpr CURRENT = YangFunctionCallExpr.of(YangFunction.CURRENT.getIdentifier()); + private static final YangFunctionCallExpr LAST = YangFunctionCallExpr.of(YangFunction.LAST.getIdentifier()); + private static final YangFunctionCallExpr LOCAL_NAME = YangFunctionCallExpr.of( + YangFunction.LOCAL_NAME.getIdentifier()); + private static final YangFunctionCallExpr NAME = YangFunctionCallExpr.of(YangFunction.NAME.getIdentifier()); + private static final YangFunctionCallExpr NAMESPACE_URI = YangFunctionCallExpr.of( + YangFunction.NAMESPACE_URI.getIdentifier()); + private static final YangFunctionCallExpr NORMALIZE_SPACE = YangFunctionCallExpr.of( + YangFunction.NORMALIZE_SPACE.getIdentifier()); + private static final YangFunctionCallExpr NUMBER = YangFunctionCallExpr.of(YangFunction.NUMBER.getIdentifier()); + static final YangFunctionCallExpr POSITION = YangFunctionCallExpr.of(YangFunction.POSITION.getIdentifier()); + private static final YangFunctionCallExpr STRING = YangFunctionCallExpr.of(YangFunction.STRING.getIdentifier()); + private static final YangFunctionCallExpr STRING_LENGTH = YangFunctionCallExpr.of( + YangFunction.STRING_LENGTH.getIdentifier()); + + private Functions() { + + } + + static YangExpr functionToExpr(final YangFunction func, final List args) { + switch (func) { + case BIT_IS_SET: + checkArgument(args.size() == 2, "bit-is-set(node-set, string) takes two arguments"); + break; + case BOOLEAN: + return booleanExpr(args); + case CEILING: + checkArgument(args.size() == 1, "ceiling(number) takes one argument"); + // TODO: constant folding requires math support + break; + case CONCAT: + return concatExpr(args); + case CONTAINS: + return containsExpr(args); + case COUNT: + checkArgument(args.size() == 1, "count(node-set) takes one argument"); + // TODO: constant folding requires math support + break; + case CURRENT: + checkArgument(args.isEmpty(), "current() does not take any arguments"); + return CURRENT; + case DEREF: + checkArgument(args.size() == 1, "deref(node-set) takes one argument"); + break; + case DERIVED_FROM: + return derivedFromExpr(args); + case DERIVED_FROM_OR_SELF: + return derivedFromOrSelfExpr(args); + case ENUM_VALUE: + checkArgument(args.size() == 1, "enum-value(node-set) takes one argument"); + break; + case FALSE: + checkArgument(args.isEmpty(), "false() does not take any arguments"); + return YangBooleanConstantExpr.FALSE; + case FLOOR: + checkArgument(args.size() == 1, "floor(number) takes one argument"); + // TODO: constant folding requires math support + break; + case ID: + checkArgument(args.size() == 1, "id(object) takes one argument"); + break; + case LANG: + checkArgument(args.size() == 1, "lang(string) takes one argument"); + break; + case LAST: + checkArgument(args.isEmpty(), "last() does not take any arguments"); + return LAST; + case LOCAL_NAME: + checkArgument(args.size() <= 1, "local-name(node-set?) takes at most one argument"); + if (args.isEmpty()) { + return LOCAL_NAME; + } + break; + case NAME: + checkArgument(args.size() <= 1, "name(node-set?) takes at most one argument"); + if (args.isEmpty()) { + return NAME; + } + break; + case NAMESPACE_URI: + checkArgument(args.size() <= 1, "namespace-uri(node-set?) takes at most one argument"); + if (args.isEmpty()) { + return NAMESPACE_URI; + } + break; + case NORMALIZE_SPACE: + return normalizeSpaceExpr(args); + case NOT: + return notExpr(args); + case NUMBER: + return numberExpr(args); + case POSITION: + checkArgument(args.isEmpty(), "position() does not take any arguments"); + return POSITION; + case RE_MATCH: + checkArgument(args.size() == 2, "re-match(string, string) takes two arguments"); + // TODO: static analysis requires XSD regex support -- we should validate args[1] and match it to + // args[0] if that is a literal + break; + case ROUND: + checkArgument(args.size() == 1, "round(number) takes one argument"); + // TODO: constant folding requires math support + break; + case STARTS_WITH: + return startsWithExpr(args); + case STRING: + return stringExpr(args); + case STRING_LENGTH: + return stringLengthExpr(args); + case SUBSTRING: + return substringExpr(args); + case SUBSTRING_AFTER: + return substringAfterExpr(args); + case SUBSTRING_BEFORE: + return substringBeforeExpr(args); + case SUM: + checkArgument(args.size() == 1, "sub(node-set) takes one argument"); + // TODO: constant folding requires math support + break; + case TRANSLATE: + checkArgument(args.size() == 3, "translate(string, string, string) takes three arguments"); + // TODO: constant folding? + break; + case TRUE: + checkArgument(args.isEmpty(), "true() does not take any arguments"); + return YangBooleanConstantExpr.TRUE; + default: + throw new IllegalStateException("Unhandled function " + func); + } + + return YangFunctionCallExpr.of(func.getIdentifier(), args); + } + + private static YangExpr booleanExpr(final List args) { + checkArgument(args.size() == 1, "boolean(object) takes one argument"); + final YangExpr arg = args.get(0); + if (arg instanceof YangBooleanConstantExpr) { + return arg; + } + if (arg instanceof YangLiteralExpr) { + return YangBooleanConstantExpr.of(((YangLiteralExpr) arg).getLiteral().isEmpty()); + } + // TODO: handling YangNumberExpr requires math support + return YangFunctionCallExpr.of(YangFunction.BOOLEAN.getIdentifier(), args); + } + + private static YangExpr concatExpr(final List args) { + checkArgument(args.size() >= 2, "concat(string, string, string*) takes at least two arguments"); + + // TODO: constant folding + + return YangFunctionCallExpr.of(YangFunction.CONCAT.getIdentifier(), args); + } + + private static YangExpr containsExpr(final List args) { + checkArgument(args.size() == 2, "contains(string, string) takes two arguments"); + final YangExpr first = args.get(0); + if (first instanceof YangLiteralExpr) { + final YangExpr second = args.get(1); + if (second instanceof YangLiteralExpr) { + return YangBooleanConstantExpr.of( + ((YangLiteralExpr) first).getLiteral().contains(((YangLiteralExpr) second).getLiteral())); + } + } + + // TODO: handling YangNumberExpr requires math support + return YangFunctionCallExpr.of(YangFunction.CONTAINS.getIdentifier(), args); + } + + private static YangExpr derivedFromExpr(final List args) { + checkArgument(args.size() == 2, "derived-from(node-set, string) takes two arguments"); + // FIXME: coerce second arg to a QName + return YangFunctionCallExpr.of(YangFunction.DERIVED_FROM.getIdentifier(), args); + } + + private static YangExpr derivedFromOrSelfExpr(final List args) { + checkArgument(args.size() == 2, "derived-from-or-self(node-set, string) takes two arguments"); + // FIXME: coerce second arg to a QName + return YangFunctionCallExpr.of(YangFunction.DERIVED_FROM_OR_SELF.getIdentifier(), args); + } + + private static YangExpr notExpr(final List args) { + checkArgument(args.size() == 1, "not(boolean) takes one argument"); + final YangExpr arg = args.get(0); + if (arg instanceof YangBooleanConstantExpr) { + return YangBooleanConstantExpr.of(((YangBooleanConstantExpr) arg).getValue()); + } + + return YangFunctionCallExpr.of(YangFunction.NOT.getIdentifier(), args); + } + + private static YangExpr normalizeSpaceExpr(final List args) { + checkArgument(args.size() <= 1, "number(object?) takes at most one argument"); + if (args.isEmpty()) { + return NORMALIZE_SPACE; + } + final YangExpr arg = args.get(0); + if (arg instanceof YangLiteralExpr) { + // TODO: normalize value + } + + return YangFunctionCallExpr.of(YangFunction.NORMALIZE_SPACE.getIdentifier(), args); + } + + private static YangExpr numberExpr(final List args) { + checkArgument(args.size() <= 1, "number(object?) takes at most one argument"); + if (args.isEmpty()) { + return NUMBER; + } + + final YangExpr arg = args.get(0); + if (arg instanceof YangNumberExpr) { + return arg; + } + + // TODO: constant literal folding requires math support + return YangFunctionCallExpr.of(YangFunction.NUMBER.getIdentifier(), args); + } + + private static YangExpr startsWithExpr(final List args) { + checkArgument(args.size() == 2, "starts-with(string, string) takes two arguments"); + + // TODO: constant folding + + return YangFunctionCallExpr.of(YangFunction.STARTS_WITH.getIdentifier(), args); + } + + private static YangExpr substringBeforeExpr(final List args) { + checkArgument(args.size() == 2, "substring-before(string, string) takes two arguments"); + + // TODO: constant folding + + return YangFunctionCallExpr.of(YangFunction.SUBSTRING_BEFORE.getIdentifier(), args); + } + + private static YangExpr substringAfterExpr(final List args) { + checkArgument(args.size() == 2, "substring-after(string, string) takes two arguments"); + + // TODO: constant folding + + return YangFunctionCallExpr.of(YangFunction.SUBSTRING_AFTER.getIdentifier(), args); + } + + private static YangExpr substringExpr(final List args) { + final int size = args.size(); + checkArgument(size == 2 || size == 3, "substring-(string, number, number?) takes two or three arguments"); + + // TODO: constant folding + + return YangFunctionCallExpr.of(YangFunction.SUBSTRING.getIdentifier(), args); + } + + private static YangExpr stringExpr(final List args) { + checkArgument(args.size() <= 1, "string(object?) takes at most one argument"); + if (args.isEmpty()) { + return STRING; + } + + final YangExpr arg = args.get(0); + if (arg instanceof YangLiteralExpr) { + return arg; + } + + // TODO: handling YangNumberExpr requires math support + return YangFunctionCallExpr.of(YangFunction.STRING.getIdentifier(), args); + } + + private static YangExpr stringLengthExpr(final List args) { + checkArgument(args.size() <= 1, "string(object?) takes at most one argument"); + if (args.isEmpty()) { + return STRING_LENGTH; + } + + // TODO: constant literal requires math support + + return YangFunctionCallExpr.of(YangFunction.STRING_LENGTH.getIdentifier(), args); + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierLiteralExpr.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierLiteralExpr.java new file mode 100644 index 0000000000..d9362861e6 --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierLiteralExpr.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step; + +final class InstanceIdentifierLiteralExpr extends YangLiteralExpr { + private static final long serialVersionUID = 1L; + + private final List steps; + + InstanceIdentifierLiteralExpr(final String str, final List steps) { + super(str); + this.steps = ImmutableList.copyOf(steps); + } + + List getSteps() { + return steps; + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameLiteralExpr.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameLiteralExpr.java new file mode 100644 index 0000000000..6f8d9a2b0e --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameLiteralExpr.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr; + +/** + * Eagerly-bound literal interpreted a PrefixName. + * + * @author Robert Varga + */ +final class QNameLiteralExpr extends YangLiteralExpr { + private static final long serialVersionUID = 1L; + + private final QName qname; + + QNameLiteralExpr(final String str, final QName qname) { + super(str); + checkArgument(str.endsWith(qname.getLocalName())); + this.qname = requireNonNull(qname); + } + + QName getQName() { + return qname; + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameSupport.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameSupport.java new file mode 100644 index 0000000000..68a2f8dfcb --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameSupport.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +import java.util.Optional; +import java.util.function.Function; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; + +final class QNameSupport { + private final Function prefixes; + private final QNameModule implicitNamespace; + + QNameSupport(final QNameModule implicitNamespace, final Function prefixes) { + this.implicitNamespace = requireNonNull(implicitNamespace); + this.prefixes = requireNonNull(prefixes); + } + + QName createQName(final String localName) { + return QName.create(implicitNamespace, localName); + } + + QName createQName(final String prefix, final String localName) { + final QNameModule namespace = prefixes.apply(prefix); + checkArgument(namespace != null, "Failed to lookup namespace for prefix %s", prefix); + return QName.create(namespace, localName); + } + + Optional resolvePrefix(final String prefix) { + return Optional.ofNullable(prefixes.apply(requireNonNull(prefix))); + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParser.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParser.java new file mode 100644 index 0000000000..33c8e7f8bd --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParser.java @@ -0,0 +1,735 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Verify.verify; +import static com.google.common.base.Verify.verifyNotNull; + +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import javax.xml.xpath.XPathExpressionException; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.common.YangConstants; +import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator; +import org.opendaylight.yangtools.yang.xpath.api.YangBooleanConstantExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangFilterExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangFunction; +import org.opendaylight.yangtools.yang.xpath.api.YangFunctionCallExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.AxisStep; +import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step; +import org.opendaylight.yangtools.yang.xpath.api.YangNaryExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangNaryOperator; +import org.opendaylight.yangtools.yang.xpath.api.YangNegateExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangNumberExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangQNameExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangVariableReferenceExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis; +import org.opendaylight.yangtools.yang.xpath.api.YangXPathExpression; +import org.opendaylight.yangtools.yang.xpath.api.YangXPathNodeType; +import org.opendaylight.yangtools.yang.xpath.api.YangXPathParser; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.EqQuotedStringContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.InstanceIdentifierContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.KeyPredicateContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.KeyPredicateExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.LeafListPredicateContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.LeafListPredicateExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.NodeIdentifierContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.PathArgumentContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.PosContext; +import org.opendaylight.yangtools.yang.xpath.impl.instanceIdentifierParser.QuotedStringContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.AbbreviatedStepContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.AbsoluteLocationPathNorootContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.AdditiveExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.AndExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.AxisSpecifierContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.EqualityExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.ExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.FilterExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.FunctionCallContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.FunctionNameContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.LocationPathContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.MultiplicativeExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.NCNameContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.NameTestContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.NodeTestContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.OrExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.PathExprNoRootContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.PredicateContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.PrimaryExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.QNameContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.RelationalExprContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.RelativeLocationPathContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.StepContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.UnaryExprNoRootContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.UnionExprNoRootContext; +import org.opendaylight.yangtools.yang.xpath.impl.xpathParser.VariableReferenceContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ANTLR-based XPath parser. Uses {@code xpath.g4} ANTLR grammar. + * + * @author Robert Varga + */ +abstract class XPathParser> implements YangXPathParser { + private static final Logger LOG = LoggerFactory.getLogger(XPathParser.class); + private static final Map BINARY_OPERATORS = Maps.uniqueIndex( + Arrays.asList(YangBinaryOperator.values()), YangBinaryOperator::toString); + private static final Map NODE_TYPES = Maps.uniqueIndex(Arrays.asList( + YangXPathNodeType.values()), YangXPathNodeType::toString); + private static final Map XPATH_AXES = Maps.uniqueIndex(Arrays.asList(YangXPathAxis.values()), + YangXPathAxis::toString); + private static final Map YANG_FUNCTIONS = Maps.uniqueIndex(Arrays.asList( + YangFunction.values()), YangFunction::getIdentifier); + + // Cached for checks in hot path + private static final AxisStep SELF_STEP = YangXPathAxis.SELF.asStep(); + + private final QNameSupport qnameSupport; + + XPathParser(final QNameModule implicitNamespace, final Function prefixes) { + qnameSupport = new QNameSupport(implicitNamespace, prefixes); + } + + @Override + public YangXPathExpression parseExpression(final String xpath) throws XPathExpressionException { + // Create a parser and disconnect it from console error output + final xpathParser parser = new xpathParser(new CommonTokenStream(new xpathLexer( + CharStreams.fromString(xpath)))); + parser.removeErrorListeners(); + final List errors = new ArrayList<>(); + parser.addErrorListener(new BaseErrorListener() { + @Override + public void syntaxError(final @Nullable Recognizer recognizer, final @Nullable Object offendingSymbol, + final int line, final int charPositionInLine, final @Nullable String msg, + final @Nullable RecognitionException cause) { + final XPathExpressionException ex = new XPathExpressionException(msg); + ex.initCause(cause); + if (errors.isEmpty()) { + errors.add(ex); + } else { + errors.get(0).addSuppressed(ex); + } + } + }); + + final YangExpr expr = parseExpr(parser.main().expr()); + if (!errors.isEmpty()) { + throw errors.get(0); + } + + return new AntlrYangXPathExpression(qnameSupport, expr, xpath); + } + + /** + * Parse and simplify an XPath expression in {@link ExprContext} representation. + * + * @param expr ANTLR ExprContext + * @return A {@link YangExpr} + * @throws NullPointerException if {@code expr} is null + * @throws IllegalArgumentException if {@code expr} references an unbound prefix + */ + private YangExpr parseExpr(final ExprContext expr) { + final OrExprContext or = expr.orExpr(); + final int size = or.getChildCount(); + if (size == 1) { + return parseAnd(getChild(or, AndExprContext.class, 0)); + } + final List tmp = new ArrayList<>((size + 1) / 2); + for (int i = 0; i < size; i += 2) { + tmp.add(parseAnd(getChild(or, AndExprContext.class, i))); + } + return YangNaryOperator.OR.exprWith(tmp); + } + + /** + * Create a {@link YangNumberExpr} backed by specified string. + * + * @param str String, matching {@link xpathParser#Number} production. + * @return number expression + * @throws NullPointerException if {@code str} is null + */ + abstract N createNumber(String str); + + /** + * Create a {@link YangNumberExpr} representing the negated value of a number. + * + * @param number input number + * @return negated number expression + * @throws NullPointerException if {@code number} is null + */ + abstract N negateNumber(N number); + + private YangExpr parseAdditive(final AdditiveExprContext expr) { + final Iterator it = expr.children.iterator(); + final YangExpr first = parseMultiplicative(nextContext(it, MultiplicativeExprContext.class)); + return it.hasNext() ? parseAdditiveExpr(first, it) : first; + } + + private YangExpr parseAnd(final AndExprContext expr) { + final int size = expr.getChildCount(); + if (size == 1) { + return parseEquality(getChild(expr, EqualityExprContext.class, 0)); + } + final List tmp = new ArrayList<>((size + 1) / 2); + for (int i = 0; i < size; i += 2) { + tmp.add(parseEquality(getChild(expr, EqualityExprContext.class, i))); + } + return YangNaryOperator.AND.exprWith(tmp); + } + + private YangExpr parseEquality(final EqualityExprContext expr) { + final Iterator it = expr.children.iterator(); + final YangExpr first = parseRelational(nextContext(it, RelationalExprContext.class)); + return it.hasNext() ? parseEqualityExpr(first, it) : first; + } + + private YangExpr parseFilter(final FilterExprContext expr) { + final Iterator it = expr.children.iterator(); + final YangExpr first = parsePrimary(nextContext(it, PrimaryExprContext.class)); + return it.hasNext() ? YangFilterExpr.of(first, ImmutableList.copyOf(Iterators.transform(it, + tree -> parsePredicate(verifyTree(PredicateContext.class, tree))))) + : first; + } + + private YangExpr parseFunctionCall(final FunctionCallContext expr) { + // We are mapping functions to RFC7950 YIN namespace, to keep us consistent with type/statement definitions + + final FunctionNameContext name = getChild(expr, FunctionNameContext.class, 0); + final QName parsed; + switch (name.getChildCount()) { + case 1: + parsed = QName.create(YangConstants.RFC6020_YIN_MODULE, name.getChild(0).getText()); + break; + case 3: + parsed = qnameSupport.createQName(name.getChild(0).getText(), name.getChild(2).getText()); + break; + default: + throw illegalShape(name); + } + + final List args = ImmutableList.copyOf(Lists.transform(expr.expr(), this::parseExpr)); + final YangFunction func = YANG_FUNCTIONS.get(parsed); + if (func != null) { + return Functions.functionToExpr(func, args); + } + + checkArgument(!YangConstants.RFC6020_YIN_MODULE.equals(parsed.getModule()), "Unknown default function %s", + parsed); + return YangFunctionCallExpr.of(parsed, args); + } + + private YangLocationPath parseLocationPath(final LocationPathContext expr) { + verifyChildCount(expr, 1); + final ParseTree first = expr.getChild(0); + if (first instanceof RelativeLocationPathContext) { + return parseRelativeLocationPath((RelativeLocationPathContext) first); + } + + final AbsoluteLocationPathNorootContext abs = verifyTree(AbsoluteLocationPathNorootContext.class, first); + verifyChildCount(abs, 2); + + final Deque steps = parseLocationPathSteps(getChild(abs, RelativeLocationPathContext.class, 1)); + switch (getTerminalType(abs, 0)) { + case xpathParser.PATHSEP: + break; + case xpathParser.ABRPATH: + steps.addFirst(YangXPathAxis.DESCENDANT_OR_SELF.asStep()); + break; + default: + throw illegalShape(abs); + } + + return YangLocationPath.of(true, steps); + } + + private YangExpr parseMultiplicative(final MultiplicativeExprContext expr) { + final ParseTree first = expr.getChild(0); + final YangExpr left; + if (first instanceof UnaryExprNoRootContext) { + left = parseUnary((UnaryExprNoRootContext) first); + } else { + left = YangLocationPath.root(); + } + if (expr.getChildCount() == 1) { + return left; + } + + verifyChildCount(expr, 3); + final YangBinaryOperator operator = parseOperator(expr.getChild(1)); + final YangExpr right = parseMultiplicative(getChild(expr, MultiplicativeExprContext.class, 2)); + final Optional simple = simplifyNumbers(operator, left, right); + return simple.isPresent() ? simple.get() : operator.exprWith(left, right); + } + + private YangExpr parsePathExpr(final PathExprNoRootContext expr) { + final ParseTree first = expr.getChild(0); + if (first instanceof LocationPathContext) { + return parseLocationPath((LocationPathContext) first); + } + + final YangExpr filter = parseFilter(verifyTree(FilterExprContext.class, first)); + if (expr.getChildCount() == 1) { + return filter; + } + + verifyChildCount(expr, 3); + return parseOperator(expr.getChild(1)).exprWith(filter, + parseRelativeLocationPath(getChild(expr, RelativeLocationPathContext.class, 2))); + } + + private YangExpr parsePredicate(final PredicateContext expr) { + verifyChildCount(expr, 3); + return parseExpr(getChild(expr, ExprContext.class, 1)); + } + + private YangExpr parsePrimary(final PrimaryExprContext expr) { + if (expr.getChildCount() == 3) { + return parseExpr(getChild(expr, ExprContext.class, 1)); + } + + verifyChildCount(expr, 1); + final ParseTree first = expr.getChild(0); + if (first instanceof TerminalNode) { + return parseTerminal((TerminalNode) first); + } + if (first instanceof FunctionCallContext) { + return parseFunctionCall((FunctionCallContext) first); + } + if (first instanceof VariableReferenceContext) { + return YangVariableReferenceExpr.of(parseQName(((VariableReferenceContext) first).qName())); + } + throw illegalShape(first); + } + + private YangExpr parseRelational(final RelationalExprContext expr) { + final Iterator it = expr.children.iterator(); + final YangExpr first = parseAdditive(nextContext(it, AdditiveExprContext.class)); + return it.hasNext() ? parseRelationalExpr(first, it) : first; + } + + private Deque parseLocationPathSteps(final RelativeLocationPathContext expr) { + final Deque steps = new ArrayDeque<>(expr.getChildCount()); + final Iterator it = expr.children.iterator(); + steps.add(parseStep(nextContext(it, StepContext.class))); + + while (it.hasNext()) { + final ParseTree tree = it.next(); + switch (verifyTerminal(tree).getSymbol().getType()) { + case xpathParser.PATHSEP: + break; + case xpathParser.ABRPATH: + steps.add(YangXPathAxis.DESCENDANT_OR_SELF.asStep()); + break; + default: + throw illegalShape(tree); + } + + // Parse step and add it if it's not SELF_STEP + final Step step = parseStep(nextContext(it, StepContext.class)); + if (!SELF_STEP.equals(step)) { + steps.add(step); + } + } + + return steps; + } + + private YangLocationPath parseRelativeLocationPath(final RelativeLocationPathContext expr) { + return YangLocationPath.of(false, parseLocationPathSteps(expr)); + } + + private YangExpr parseTerminal(final TerminalNode term) { + final String text = term.getText(); + switch (term.getSymbol().getType()) { + case xpathParser.Literal: + // We have to strip quotes + return parseLiteral(text.substring(1, text.length() - 1)); + case xpathParser.Number: + return createNumber(text); + default: + throw illegalShape(term); + } + } + + private YangLiteralExpr parseLiteral(final String text) { + if (text.isEmpty()) { + return YangLiteralExpr.empty(); + } + if (text.charAt(0) == '/') { + return parseLocationLiteral(text); + } + return parseQNameLiteral(text); + } + + private YangLiteralExpr parseLocationLiteral(final String text) { + final instanceIdentifierParser parser = new instanceIdentifierParser(new CommonTokenStream(new xpathLexer( + CharStreams.fromString(text)))); + parser.removeErrorListeners(); + + final InstanceIdentifierContext id = parser.instanceIdentifier(); + final int length = id.getChildCount(); + final List steps = new ArrayList<>(length / 2); + for (int i = 1; i < length; i += 2) { + steps.add(parsePathArgument(getChild(id, PathArgumentContext.class, i))); + } + + return new InstanceIdentifierLiteralExpr(text, steps); + } + + private Step parsePathArgument(final PathArgumentContext expr) { + final QName qname = parseInstanceIdentifierQName(getChild(expr, NodeIdentifierContext.class, 0)); + switch (expr.getChildCount()) { + case 1: + return YangXPathAxis.CHILD.asStep(qname, ImmutableSet.of()); + case 2: + return YangXPathAxis.CHILD.asStep(qname, + parsePathArgumentPredicate(getChild(expr, instanceIdentifierParser.PredicateContext.class, 1))); + default: + throw illegalShape(expr); + } + } + + private Collection parsePathArgumentPredicate(final instanceIdentifierParser.PredicateContext expr) { + final ParseTree first = expr.getChild(0); + if (first instanceof LeafListPredicateContext) { + return ImmutableSet.of(YangBinaryOperator.EQUALS.exprWith(YangLocationPath.self(), + parseEqStringValue(getChild(((LeafListPredicateContext) first) + .getChild(LeafListPredicateExprContext.class, 0), EqQuotedStringContext.class, 1)))); + } else if (first instanceof PosContext) { + return ImmutableSet.of(YangBinaryOperator.EQUALS.exprWith(Functions.POSITION, + createNumber(((PosContext) first).getToken(instanceIdentifierParser.PositiveIntegerValue, 0) + .getText()))); + } + + final int length = expr.getChildCount(); + final List ret = new ArrayList<>(length); + for (int i = 0; i < length; ++i) { + final KeyPredicateExprContext pred = getChild(expr, KeyPredicateContext.class, i) + .getChild(KeyPredicateExprContext.class, 0); + ret.add(YangBinaryOperator.EQUALS.exprWith( + YangQNameExpr.of(parseInstanceIdentifierQName(getChild(pred, NodeIdentifierContext.class, 0))), + parseEqStringValue(getChild(pred, EqQuotedStringContext.class, 1)))); + + } + + return ret; + } + + private YangLiteralExpr parseEqStringValue(final EqQuotedStringContext expr) { + return parseLiteral(verifyToken(getChild(expr, QuotedStringContext.class, expr.getChildCount() - 1), 1, + instanceIdentifierParser.STRING).getText()); + } + + private QName parseInstanceIdentifierQName(final NodeIdentifierContext expr) { + return qnameSupport.createQName(verifyToken(expr, 0, instanceIdentifierParser.Identifier).getText(), + verifyToken(expr, 2, instanceIdentifierParser.Identifier).getText()); + } + + private YangLiteralExpr parseQNameLiteral(final String text) { + final int firstColon = text.indexOf(':'); + if (firstColon != -1) { + // If we have two colons this node cannot be interpreted as a QName -- this may explode at evaluation-time, + // but that's fine as it will just result in evaluation error. Users do have unit tests, right? + final int secondColon = text.indexOf(':', firstColon + 1); + if (secondColon == -1) { + final Optional optNamespace = qnameSupport.resolvePrefix(text.substring(0, firstColon)); + // If we cannot resolve the namespace at evaluation-time has to deal with it. + if (optNamespace.isPresent()) { + try { + return new QNameLiteralExpr(text, QName.create(optNamespace.get(), + text.substring(firstColon + 1))); + } catch (IllegalArgumentException e) { + LOG.trace("Cannot interpret {} as a QName", text, e); + return YangLiteralExpr.of(text); + } + } + } + } + return YangLiteralExpr.of(text); + } + + private YangExpr parseUnary(final UnaryExprNoRootContext expr) { + // any number of '-' and an union expr + final int size = verifyAtLeastChildren(expr, 1); + final YangExpr ret = parseUnion(getChild(expr, UnionExprNoRootContext.class, size - 1)); + if (size % 2 != 0) { + // Even number of '-' tokens cancel out + return ret; + } + + return ret instanceof YangNumberExpr ? negateNumber((N) ret) : YangNegateExpr.of(ret); + } + + private YangExpr parseUnion(final UnionExprNoRootContext expr) { + final ParseTree first = expr.getChild(0); + final YangExpr path; + if (first instanceof PathExprNoRootContext) { + path = parsePathExpr((PathExprNoRootContext) first); + if (expr.getChildCount() == 1) { + return path; + } + } else { + path = YangLocationPath.root(); + } + + verifyChildCount(expr, 3); + final YangExpr union = parseUnion(getChild(expr, UnionExprNoRootContext.class, 2)); + + // Deduplicate expressions so we do not perform useless unioning + final Set expressions = new LinkedHashSet<>(); + expressions.add(path); + if (union instanceof YangNaryExpr) { + // If the result is a union expression, integrate it into this expression + final YangNaryExpr nary = (YangNaryExpr) union; + if (nary.getOperator() == YangNaryOperator.UNION) { + expressions.addAll(nary.getExpressions()); + } else { + expressions.add(union); + } + } else { + expressions.add(union); + } + + return YangNaryOperator.UNION.exprWith(expressions); + } + + private YangExpr parseAdditiveExpr(final YangExpr left, final Iterator it) { + YangExpr ret = left; + do { + final YangBinaryOperator operator = nextOperator(it); + final YangExpr right = parseMultiplicative(nextContext(it, MultiplicativeExprContext.class)); + final Optional simple = simplifyNumbers(operator, ret, right); + ret = simple.isPresent() ? simple.get() : operator.exprWith(ret, right); + } while (it.hasNext()); + + return ret; + } + + private Optional simplifyNumbers(final YangBinaryOperator operator, final YangExpr left, + final YangExpr right) { + if (left instanceof YangNumberExpr && right instanceof YangNumberExpr) { + // Constant folding on numbers -- precision plays a role here + return simplifyNumbers(operator, (N) left, (N) right); + } + return Optional.empty(); + } + + Optional simplifyNumbers(final YangBinaryOperator operator, final N left, final N right) { + switch (operator) { + case EQUALS: + return Optional.of(YangBooleanConstantExpr.of(left.getNumber().equals(right.getNumber()))); + case NOT_EQUALS: + return Optional.of(YangBooleanConstantExpr.of(!left.getNumber().equals(right.getNumber()))); + default: + return Optional.empty(); + } + } + + private YangExpr parseEqualityExpr(final YangExpr left, final Iterator it) { + YangExpr ret = left; + do { + final YangBinaryOperator operator = nextOperator(it); + final YangExpr right = parseRelational(nextContext(it, RelationalExprContext.class)); + + if (left.equals(right)) { + // Constant folding on expression level: equal expressions are result in equal results + switch (operator) { + case EQUALS: + return YangBooleanConstantExpr.TRUE; + case NOT_EQUALS: + return YangBooleanConstantExpr.FALSE; + default: + break; + } + } + + final Optional simple = simplifyNumbers(operator, ret, right); + ret = simple.isPresent() ? simple.get() : operator.exprWith(ret, right); + } while (it.hasNext()); + + return ret; + } + + private YangExpr parseRelationalExpr(final YangExpr left, final Iterator it) { + YangExpr ret = left; + do { + final YangBinaryOperator operator = nextOperator(it); + final YangExpr right = parseAdditive(nextContext(it, AdditiveExprContext.class)); + final Optional simple = simplifyNumbers(operator, ret, right); + ret = simple.isPresent() ? simple.get() : nextOperator(it).exprWith(ret, right); + } while (it.hasNext()); + + return ret; + } + + private QName parseQName(final QNameContext expr) { + switch (expr.getChildCount()) { + case 1: + return qnameSupport.createQName(getChild(expr, NCNameContext.class, 0).getText()); + case 3: + return qnameSupport.createQName(getChild(expr, NCNameContext.class, 0).getText(), + getChild(expr, NCNameContext.class, 2).getText()); + default: + throw illegalShape(expr); + } + } + + private Step parseStep(final StepContext expr) { + if (expr.getChildCount() == 1) { + final AbbreviatedStepContext abbrev = getChild(expr, AbbreviatedStepContext.class, 0); + verifyChildCount(abbrev, 1); + switch (getTerminalType(abbrev, 0)) { + case xpathParser.DOT: + return YangXPathAxis.SELF.asStep(); + case xpathParser.DOTDOT: + return YangXPathAxis.PARENT.asStep(); + default: + throw illegalShape(abbrev); + } + } + + final int size = verifyAtLeastChildren(expr, 2); + final List predicates = new ArrayList<>(size - 2); + for (int i = 2; i < size; ++i) { + predicates.add(parsePredicate(getChild(expr, PredicateContext.class, i))); + } + + final YangXPathAxis axis = parseAxis(getChild(expr, AxisSpecifierContext.class, 0)); + final NodeTestContext nodeTest = getChild(expr, NodeTestContext.class, 1); + switch (nodeTest.getChildCount()) { + case 1: + final NameTestContext nameChild = getChild(nodeTest, NameTestContext.class, 0); + final ParseTree first = nameChild.getChild(0); + if (first instanceof TerminalNode) { + verify(((TerminalNode) first).getSymbol().getType() == xpathParser.MUL); + return axis.asStep(predicates); + } + return axis.asStep(parseQName(verifyTree(QNameContext.class, first)), predicates); + case 3: + return axis.asStep(parseNodeType(nodeTest.getChild(0)), predicates); + case 4: + final String text = verifyToken(nodeTest, 2, xpathParser.Literal).getText(); + return axis.asStep(text.substring(1, text.length() - 1), predicates); + default: + throw illegalShape(nodeTest); + } + } + + private static YangXPathAxis parseAxis(final AxisSpecifierContext expr) { + switch (expr.getChildCount()) { + case 0: + return YangXPathAxis.CHILD; + case 1: + verify(getTerminalType(expr, 0) == xpathParser.AT, "Unhandled axis specifier shape %s", expr); + return YangXPathAxis.ATTRIBUTE; + case 2: + final String str = verifyTerminal(expr.getChild(0)).getText(); + return verifyNotNull(XPATH_AXES.get(str), "Unhandled axis %s", str); + default: + throw illegalShape(expr); + } + } + + private static T nextContext(final Iterator it, final Class type) { + return verifyTree(type, it.next()); + } + + private static YangBinaryOperator nextOperator(final Iterator it) { + return parseOperator(it.next()); + } + + private static T getChild(final ParseTree parent, final Class type, final int offset) { + return verifyTree(type, parent.getChild(offset)); + } + + private static Token verifyToken(final ParseTree parent, final int offset, final int expected) { + final TerminalNode node = verifyTerminal(parent.getChild(offset)); + final Token ret = node.getSymbol(); + final int type = ret.getType(); + verify(type == expected, "Item %s has type %s, expected %s", node, type, expected); + return ret; + } + + private static int getTerminalType(final ParseTree parent, final int offset) { + return verifyTerminal(parent.getChild(offset)).getSymbol().getType(); + } + + private static YangXPathNodeType parseNodeType(final ParseTree tree) { + final String str = verifyTerminal(tree).getText(); + return verifyNotNull(NODE_TYPES.get(str), "Unhandled node type %s", str); + } + + private static YangBinaryOperator parseOperator(final ParseTree tree) { + final String str = verifyTerminal(tree).getText(); + return verifyNotNull(BINARY_OPERATORS.get(str), "Unhandled operator %s", str); + } + + private static void verifyChildCount(final ParseTree tree, final int expected) { + if (tree.getChildCount() != expected) { + throw illegalShape(tree); + } + } + + private static int verifyAtLeastChildren(final ParseTree tree, final int expected) { + final int count = tree.getChildCount(); + if (count < expected) { + throw illegalShape(tree); + } + return count; + } + + private static TerminalNode verifyTerminal(final ParseTree tree) { + if (tree instanceof TerminalNode) { + return (TerminalNode) tree; + } + throw new VerifyException(String.format("'%s' is not a terminal node", tree.getText())); + } + + private static T verifyTree(final Class type, final ParseTree tree) { + if (type.isInstance(tree)) { + return type.cast(tree); + } + throw new VerifyException(String.format("'%s' does not have expected type %s", tree.getText(), type)); + } + + private static VerifyException illegalShape(final ParseTree tree) { + return new VerifyException(String.format("Invalid parser shape of '%s'", tree.getText())); + } +} diff --git a/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/package-info.java b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/package-info.java new file mode 100644 index 0000000000..5f3686103c --- /dev/null +++ b/yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/package-info.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +/** + * XPath parsing which results in a {@link org.opendaylight.yangtools.yang.xpath.api.YangExpr}. This is based + * on the XPath grammar from grammars-v4. + * + * @author Robert Varga + */ +@org.eclipse.jdt.annotation.NonNullByDefault +package org.opendaylight.yangtools.yang.xpath.impl; \ No newline at end of file diff --git a/yang/yang-xpath-impl/src/test/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParserTest.java b/yang/yang-xpath-impl/src/test/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParserTest.java new file mode 100644 index 0000000000..2a38822356 --- /dev/null +++ b/yang/yang-xpath-impl/src/test/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParserTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.yangtools.yang.xpath.impl; + +import static org.junit.Assert.assertEquals; + +import com.google.common.collect.ImmutableMap; +import java.net.URI; +import java.util.Map; +import javax.xml.xpath.XPathExpressionException; +import org.eclipse.jdt.annotation.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.xpath.api.YangBooleanConstantExpr; +import org.opendaylight.yangtools.yang.xpath.api.YangExpr; + +@SuppressWarnings("null") +public class XPathParserTest { + private static final QNameModule DEF_NS = QNameModule.create(URI.create("defaultns")); + private static final Map NAMESPACES = ImmutableMap.of( + "foo", QNameModule.create(URI.create("foo")), + "bar", QNameModule.create(URI.create("bar"))); + + private @Nullable XPathParser parser; + + @Before + public void before() { + parser = new BigDecimalXPathParser(DEF_NS, NAMESPACES::get); + } + + @Test + public void testSmoke() throws XPathExpressionException { + parseExpr("3 + 5"); + parseExpr("/a/b"); + parseExpr("a/b"); + parseExpr("./a/b"); + parseExpr("../a/b"); + parseExpr("foo:foo"); + parseExpr("@foo"); + parseExpr("@foo:foo"); + parseExpr("current()"); + parseExpr("foo:self()"); + parseExpr("foo:comment()"); + parseExpr("/a[foo = 2 and bar = 3]"); + parseExpr("/a[foo = \"2\" and bar = '3']"); + parseExpr("/foo:a[foo = 2 and bar = 3]"); + parseExpr("//a[foo = 2 and bar = 3]"); + parseExpr("//foo:a[foo=2 and bar:bar=3]"); + parseExpr("a//b[foo = 2]"); + parseExpr("foo:a//b[foo = 2]"); + parseExpr("$foo:comment"); + parseExpr("$comment"); + parseExpr("$self"); + } + + @Test + public void testUnionSquashing() throws XPathExpressionException { + final YangExpr a = parseExpr("a"); + assertEquals(a, parseExpr("a|a")); + assertEquals(a, parseExpr("a|a|a")); + assertEquals(a, parseExpr("a|a|a|a")); + + final YangExpr ab = parseExpr("a|b"); + assertEquals(ab, parseExpr("a|b|a")); + assertEquals(ab, parseExpr("a|b|a|b")); + assertEquals(ab, parseExpr("a|a|b|a|b")); + assertEquals(ab, parseExpr("a|b|b|a|b")); + assertEquals(ab, parseExpr("a|b|a|a|b")); + } + + @Test + public void testNumberSquashing() throws XPathExpressionException { + final YangExpr two = parseExpr("2"); + assertEquals(two, parseExpr("1 + 1")); + assertEquals(two, parseExpr("3 - 1")); + assertEquals(two, parseExpr("2 * 1")); + assertEquals(two, parseExpr("4 div 2")); + assertEquals(two, parseExpr("6 mod 4")); + } + + @Test + public void testSameExprSquashing() throws XPathExpressionException { + // Expressions + assertEquals(YangBooleanConstantExpr.FALSE, parseExpr("/a != /a")); + assertEquals(YangBooleanConstantExpr.TRUE, parseExpr("/a = /a")); + + // Numbers + assertEquals(YangBooleanConstantExpr.FALSE, parseExpr("2 != 2")); + assertEquals(YangBooleanConstantExpr.FALSE, parseExpr("2 != (1 + 1)")); + assertEquals(YangBooleanConstantExpr.TRUE, parseExpr("2 = 2")); + assertEquals(YangBooleanConstantExpr.TRUE, parseExpr("2 = (1 + 1)")); + } + + private YangExpr parseExpr(final String xpath) throws XPathExpressionException { + return parser.parseExpression(xpath).getRootExpr(); + } +} -- 2.36.6