Add yang-xpath-impl 73/71973/23
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 9 May 2018 18:54:22 +0000 (20:54 +0200)
committerRobert Varga <nite@hq.sk>
Thu, 2 Aug 2018 08:50:59 +0000 (08:50 +0000)
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 <robert.varga@pantheon.tech>
21 files changed:
common/artifacts/pom.xml
features/features-yangtools-experimental/pom.xml
features/odl-yangtools-exp-xpath-impl/pom.xml [new file with mode: 0644]
features/pom.xml
yang/pom.xml
yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangLocationPath.java
yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNaryExpr.java
yang/yang-xpath-api/src/main/java/org/opendaylight/yangtools/yang/xpath/api/YangNumberExpr.java
yang/yang-xpath-impl/pom.xml [new file with mode: 0644]
yang/yang-xpath-impl/src/main/antlr/instanceIdentifier.g4 [new file with mode: 0644]
yang/yang-xpath-impl/src/main/antlr/xpath.g4 [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/AntlrYangXPathExpression.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/BigDecimalXPathParser.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/DoubleXPathParser.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/Functions.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/InstanceIdentifierLiteralExpr.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameLiteralExpr.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/QNameSupport.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParser.java [new file with mode: 0644]
yang/yang-xpath-impl/src/main/java/org/opendaylight/yangtools/yang/xpath/impl/package-info.java [new file with mode: 0644]
yang/yang-xpath-impl/src/test/java/org/opendaylight/yangtools/yang/xpath/impl/XPathParserTest.java [new file with mode: 0644]

index 554944b6af625d5a94fd93282a33e67cc64ee01f..10e773360008f0a81de15114cb6ba0ff15f60d2f 100644 (file)
                 <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>
 
index 8e6047a498f7c81f4a772d00da9a23663f131f50..23de881d8604df8d07bd43a8af12a07a035fb53e 100644 (file)
             <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>
diff --git a/features/odl-yangtools-exp-xpath-impl/pom.xml b/features/odl-yangtools-exp-xpath-impl/pom.xml
new file mode 100644 (file)
index 0000000..4c164cc
--- /dev/null
@@ -0,0 +1,49 @@
+<?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>
index 21f386ea5dcd49ed4eba6e6870978677dfb5374a..252218b13fc3ac620a7b5d1cfb757859575110af 100644 (file)
@@ -41,6 +41,7 @@
         <!-- 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>
index f94618d137d3fda27ae4e84b08dab62ca5f95995..8144d320e1062cc8ecc5ef87e80babc6adc7a643 100644 (file)
@@ -41,6 +41,7 @@
 
         <!-- 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>
index be9351f96f714009f162cf83ef0db09fc4553023..b3de1ee777e572c112687c8b46a6b9434c65a30e 100644 (file)
@@ -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<Step> steps = getSteps();
+        if (!steps.isEmpty()) {
+            helper.add("steps", steps);
+        }
+        return helper.toString();
+    }
 }
index a59705c7a66c1f94ac53d8e88a0278adb34a3a81..fe513a8adb33c262b70a3deb836962fe4fe47091 100644 (file)
@@ -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();
+    }
 }
index 85deadb77d76887beb49e81035a2802670d33656..96f6bd7051f393450f643736c0b3fe45e8902e72 100644 (file)
@@ -83,7 +83,6 @@ public abstract class YangNumberExpr<T extends YangNumberExpr<T, N>, 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 (file)
index 0000000..e91cc91
--- /dev/null
@@ -0,0 +1,106 @@
+<?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>
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 (file)
index 0000000..eea2ee4
--- /dev/null
@@ -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 (file)
index 0000000..1b94867
--- /dev/null
@@ -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 <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'
+  ;
+
+
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 (file)
index 0000000..fa663d2
--- /dev/null
@@ -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 (file)
index 0000000..37a6131
--- /dev/null
@@ -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<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));
+    }
+}
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 (file)
index 0000000..40e459c
--- /dev/null
@@ -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<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));
+    }
+}
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 (file)
index 0000000..dc67397
--- /dev/null
@@ -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<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);
+    }
+}
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 (file)
index 0000000..d936286
--- /dev/null
@@ -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<Step> steps;
+
+    InstanceIdentifierLiteralExpr(final String str, final List<Step> steps) {
+        super(str);
+        this.steps = ImmutableList.copyOf(steps);
+    }
+
+    List<Step> 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 (file)
index 0000000..6f8d9a2
--- /dev/null
@@ -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 (file)
index 0000000..68a2f8d
--- /dev/null
@@ -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<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)));
+    }
+}
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 (file)
index 0000000..33c8e7f
--- /dev/null
@@ -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<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()));
+    }
+}
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 (file)
index 0000000..5f36861
--- /dev/null
@@ -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 <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
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 (file)
index 0000000..2a38822
--- /dev/null
@@ -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<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();
+    }
+}