2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.parser.rfc7950.repo;
10 import static com.google.common.base.Verify.verify;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.CharMatcher;
14 import java.util.List;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.yangtools.yang.common.YangVersion;
17 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
18 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Concatenation;
19 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Single;
20 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
21 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
24 * Utilities for dealing with YANG statement argument strings, encapsulated in ANTLR grammar's ArgumentContext.
26 abstract class ArgumentContextUtils {
28 * YANG 1.0 version of strings, which were not completely clarified in
29 * <a href="https://tools.ietf.org/html/rfc6020#section-6.1.3">RFC6020</a>.
31 private static final class RFC6020 extends ArgumentContextUtils {
32 private static final @NonNull RFC6020 INSTANCE = new RFC6020();
35 void checkDoubleQuoted(final String str, final StatementSourceReference ref, final int backslash) {
40 void checkUnquoted(final String str, final StatementSourceReference ref) {
46 * YANG 1.1 version of strings, which were clarified in
47 * <a href="https://tools.ietf.org/html/rfc7950#section-6.1.3">RFC7950</a>.
49 // NOTE: the differences clarified lead to a proper ability to delegate this to ANTLR lexer, but that does not
50 // understand versions and needs to work with both.
51 private static final class RFC7950 extends ArgumentContextUtils {
52 private static final CharMatcher ANYQUOTE_MATCHER = CharMatcher.anyOf("'\"");
53 private static final @NonNull RFC7950 INSTANCE = new RFC7950();
56 void checkDoubleQuoted(final String str, final StatementSourceReference ref, final int backslash) {
57 if (backslash < str.length() - 1) {
58 int index = backslash;
60 switch (str.charAt(index + 1)) {
65 index = str.indexOf('\\', index + 2);
68 throw new SourceException(ref, "YANG 1.1: illegal double quoted string (%s). In double "
69 + "quoted string the backslash must be followed by one of the following character "
70 + "[n,t,\",\\], but was '%s'.", str, str.charAt(index + 1));
77 void checkUnquoted(final String str, final StatementSourceReference ref) {
78 SourceException.throwIf(ANYQUOTE_MATCHER.matchesAnyOf(str), ref,
79 "YANG 1.1: unquoted string (%s) contains illegal characters", str);
83 private ArgumentContextUtils() {
87 static @NonNull ArgumentContextUtils forVersion(final YangVersion version) {
90 return RFC6020.INSTANCE;
92 return RFC7950.INSTANCE;
94 throw new IllegalStateException("Unhandled version " + version);
98 // TODO: teach the only caller about versions, or provide common-enough idioms for its use case
99 static @NonNull ArgumentContextUtils rfc6020() {
100 return RFC6020.INSTANCE;
104 * NOTE: this method we do not use convenience methods provided by generated parser code, but instead are making
105 * based on the grammar assumptions. While this is more verbose, it cuts out a number of unnecessary code,
106 * such as intermediate List allocation et al.
108 final @NonNull String stringFromStringContext(final IRArgument argument, final StatementSourceReference ref) {
109 if (argument instanceof Single) {
110 final Single single = (Single) argument;
111 final String str = single.string();
112 if (single.needQuoteCheck()) {
113 checkUnquoted(str, ref);
115 return single.needUnescape() ? unescape(str, ref) : str;
118 verify(argument instanceof Concatenation, "Unexpected argument %s", argument);
119 return concatStrings(((Concatenation) argument).parts(), ref);
122 private @NonNull String concatStrings(final List<? extends Single> parts, final StatementSourceReference ref) {
123 final StringBuilder sb = new StringBuilder();
124 for (Single part : parts) {
125 final String str = part.string();
126 sb.append(part.needUnescape() ? unescape(str, ref) : str);
128 return sb.toString();
132 * NOTE: Enforcement and transformation logic done by these methods should logically reside in the lexer and ANTLR
133 * account the for it with lexer modes. We do not want to force a re-lexing phase in the parser just because
134 * we decided to let ANTLR do the work.
136 abstract void checkDoubleQuoted(String str, StatementSourceReference ref, int backslash);
138 abstract void checkUnquoted(String str, StatementSourceReference ref);
140 private @NonNull String unescape(final String str, final StatementSourceReference ref) {
141 // Now we need to perform some amount of unescaping. This serves as a pre-check before we dispatch
142 // validation and processing (which will reuse the work we have done)
143 final int backslash = str.indexOf('\\');
144 return backslash == -1 ? str : unescape(ref, str, backslash);
148 * Unescape escaped double quotes, tabs, new line and backslash in the inner string and trim the result.
150 private @NonNull String unescape(final StatementSourceReference ref, final String str, final int backslash) {
151 checkDoubleQuoted(str, ref, backslash);
152 StringBuilder sb = new StringBuilder(str.length());
153 unescapeBackslash(sb, str, backslash);
154 return sb.toString();
158 static void unescapeBackslash(final StringBuilder sb, final String str, final int backslash) {
159 String substring = str;
160 int backslashIndex = backslash;
162 int nextIndex = backslashIndex + 1;
163 if (backslashIndex != -1 && nextIndex < substring.length()) {
164 replaceBackslash(sb, substring, nextIndex);
165 substring = substring.substring(nextIndex + 1);
166 if (substring.length() > 0) {
167 backslashIndex = substring.indexOf('\\');
172 sb.append(substring);
178 private static void replaceBackslash(final StringBuilder sb, final String str, final int nextAfterBackslash) {
179 int backslash = nextAfterBackslash - 1;
180 sb.append(str, 0, backslash);
181 final char c = str.charAt(nextAfterBackslash);
194 sb.append(str, backslash, nextAfterBackslash + 1);