<artifactId>yang-xpath-api</artifactId>
<version>0.1.10-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-xpath-impl</artifactId>
+ <version>0.1.10-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<type>xml</type>
<classifier>features</classifier>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>odl-yangtools-exp-xpath-impl</artifactId>
+ <version>0.1.10-SNAPSHOT</version>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </dependency>
</dependencies>
</dependencyManagement>
<classifier>features</classifier>
<type>xml</type>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>odl-yangtools-exp-xpath-impl</artifactId>
+ <classifier>features</classifier>
+ <type>xml</type>
+ </dependency>
</dependencies>
</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright © 2016 Red Hat, 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
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>single-feature-parent</artifactId>
+ <version>3.1.3</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>odl-yangtools-exp-xpath-impl</artifactId>
+ <version>0.1.10-SNAPSHOT</version>
+ <packaging>feature</packaging>
+ <name>OpenDaylight :: Yangtools :: Experimental :: XPath API implementation</name>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yangtools-artifacts</artifactId>
+ <version>2.0.10-SNAPSHOT</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>odl-yangtools-exp-xpath-api</artifactId>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-xpath-impl</artifactId>
+ </dependency>
+ </dependencies>
+</project>
<!-- Experimental features -->
<module>odl-exp-objcache</module>
<module>odl-yangtools-exp-xpath-api</module>
+ <module>odl-yangtools-exp-xpath-impl</module>
<!-- Experimental feature repostitory -->
<module>features-yangtools-experimental</module>
<!-- YANG XPath API and implementation -->
<module>yang-xpath-api</module>
+ <module>yang-xpath-impl</module>
<!-- End-user utility for validating YANG models -->
<module>yang-model-validator</module>
@Override
public final String toString() {
- return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
+ return addToStringAttributes(MoreObjects.toStringHelper(Step.class)).toString();
}
protected ToStringHelper addToStringAttributes(final ToStringHelper helper) {
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<Step> steps = getSteps();
+ if (!steps.isEmpty()) {
+ helper.add("steps", steps);
+ }
+ return helper.toString();
+ }
}
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;
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();
+ }
}
}
}
-
private static final long serialVersionUID = 1L;
YangNumberExpr() {
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2013 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>bundle-parent</artifactId>
+ <version>3.1.3</version>
+ <relativePath/>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-xpath-impl</artifactId>
+ <version>0.1.10-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <name>${project.artifactId}</name>
+ <description>YANG XPath parser</description>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yangtools-artifacts</artifactId>
+ <version>2.0.10-SNAPSHOT</version>
+ <scope>import</scope>
+ <type>pom</type>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>concepts</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-xpath-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-runtime</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.antlr</groupId>
+ <artifactId>antlr4-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>antlr4</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <sourceDirectory>src/main/antlr</sourceDirectory>
+ <outputDirectory>${project.build.directory}/generated-sources/antlr/org/opendaylight/yangtools/yang/xpath/impl</outputDirectory>
+ <visitor>false</visitor>
+ <listener>false</listener>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <configuration>
+ <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
--- /dev/null
+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}'
+ ;
+
--- /dev/null
+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 <tom@khubla.com>
+*/
+
+
+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'
+ ;
+
+
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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<YangBigDecimal> {
+ 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<String, QNameModule> 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<YangExpr> 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<YangExpr> of(final boolean value) {
+ return Optional.of(YangBooleanConstantExpr.of(value));
+ }
+}
--- /dev/null
+/*
+ * 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<YangDouble> {
+
+ DoubleXPathParser(final QNameModule implicitNamespace, final Function<String, QNameModule> 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<YangExpr> 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<YangExpr> of(final boolean value) {
+ return Optional.of(YangBooleanConstantExpr.of(value));
+ }
+}
--- /dev/null
+/*
+ * 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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<YangExpr> 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);
+ }
+}
--- /dev/null
+/*
+ * 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<Step> steps;
+
+ InstanceIdentifierLiteralExpr(final String str, final List<Step> steps) {
+ super(str);
+ this.steps = ImmutableList.copyOf(steps);
+ }
+
+ List<Step> getSteps() {
+ return steps;
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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<String, QNameModule> prefixes;
+ private final QNameModule implicitNamespace;
+
+ QNameSupport(final QNameModule implicitNamespace, final Function<String, QNameModule> 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<QNameModule> resolvePrefix(final String prefix) {
+ return Optional.ofNullable(prefixes.apply(requireNonNull(prefix)));
+ }
+}
--- /dev/null
+/*
+ * 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<N extends YangNumberExpr<N, ?>> implements YangXPathParser {
+ private static final Logger LOG = LoggerFactory.getLogger(XPathParser.class);
+ private static final Map<String, YangBinaryOperator> BINARY_OPERATORS = Maps.uniqueIndex(
+ Arrays.asList(YangBinaryOperator.values()), YangBinaryOperator::toString);
+ private static final Map<String, YangXPathNodeType> NODE_TYPES = Maps.uniqueIndex(Arrays.asList(
+ YangXPathNodeType.values()), YangXPathNodeType::toString);
+ private static final Map<String, YangXPathAxis> XPATH_AXES = Maps.uniqueIndex(Arrays.asList(YangXPathAxis.values()),
+ YangXPathAxis::toString);
+ private static final Map<QName, YangFunction> 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<String, QNameModule> 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<XPathExpressionException> 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<YangExpr> 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<ParseTree> 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<YangExpr> 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<ParseTree> 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<ParseTree> 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<YangExpr> 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<Step> 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<YangExpr> 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<ParseTree> it = expr.children.iterator();
+ final YangExpr first = parseAdditive(nextContext(it, AdditiveExprContext.class));
+ return it.hasNext() ? parseRelationalExpr(first, it) : first;
+ }
+
+ private Deque<Step> parseLocationPathSteps(final RelativeLocationPathContext expr) {
+ final Deque<Step> steps = new ArrayDeque<>(expr.getChildCount());
+ final Iterator<ParseTree> 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<Step> 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<YangExpr> 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<YangExpr> 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<QNameModule> 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<YangExpr> 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<ParseTree> it) {
+ YangExpr ret = left;
+ do {
+ final YangBinaryOperator operator = nextOperator(it);
+ final YangExpr right = parseMultiplicative(nextContext(it, MultiplicativeExprContext.class));
+ final Optional<YangExpr> simple = simplifyNumbers(operator, ret, right);
+ ret = simple.isPresent() ? simple.get() : operator.exprWith(ret, right);
+ } while (it.hasNext());
+
+ return ret;
+ }
+
+ private Optional<YangExpr> 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<YangExpr> 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<ParseTree> 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<YangExpr> 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<ParseTree> it) {
+ YangExpr ret = left;
+ do {
+ final YangBinaryOperator operator = nextOperator(it);
+ final YangExpr right = parseAdditive(nextContext(it, AdditiveExprContext.class));
+ final Optional<YangExpr> 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<YangExpr> 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 extends ParserRuleContext> T nextContext(final Iterator<ParseTree> it, final Class<T> type) {
+ return verifyTree(type, it.next());
+ }
+
+ private static YangBinaryOperator nextOperator(final Iterator<ParseTree> it) {
+ return parseOperator(it.next());
+ }
+
+ private static <T extends ParseTree> T getChild(final ParseTree parent, final Class<T> 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 extends ParseTree> T verifyTree(final Class<T> 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()));
+ }
+}
--- /dev/null
+/*
+ * 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 <a href="https://github.com/antlr/grammars-v4/blob/master/xpath/xpath.g4">grammars-v4</a>.
+ *
+ * @author Robert Varga
+ */
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.opendaylight.yangtools.yang.xpath.impl;
\ No newline at end of file
--- /dev/null
+/*
+ * 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<String, QNameModule> 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();
+ }
+}