-grammar instanceIdentifier;
+lexer grammar instanceIdentifierLexer;
/*
* YANG 1.1 instance-identifier grammar, as defined in
* 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)+ EOF
- ;
-
-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 '"'
- ;
+COLON : ':' ;
+DOT : '.' ;
+EQ : '=' ;
+LBRACKET : '[' ;
+RBRACKET : ']' ;
+SLASH : '/' ;
Identifier : [a-zA-Z][a-zA-Z0-9_\-.]*
;
+// Note: XPath elements are counted from 1, not from 0, as per
+// https://mailarchive.ietf.org/arch/msg/netmod/xSR_hu6ry4EWfrvIY5NqcmC7ZoM/
PositiveIntegerValue : [1-9][0-9]*
;
-STRING : YANGCHAR+
- ;
-
WSP : [ \t]+
;
+// Double/single-quoted strings. We deal with these using specialized modes.
+DQUOT_START : '"' -> pushMode(DQUOT_STRING_MODE), skip;
+SQUOT_START : '\'' -> pushMode(SQUOT_STRING_MODE), skip;
+
+//
+// Double-quoted string lexing mode. We interpret \n, \t, \", \\ only, as per RFC7950.
+//
+mode DQUOT_STRING_MODE;
+DQUOT_STRING : (YANGCHAR | '\'' | ('\\' [nt"\\]))+ ;
+DQUOT_END : '"' -> popMode;
+
+//
+// Single-quoted string lexing mode. We do not interpret anything within single
+// quotes.
+//
+mode SQUOT_STRING_MODE;
+SQUOT_STRING : (YANGCHAR | '"' | '\\')+ ;
+SQUOT_END : '\'' -> popMode;
+
fragment
YANGCHAR : '\t'..'\n'
| '\r'
- | '\u0020'..'\uD7FF'
+
+ // '\u0020'..'\uD7FF' without "'", '"' and '\'
+ | '\u0020'..'\u0021' // 0x22 = "
+ | '\u0023'..'\u0026' // 0x27 = '
+ | '\u0028'..'\u005B' // 0x5C = \
+ | '\u005D'..'\uD7FF'
+
| '\uE000'..'\uFDCF'
| '\uFDF0'..'\uFFFD'
| '\u{10000}'..'\u{1FFFD}'
--- /dev/null
+parser grammar instanceIdentifierParser;
+
+/*
+ * 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
+ */
+options {
+ tokenVocab = instanceIdentifierLexer;
+}
+
+instanceIdentifier : (SLASH pathArgument)+ EOF
+ ;
+
+pathArgument : nodeIdentifier predicate?
+ ;
+
+nodeIdentifier : Identifier COLON Identifier
+ ;
+
+predicate : keyPredicate+
+ | leafListPredicate
+ | pos
+ ;
+
+keyPredicate : LBRACKET WSP? keyPredicateExpr WSP? RBRACKET
+ ;
+
+keyPredicateExpr : nodeIdentifier eqQuotedString
+ ;
+
+leafListPredicate : LBRACKET WSP? leafListPredicateExpr WSP? RBRACKET
+ ;
+
+leafListPredicateExpr : DOT eqQuotedString
+ ;
+
+// Common tail of leafListPredicateExpr and keyPredicateExpr
+eqQuotedString : WSP? EQ WSP? quotedString
+ ;
+
+pos : LBRACKET WSP? PositiveIntegerValue WSP? RBRACKET
+ ;
+
+quotedString : SQUOT_STRING? SQUOT_END
+ | DQUOT_STRING? DQUOT_END
+ ;
+
import static java.util.Objects.requireNonNull;
import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.getChild;
import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.illegalShape;
+import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.verifyTerminal;
import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.verifyToken;
+import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collection;
import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierLexer;
import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser;
import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.EqQuotedStringContext;
-import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.InstanceIdentifierContext;
import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.KeyPredicateContext;
import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.KeyPredicateExprContext;
import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.LeafListPredicateContext;
}
final Absolute interpretAsInstanceIdentifier(final YangLiteralExpr expr) throws XPathExpressionException {
- final instanceIdentifierLexer lexer = new instanceIdentifierLexer(CharStreams.fromString(expr.getLiteral()));
- final instanceIdentifierParser parser = new instanceIdentifierParser(new CommonTokenStream(lexer));
- final CapturingErrorListener listener = new CapturingErrorListener();
+ final var lexer = new instanceIdentifierLexer(CharStreams.fromString(expr.getLiteral()));
+ final var parser = new instanceIdentifierParser(new CommonTokenStream(lexer));
+ final var listener = new CapturingErrorListener();
lexer.removeErrorListeners();
lexer.addErrorListener(listener);
parser.removeErrorListeners();
parser.addErrorListener(listener);
- final InstanceIdentifierContext id = parser.instanceIdentifier();
+ final var id = parser.instanceIdentifier();
listener.reportError();
final int length = id.getChildCount();
}
private static YangLiteralExpr parseEqStringValue(final EqQuotedStringContext expr) {
- return YangLiteralExpr.of(verifyToken(getChild(expr, QuotedStringContext.class, expr.getChildCount() - 1), 1,
- instanceIdentifierParser.STRING).getText());
+ final var quotedString = getChild(expr, QuotedStringContext.class, expr.getChildCount() - 1);
+ switch (quotedString.getChildCount()) {
+ case 1:
+ return YangLiteralExpr.empty();
+ case 2:
+ final var terminal = verifyTerminal(quotedString.getChild(0));
+ final var token = terminal.getSymbol();
+ final String literal;
+ switch (token.getType()) {
+ case instanceIdentifierParser.DQUOT_STRING:
+ literal = unescape(token.getText());
+ break;
+ case instanceIdentifierParser.SQUOT_STRING:
+ literal = token.getText();
+ break;
+ default:
+ throw illegalShape(terminal);
+ }
+ return YangLiteralExpr.of(literal);
+ default:
+ throw illegalShape(quotedString);
+ }
+ }
+
+ private static String unescape(final String escaped) {
+ final int firstEscape = escaped.indexOf('\\');
+ return firstEscape == -1 ? escaped : unescape(escaped, firstEscape);
+ }
+
+ private static String unescape(final String escaped, final int firstEscape) {
+ // Sizing: optimize allocation for only a single escape
+ final int length = escaped.length();
+ final var sb = new StringBuilder(length - 1).append(escaped, 0, firstEscape);
+
+ int esc = firstEscape;
+ while (true) {
+ sb.append(replace(escaped.charAt(esc + 1)));
+
+ final int start = esc + 2;
+ esc = escaped.indexOf('\\', start);
+ if (esc == -1) {
+ return sb.append(escaped, start, length).toString();
+ }
+
+ sb.append(escaped, start, esc);
+ }
+ }
+
+ private static char replace(final char ch) {
+ switch (ch) {
+ case 'n':
+ return '\n';
+ case 't':
+ return '\t';
+ case '"':
+ return '"';
+ case '\\':
+ return '\\';
+ default:
+ throw new VerifyException("Unexpected escaped char '" + ch + "'");
+ }
}
}
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, 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
+ */
+package org.opendaylight.yangtools.yang.xpath.impl;
+
+import com.google.common.collect.ImmutableBiMap;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.common.BiMapYangNamespaceContext;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.common.XMLNamespace;
+import org.opendaylight.yangtools.yang.common.YangNamespaceContext;
+import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr;
+import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Absolute;
+import org.opendaylight.yangtools.yang.xpath.api.YangXPathMathMode;
+
+class InstanceIdentifierParserTest {
+ private static final QNameModule DEFNS = QNameModule.create(XMLNamespace.of("defaultns"));
+ private static final YangNamespaceContext CONTEXT = new BiMapYangNamespaceContext(ImmutableBiMap.of(
+ "def", DEFNS,
+ "foo", QNameModule.create(XMLNamespace.of("foo")),
+ "bar", QNameModule.create(XMLNamespace.of("bar"))));
+
+ private static final InstanceIdentifierParser PARSER =
+ new InstanceIdentifierParser.Qualified(YangXPathMathMode.IEEE754, CONTEXT);
+
+ @Test
+ void smokeTests() throws Exception {
+ assertPath("/foo:foo");
+ assertPath("/foo:foo[.='abcd']");
+ assertPath("/foo:foo[.=\"abcd\"]");
+ }
+
+ private static Absolute assertPath(final String literal) throws Exception {
+ return PARSER.interpretAsInstanceIdentifier(YangLiteralExpr.of(literal));
+ }
+}