Add an explicit intermediate YANG representation 53/92153/21
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 14 Aug 2020 10:55:12 +0000 (12:55 +0200)
committerRobert Varga <nite@hq.sk>
Fri, 21 Aug 2020 08:40:40 +0000 (08:40 +0000)
ANTLR classes are geared towards parsing and raw speed, without much
regards to memory usage. Most notably using ParseTree as our AST ends
up retaining separators and various metadata which we do not really
need.

Add a simplistic intermediate representation for our AST, which where
we pre-process ANTLR trees and de-duplicate strings. This leads to
elimination of 90% of objects, with corresponding reduction of memory
footprint.

JIRA: YANGTOOLS-1130
Change-Id: Idf057bbb1e29280a8e378529401023fb87d18923
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
20 files changed:
yang/yang-parser-impl/src/main/java/org/opendaylight/yangtools/yang/parser/repo/SharedSchemaContextFactory.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/AbstractIRObject.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRArgument.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRKeyword.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement022.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement031.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement044.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement144.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatementL44.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/StatementFactory.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/package-info.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/ASTSchemaSource.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/ArgumentContextUtils.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/StatementContextVisitor.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/TextToASTTransformer.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangModelDependencyInfo.java
yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/YangStatementStreamSource.java
yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/StatementFactoryTest.java [new file with mode: 0644]
yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/repo/ArgumentContextUtilsTest.java

index b5ee5fd53e8180befef594c9a3897ab4826270d2..87ab72d55587327d390d8cf57d0fdc5b41658fee 100644 (file)
@@ -7,7 +7,6 @@
  */
 package org.opendaylight.yangtools.yang.parser.repo;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
 
@@ -34,7 +33,6 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import org.antlr.v4.runtime.ParserRuleContext;
 import org.eclipse.jdt.annotation.NonNull;
 import org.gaul.modernizer_maven_annotations.SuppressModernizer;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@@ -47,7 +45,6 @@ import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactoryConfig
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaResolutionException;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.ASTSchemaSource;
 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.YangModelDependencyInfo;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
@@ -231,12 +228,8 @@ final class SharedSchemaContextFactory implements EffectiveModelContextFactory {
 
             for (final Entry<SourceIdentifier, ASTSchemaSource> entry : srcs.entrySet()) {
                 final ASTSchemaSource ast = entry.getValue();
-                final ParserRuleContext parserRuleCtx = ast.getAST();
-                checkArgument(parserRuleCtx instanceof StatementContext, "Unsupported context class %s for source %s",
-                    parserRuleCtx.getClass(), entry.getKey());
-
                 try {
-                    parser.addSource(entry.getValue());
+                    parser.addSource(ast);
                 } catch (YangSyntaxErrorException | IOException e) {
                     throw new SchemaResolutionException("Failed to add source " + entry.getKey(), e);
                 }
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/AbstractIRObject.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/AbstractIRObject.java
new file mode 100644 (file)
index 0000000..ab71028
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import com.google.common.base.MoreObjects;
+import org.opendaylight.yangtools.concepts.Immutable;
+
+abstract class AbstractIRObject implements Immutable {
+    @Override
+    public final String toString() {
+        return MoreObjects.toStringHelper(this).add("fragment", toYangFragment(new StringBuilder())).toString();
+    }
+
+    abstract StringBuilder toYangFragment(StringBuilder sb);
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRArgument.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRArgument.java
new file mode 100644 (file)
index 0000000..87aec4e
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * An argument to a YANG statement, as defined by section 6.1.3 of both
+ * <a href="https://tools.ietf.org/html/rfc6020#section-6.1.3">RFC6020</a> and
+ * <a href="https://tools.ietf.org/html/rfc7950#section-6.1.3">RFC7950</a>. An argument is effectively any old string,
+ * except it can be defined in a number of ways:
+ * <ul>
+ *   <li>it can be a simple unquoted string, or</li>
+ *   <li>it can be a single-quoted string, with its contents being completely preserved, or</li>
+ *   <li>it can be a double-quoted string, which defines some escaping and whitespace-stripping rules, or</li>
+ *   <li>it can be a concatenation of any number of single- or double-quoted strings</li>
+ * </ul>
+ *
+ * <p>
+ * The first three cases as covered by {@link Single} subclass, which exposes appropriate methods to infer how its
+ * string literal is to be interpreted. The last case is handled by {@link Concatenation} subclass, which exposes
+ * the constituent parts as {@link Single} items.
+ *
+ * <p>
+ * Please note that parser implementations producing these argument representations are <b>NOT</b> required to retain
+ * the format of the original definition. They are free to perform quoting and concatenation transformations as long as
+ * they maintain semantic equivalence. As a matter of example, these transformations are explicitly allowed:
+ * <ul>
+ *   <li>elimination of unneeded quotes, for example turning {@code "foo"} into {@code foo}</li>
+ *   <li>transformation of quotes, for example turning {@code "foo\nbar"} into {@code 'foo&#10bar'}</li>
+ *   <li>concatenation processing, for example turning {@code 'foo' + 'bar'} into {@code foobar}</li>
+ * </ul>
+ */
+@Beta
+public abstract class IRArgument extends AbstractIRObject {
+    /**
+     * An argument composed of multiple concatenated parts.
+     */
+    public static final class Concatenation extends IRArgument {
+        private final @NonNull ImmutableList<Single> parts;
+
+        Concatenation(final ImmutableList<Single> parts) {
+            this.parts = requireNonNull(parts);
+        }
+
+        /**
+         * Return the argument parts that need to be concatenated.
+         *
+         * @return Argument parts.
+         */
+        public @NonNull List<? extends Single> parts() {
+            return parts;
+        }
+
+        @Override
+        StringBuilder toYangFragment(final StringBuilder sb) {
+            final Iterator<Single> it = parts.iterator();
+            it.next().toYangFragment(sb);
+            while (it.hasNext()) {
+                it.next().toYangFragment(sb.append(" + "));
+            }
+            return sb;
+        }
+    }
+
+    /**
+     * An argument composed of a single string. This string may need further validation and processing, as it may not
+     * actually conform to the specification as requested by {@code yang-version}.
+     */
+    /*
+     * This is the public footprint which is served by three final subclasses: DoublyQuoted, SingleQuoted, Unquoted.
+     * Those classes must never be exposed, as they are a manifestation of current implementation in StatementFactory.
+     * As noted in the interface contract of IRArgument, we have very much free reign on syntactic transformations,
+     * StatementFactory is just not taking advantage of those at this point.
+     *
+     * The subclasses may very much change, in terms of both naming and function, to support whatever StatementFactory
+     * ends up doing.
+     */
+    public abstract static class Single extends IRArgument {
+        private final @NonNull String string;
+
+        Single(final String string) {
+            this.string = requireNonNull(string);
+        }
+
+        /**
+         * Significant portion of this argument. For unquoted and single-quoted strings this is the unquoted string
+         * literal. For double-quoted strings this is the unquoted string, after whitespace trimming as defined by
+         * RFC6020/RFC7950 section 6.1.3, but before escape substitution.
+         *
+         * @return Significant portion of this argument.
+         */
+        public final @NonNull String string() {
+            return string;
+        }
+
+        /**
+         * Imprecise check if this argument needs further unescape operation (which is version-specific) to arrive at
+         * the literal string value. This is false for unquoted and single-quoted strings, which do not support any sort
+         * of escaping. This may be true for double-quoted strings, as they <b>may</b> need to be further processed in
+         * version-dependent ways to arrive at the correct literal value.
+         *
+         * <p>
+         * This method is allowed to err on the false-positive side -- i.e. it may report any double-quoted string as
+         * needing further processing, even when the actual content could be determined to not need further processing.
+         *
+         * @return False if the value of {@link #string} can be used as-is.
+         */
+        public final boolean needUnescape() {
+            return this instanceof DoubleQuoted;
+        }
+
+        /**
+         * Imprecise check if this argument needs an additional content check for compliance. This is false if the
+         * string was explicitly quoted and therefore cannot contain stray single- or double-quotes, or if the content
+         * has already been checked to not contain them.
+         *
+         * <p>
+         * The content check is needed to ascertain RFC7950 compliance, because RFC6020 allows constructs like
+         * <pre>abc"def</pre> in unquoted strings, while RFC7950 explicitly forbids them.
+         *
+         * <p>
+         * This method is allowed to err on the false-positive side -- i.e. it may report any unquoted string as
+         * needing this check, even when the actual content could be determined to not contain quotes.
+         *
+         * @return True if this argument requires a version-specific check for quote content.
+         */
+        public final boolean needQuoteCheck() {
+            return this instanceof Unquoted;
+        }
+
+        /**
+         * Imprecise check if this argument complies with the {@code identifier} YANG specification.
+         *
+         * <p>
+         * This method is allowed to err on the false-negative side -- i.e. it may report any string as not being
+         * compliant with {@code identifier}, even when the actual content could be determined to be compliant.
+         *
+         * @return True if this argument is known to be directly usable in contexts where YANG requires the use of
+         */
+        public final boolean isValidIdentifier() {
+            return this instanceof Identifier;
+        }
+
+        @Override
+        StringBuilder toYangFragment(final StringBuilder sb) {
+            return sb.append(string);
+        }
+    }
+
+    static final class DoubleQuoted extends Single {
+        DoubleQuoted(final String string) {
+            super(string);
+        }
+
+        @Override
+        StringBuilder toYangFragment(final StringBuilder sb) {
+            // Note this is just an approximation. We do not have enough state knowledge to restore any whitespace we
+            // may have trimmed.
+            return super.toYangFragment(sb.append('"')).append('"');
+        }
+    }
+
+    static final class SingleQuoted extends Single {
+        SingleQuoted(final String string) {
+            super(string);
+        }
+
+        @Override
+        StringBuilder toYangFragment(final StringBuilder sb) {
+            return super.toYangFragment(sb.append('\'')).append('\'');
+        }
+    }
+
+    static final class Identifier extends Single {
+        Identifier(final String string) {
+            super(string);
+        }
+    }
+
+    static final class Unquoted extends Single {
+        Unquoted(final String string) {
+            super(string);
+        }
+    }
+
+    IRArgument() {
+        // Hidden on purpose
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRKeyword.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRKeyword.java
new file mode 100644 (file)
index 0000000..0df3c4d
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.AbstractQName;
+
+/**
+ * A YANG keyword, as defined, as defined by section 6.1.2 of both
+ * <a href="https://tools.ietf.org/html/rfc6020#section-6.1.2">RFC6020</a> and
+ * <a href="https://tools.ietf.org/html/rfc7950#section-6.1.2">RFC7950</a>. The two options are discerned by nullability
+ * of {@link #prefix()} method's return, as hinted by the ABNF for {@code node-identifier} -- and while a keyword is a
+ * semantically different construct, it shares the same value space.
+ *
+ * <p>
+ * Naming in this class prefers the formal ABNF specification and draws value-space and type-safety implications from
+ * that connection, rather than following the RFC-assigned names.
+ */
+@Beta
+public abstract class IRKeyword extends AbstractIRObject {
+    @Beta
+    public static final class Qualified extends IRKeyword {
+        private final @NonNull String prefix;
+
+        Qualified(final String prefix, final String localName) {
+            super(localName);
+            this.prefix = requireNonNull(prefix);
+        }
+
+        @Override
+        public @NonNull String prefix() {
+            return prefix;
+        }
+
+        @Override
+        public String asStringDeclaration() {
+            return prefix + ':' + identifier();
+        }
+
+        @Override
+        StringBuilder toYangFragment(final StringBuilder sb) {
+            return sb.append(prefix).append(':').append(identifier());
+        }
+    }
+
+    @Beta
+    public static final class Unqualified extends IRKeyword {
+        Unqualified(final String localName) {
+            super(localName);
+        }
+
+        @Override
+        public String prefix() {
+            return null;
+        }
+
+        @Override
+        public String asStringDeclaration() {
+            return identifier();
+        }
+
+        @Override
+        StringBuilder toYangFragment(final StringBuilder sb) {
+            return sb.append(identifier());
+        }
+    }
+
+    private final @NonNull String identifier;
+
+    IRKeyword(final String localName) {
+        this.identifier = requireNonNull(localName);
+    }
+
+    /**
+     * This keyword's 'identifier' part. This corresponds to what the RFCs refer to as {@code YANG keyword} or as
+     * {@code language extension keyword}.
+     *
+     * <p>
+     * Note the returned string is guaranteed to conform to rules of {@code identifier} ABNF and therefore
+     * is directly usable as a {@code localName} in an {@link AbstractQName}.
+     *
+     * @return This keyword's identifier part.
+     */
+    public final @NonNull String identifier() {
+        return identifier;
+    }
+
+    /**
+     * This keyword's 'prefix' part. This corresponds to {@code prefix identifier}. For {@code YANG keyword}s this is
+     * null. For language extension references this is the non-null prefix which references the YANG module defining
+     * the language extension.
+     *
+     * <p>
+     * Note the returned string, if non-null, is guaranteed to conform to rules of {@code identifier} ABNF and therefore
+     * is directly usable as a {@code localName} in an {@link AbstractQName}.
+     *
+     * @return This keyword's prefix, or null if this keyword references a YANG keyword.
+     */
+    public abstract @Nullable String prefix();
+
+    /**
+     * Helper method to re-create the string which was used to declared this keyword.
+     *
+     * @return Declaration string.
+     */
+    public abstract @NonNull String asStringDeclaration();
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement.java
new file mode 100644 (file)
index 0000000..adaf5db
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
+
+/**
+ * A single YANG statement in its raw string form. A statement is composed of:
+ * <ul>
+ *   <li>a mandatory keyword, modeled as {@link IRKeyword}</li>
+ *   <li>an optional argument, modeled as {@link IRArgument}</li>
+ *   <li>zero or more nested statements</li>
+ * </ul>
+ */
+@Beta
+public abstract class IRStatement extends AbstractIRObject {
+    private final @NonNull IRKeyword keyword;
+    private final IRArgument argument;
+
+    IRStatement(final IRKeyword keyword, final IRArgument argument) {
+        this.keyword = requireNonNull(keyword);
+        this.argument = argument;
+    }
+
+    /**
+     * Create an {@link IRStatement} from a parsed {@link StatementContext}.
+     *
+     * @param context ANTLR statement context
+     * @return A new IRStatement
+     * @throws NullPointerException if {@code context} is null
+     */
+    public static @NonNull IRStatement forContext(final StatementContext context) {
+        return new StatementFactory().createStatement(context);
+    }
+
+    /**
+     * Return this statement's keyword.
+     *
+     * @return This statement's keyword.
+     */
+    public final @NonNull IRKeyword keyword() {
+        return keyword;
+    }
+
+    /**
+     * Return this statement's argument, if it is present.
+     *
+     * @return This statement's argument, or null if this statement does not have an argument
+     */
+    public final @Nullable IRArgument argument() {
+        return argument;
+    }
+
+    /**
+     * Return this statement's substatements.
+     *
+     * @return This statement's substatements.
+     */
+    public @NonNull List<? extends IRStatement> statements() {
+        return ImmutableList.of();
+    }
+
+    /**
+     * Return the line number on which this statement's keyword has its first character, counting from <b>1</b>. This
+     * information is used only for diagnostic purposes.
+     *
+     * @return Line number where this statement started in the source code.
+     */
+    public abstract int startLine();
+
+    /**
+     * Return the column number on which this statement's keyword has its first character, counting from <b>0</b>. This
+     * information is used only for diagnostic purposes.
+     *
+     * @return Column number where this statement started in the source code.
+     */
+    public abstract int startColumn();
+
+    @Override
+    final StringBuilder toYangFragment(final StringBuilder sb) {
+        sb.append(keyword);
+        if (argument != null) {
+            argument.toYangFragment(sb.append(' '));
+        }
+
+        final List<? extends IRStatement> statements = statements();
+        if (statements.isEmpty()) {
+            return sb.append(';');
+        }
+
+        sb.append(" {\n");
+        for (IRStatement stmt : statements) {
+            stmt.toYangFragment(sb).append('\n');
+        }
+        return sb.append('}');
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement022.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement022.java
new file mode 100644 (file)
index 0000000..0d63bcb
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+final class IRStatement022 extends IRStatement {
+    private final short startLine;
+    private final short startColumn;
+
+    IRStatement022(final IRKeyword keyword, final IRArgument argument, final int startLine, final int startColumn) {
+        super(keyword, argument);
+        this.startLine = (short) startLine;
+        this.startColumn = (short) startColumn;
+    }
+
+    @Override
+    public int startLine() {
+        return Short.toUnsignedInt(startLine);
+    }
+
+    @Override
+    public int startColumn() {
+        return Short.toUnsignedInt(startColumn);
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement031.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement031.java
new file mode 100644 (file)
index 0000000..edd424b
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+final class IRStatement031 extends IRStatement {
+    private final int value;
+
+    IRStatement031(final IRKeyword keyword, final IRArgument argument, final int startLine, final int startColumn) {
+        super(keyword, argument);
+        this.value = startLine << 8 | startColumn & 0xFF;
+    }
+
+    @Override
+    public int startLine() {
+        return value >>> 8;
+    }
+
+    @Override
+    public int startColumn() {
+        return value & 0xFF;
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement044.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement044.java
new file mode 100644 (file)
index 0000000..ed8dfc6
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+class IRStatement044 extends IRStatement {
+    private final int startLine;
+    private final int startColumn;
+
+    IRStatement044(final IRKeyword keyword, final IRArgument argument, final int startLine, final int startColumn) {
+        super(keyword, argument);
+        this.startLine = startLine;
+        this.startColumn = startColumn;
+    }
+
+    @Override
+    public final int startLine() {
+        return startLine;
+    }
+
+    @Override
+    public final int startColumn() {
+        return startColumn;
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement144.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatement144.java
new file mode 100644 (file)
index 0000000..2a7e894
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import org.eclipse.jdt.annotation.NonNull;
+
+final class IRStatement144 extends IRStatement044 {
+    private final @NonNull IRStatement statement;
+
+    IRStatement144(final IRKeyword keyword, final IRArgument argument, final IRStatement statement,
+            final int startLine, final int startColumn) {
+        super(keyword, argument, startLine, startColumn);
+        this.statement = requireNonNull(statement);
+    }
+
+    @Override
+    public ImmutableList<IRStatement> statements() {
+        return ImmutableList.of(statement);
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatementL44.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/IRStatementL44.java
new file mode 100644 (file)
index 0000000..c6acf69
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import org.eclipse.jdt.annotation.NonNull;
+
+final class IRStatementL44 extends IRStatement044 {
+    private final @NonNull ImmutableList<IRStatement> statements;
+
+    IRStatementL44(final IRKeyword keyword, final IRArgument argument, final ImmutableList<IRStatement> statements,
+            final int startLine, final int startColumn) {
+        super(keyword, argument, startLine, startColumn);
+        this.statements = requireNonNull(statements);
+    }
+
+    @Override
+    public ImmutableList<IRStatement> statements() {
+        return statements;
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/StatementFactory.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/StatementFactory.java
new file mode 100644 (file)
index 0000000..c2e445b
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2020 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.parser.rfc7950.ir;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Function;
+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.NonNull;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.ArgumentContext;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.KeywordContext;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.QuotedStringContext;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
+import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.UnquotedStringContext;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Concatenation;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.DoubleQuoted;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Identifier;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Single;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.SingleQuoted;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Unquoted;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Qualified;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
+
+final class StatementFactory {
+    private static final CharMatcher WHITESPACE_MATCHER = CharMatcher.whitespace();
+
+    private final Map<String, DoubleQuoted> dquotArguments = new HashMap<>();
+    private final Map<String, SingleQuoted> squotArguments = new HashMap<>();
+    private final Map<String, Unquoted> uquotArguments = new HashMap<>();
+    private final Map<String, Identifier> idenArguments = new HashMap<>();
+    private final Map<String, Unqualified> uqualKeywords = new HashMap<>();
+    private final Map<Entry<String, String>, Qualified> qualKeywords = new HashMap<>();
+    private final Map<String, String> strings = new HashMap<>();
+
+    @NonNull IRStatement createStatement(final StatementContext stmt) {
+        final ParseTree firstChild = stmt.getChild(0);
+        verify(firstChild instanceof KeywordContext, "Unexpected shape of %s", stmt);
+
+        final ParseTree keywordStart = firstChild.getChild(0);
+        verify(keywordStart instanceof TerminalNode, "Unexpected keyword start %s", keywordStart);
+        final Token keywordToken = ((TerminalNode) keywordStart).getSymbol();
+
+        final IRKeyword keyword;
+        switch (firstChild.getChildCount()) {
+            case 1:
+                keyword = uqualKeywords.computeIfAbsent(strOf(keywordToken), Unqualified::new);
+                break;
+            case 3:
+                keyword = qualKeywords.computeIfAbsent(Map.entry(strOf(keywordToken), strOf(firstChild.getChild(2))),
+                    entry -> new Qualified(entry.getKey(), entry.getValue()));
+                break;
+            default:
+                throw new VerifyException("Unexpected keyword " + firstChild);
+        }
+
+        final IRArgument argument = createArgument(stmt);
+        final ImmutableList<IRStatement> statements = createStatements(stmt);
+        final int line = keywordToken.getLine();
+        final int column = keywordToken.getCharPositionInLine();
+
+        switch (statements.size()) {
+            case 0:
+                return createStatement(keyword, argument, line, column);
+            case 1:
+                return new IRStatement144(keyword, argument, statements.get(0), line, column);
+            default:
+                return new IRStatementL44(keyword, argument, statements, line, column);
+        }
+    }
+
+    private static @NonNull IRStatement createStatement(final IRKeyword keyword, final IRArgument argument,
+            final int line, final int column) {
+        if (line >= 0 && column >= 0) {
+            if (line <= 65535 && column <= 65535) {
+                return new IRStatement022(keyword, argument, line, column);
+            }
+            if (line <= 16777215 && column <= 255) {
+                return new IRStatement031(keyword, argument, line, column);
+            }
+        }
+        return new IRStatement044(keyword, argument, line, column);
+    }
+
+    private IRArgument createArgument(final StatementContext stmt) {
+        final ArgumentContext argCtx = stmt.argument();
+        if (argCtx == null) {
+            return null;
+        }
+        if (argCtx.getChildCount() == 1) {
+            final ParseTree child = argCtx.getChild(0);
+            if (child instanceof TerminalNode) {
+                // This is as simple as it gets: we are dealing with an identifier here.
+                return idenArguments.computeIfAbsent(strOf(((TerminalNode) child).getSymbol()), Identifier::new);
+            }
+            if (child instanceof UnquotedStringContext) {
+                // TODO: check non-presence of quotes and create a different subclass, so that ends up treated as if it
+                //       was single-quoted, i.e. bypass the check implied by IRArgument.Single#needQuoteCheck().
+                return uquotArguments.computeIfAbsent(strOf(child), Unquoted::new);
+            }
+
+            verify(child instanceof QuotedStringContext, "Unexpected child %s", child);
+            return createArgument((QuotedStringContext) child);
+        }
+
+        // TODO: perform concatenation of single-quoted strings. For double-quoted strings this may not be as nice, but
+        //       for single-quoted strings we do not need further validation in in the reactor and can use them as raw
+        //       literals. This saves some indirection overhead (on memory side) and can slightly improve execution
+        //       speed when we process the same IR multiple times.
+
+        return new Concatenation(argCtx.quotedString().stream().map(this::createArgument)
+            .collect(ImmutableList.toImmutableList()));
+    }
+
+    private Single createArgument(final QuotedStringContext argument) {
+        final ParseTree literal = argument.getChild(1);
+        verify(literal instanceof TerminalNode, "Unexpected literal %s", literal);
+        final Token token = ((TerminalNode) literal).getSymbol();
+        switch (token.getType()) {
+            case YangStatementParser.DQUOT_END:
+                return dquotArguments.computeIfAbsent("", DoubleQuoted::new);
+            case YangStatementParser.DQUOT_STRING:
+                // Whitespace normalization happens irrespective of further handling and has no effect on the result
+                final String str = intern(trimWhitespace(token.getText(), token.getCharPositionInLine() - 1));
+
+                // TODO: turn this into a single-quoted literal if a backslash is not present. Doing so allows the
+                //       argument to be treated as a literal. See IRArgument.Single#needUnescape() for more context.
+                //       This may look unimportant, but there are scenarios where we process the same AST multiple times
+                //       and remembering this detail saves a string scan.
+
+                return dquotArguments.computeIfAbsent(str, DoubleQuoted::new);
+            case YangStatementParser.SQUOT_END:
+                return squotArguments.computeIfAbsent("", SingleQuoted::new);
+            case YangStatementParser.SQUOT_STRING:
+                return squotArguments.computeIfAbsent(strOf(token), SingleQuoted::new);
+            default:
+                throw new VerifyException("Unexpected token " + token);
+        }
+    }
+
+    private ImmutableList<IRStatement> createStatements(final StatementContext stmt) {
+        final List<StatementContext> statements = stmt.statement();
+        return statements.isEmpty() ? ImmutableList.of()
+                : statements.stream().map(this::createStatement).collect(ImmutableList.toImmutableList());
+    }
+
+    private String strOf(final ParseTree tree) {
+        return intern(tree.getText());
+    }
+
+    private String strOf(final Token token) {
+        return intern(token.getText());
+    }
+
+    private String intern(final String str) {
+        return strings.computeIfAbsent(str, Function.identity());
+    }
+
+    @VisibleForTesting
+    static String trimWhitespace(final String str, final int dquot) {
+        final int firstBrk = str.indexOf('\n');
+        if (firstBrk == -1) {
+            return str;
+        }
+
+        // Okay, we may need to do some trimming, set up a builder and append the first segment
+        final int length = str.length();
+        final StringBuilder sb = new StringBuilder(length);
+
+        // Append first segment, which needs only tail-trimming
+        sb.append(str, 0, trimTrailing(str, 0, firstBrk)).append('\n');
+
+        // With that out of the way, setup our iteration state. The string segment we are looking at is
+        // str.substring(start, end), which is guaranteed not to include any line breaks, i.e. end <= brk unless we are
+        // at the last segment.
+        int start = firstBrk + 1;
+        int brk = str.indexOf('\n', start);
+
+        // Loop over inner strings
+        while (brk != -1) {
+            trimLeadingAndAppend(sb, dquot, str, start, trimTrailing(str, start, brk)).append('\n');
+            start = brk + 1;
+            brk = str.indexOf('\n', start);
+        }
+
+        return trimLeadingAndAppend(sb, dquot, str, start, length).toString();
+    }
+
+    private static StringBuilder trimLeadingAndAppend(final StringBuilder sb, final int dquot, final String str,
+            final int start, final int end) {
+        int offset = start;
+        int pos = 0;
+
+        while (pos <= dquot) {
+            if (offset == end) {
+                // We ran out of data, nothing to append
+                return sb;
+            }
+
+            final char ch = str.charAt(offset);
+            if (ch == '\t') {
+                // tabs are to be treated as 8 spaces
+                pos += 8;
+            } else if (WHITESPACE_MATCHER.matches(ch)) {
+                pos++;
+            } else {
+                break;
+            }
+
+            offset++;
+        }
+
+        // We have expanded beyond double quotes, push equivalent spaces
+        while (pos - 1 > dquot) {
+            sb.append(' ');
+            pos--;
+        }
+
+        return sb.append(str, offset, end);
+    }
+
+    private static int trimTrailing(final String str, final int start, final int end) {
+        int ret = end;
+        while (ret > start) {
+            final int prev = ret - 1;
+            if (!WHITESPACE_MATCHER.matches(str.charAt(prev))) {
+                break;
+            }
+            ret = prev;
+        }
+        return ret;
+    }
+}
diff --git a/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/package-info.java b/yang/yang-parser-rfc7950/src/main/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/package-info.java
new file mode 100644 (file)
index 0000000..5b14723
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2020 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
+ */
+/**
+ * Intermediate representation of a YANG file. This is an Abstract Syntax Tree equivalent to ParseTree we get from
+ * ANTLR, except it is immutable and has a denser in-memory representation due to it not containing any metadata which
+ * is not required for the purposes of statement inference.
+ *
+ * <p>
+ * The main entrypoint into this package is {@link org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement}, which
+ * represents a single YANG statement. Every YANG file is required to contain exactly one top-level statement,
+ * {@code module} or {@code submodule}, hence an IRStatement also represents the significant contents of a YANG file.
+ */
+package org.opendaylight.yangtools.yang.parser.rfc7950.ir;
\ No newline at end of file
index cf059e59e4c69573444182c4b70b567844a62d44..9cf3717a76971482ddec2938afb3f61ebace44fd 100644 (file)
@@ -11,7 +11,6 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import java.util.Optional;
-import org.antlr.v4.runtime.ParserRuleContext;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.AbstractIdentifiable;
@@ -20,6 +19,7 @@ import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
 import org.opendaylight.yangtools.yang.model.repo.api.SemVerSourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
 
 /**
  * Abstract Syntax Tree representation of a schema source. This representation is internal to the YANG parser
@@ -35,15 +35,15 @@ public final class ASTSchemaSource extends AbstractIdentifiable<SourceIdentifier
         implements SchemaSourceRepresentation {
     private final @NonNull YangModelDependencyInfo depInfo;
     private final @NonNull SemVerSourceIdentifier semVerId;
-    private final @NonNull ParserRuleContext tree;
+    private final @NonNull IRStatement rootStatement;
     private final @Nullable String symbolicName;
 
     private ASTSchemaSource(final @NonNull SourceIdentifier identifier, final @NonNull SemVerSourceIdentifier semVerId,
-            final @NonNull ParserRuleContext tree, final @NonNull YangModelDependencyInfo depInfo,
+            final @NonNull IRStatement tree, final @NonNull YangModelDependencyInfo depInfo,
             @Nullable final String symbolicName) {
         super(identifier);
         this.depInfo = requireNonNull(depInfo);
-        this.tree = requireNonNull(tree);
+        this.rootStatement = requireNonNull(tree);
         this.semVerId = requireNonNull(semVerId);
         this.symbolicName = symbolicName;
     }
@@ -63,9 +63,9 @@ public final class ASTSchemaSource extends AbstractIdentifiable<SourceIdentifier
      *             if we fail to extract dependency information.
      */
     static @NonNull ASTSchemaSource create(final @NonNull SourceIdentifier identifier,
-            final @Nullable String symbolicName, final @NonNull ParserRuleContext tree)
+            final @Nullable String symbolicName, final @NonNull IRStatement rootStatement)
                     throws YangSyntaxErrorException {
-        final YangModelDependencyInfo depInfo = YangModelDependencyInfo.fromAST(identifier, tree);
+        final YangModelDependencyInfo depInfo = YangModelDependencyInfo.parseAST(rootStatement, identifier);
         final SourceIdentifier id = getSourceId(depInfo);
 
         final SemVerSourceIdentifier semVerId;
@@ -75,7 +75,7 @@ public final class ASTSchemaSource extends AbstractIdentifiable<SourceIdentifier
             semVerId = getSemVerSourceId(depInfo);
         }
 
-        return new ASTSchemaSource(id, semVerId, tree, depInfo, symbolicName);
+        return new ASTSchemaSource(id, semVerId, rootStatement, depInfo, symbolicName);
     }
 
     @Override
@@ -93,12 +93,12 @@ public final class ASTSchemaSource extends AbstractIdentifiable<SourceIdentifier
     }
 
     /**
-     * Return the underlying abstract syntax tree.
+     * Return the root statement of this source.
      *
-     * @return Underlying AST.
+     * @return Root statement.
      */
-    public @NonNull ParserRuleContext getAST() {
-        return tree;
+    public @NonNull IRStatement getRootStatement() {
+        return rootStatement;
     }
 
     /**
index 2f35f13f66a0774bf75e5175e4cb4aaf6c770cb3..f3878c33714d408933fa4c6a3f16b6e5a2efc402 100644 (file)
@@ -11,16 +11,12 @@ import static com.google.common.base.Verify.verify;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.VerifyException;
-import org.antlr.v4.runtime.Token;
-import org.antlr.v4.runtime.tree.ParseTree;
-import org.antlr.v4.runtime.tree.TerminalNode;
+import java.util.List;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.YangVersion;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.ArgumentContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.QuotedStringContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.UnquotedStringContext;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Concatenation;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Single;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
 
@@ -84,8 +80,6 @@ abstract class ArgumentContextUtils {
         }
     }
 
-    private static final CharMatcher WHITESPACE_MATCHER = CharMatcher.whitespace();
-
     private ArgumentContextUtils() {
         // Hidden on purpose
     }
@@ -111,91 +105,29 @@ abstract class ArgumentContextUtils {
      *       based on the grammar assumptions. While this is more verbose, it cuts out a number of unnecessary code,
      *       such as intermediate List allocation et al.
      */
-    final @NonNull String stringFromStringContext(final ArgumentContext context, final StatementSourceReference ref) {
-        // Get first child, which we fully expect to exist and be a lexer token
-        final ParseTree firstChild = context.getChild(0);
-        if (firstChild instanceof TerminalNode) {
-            // Simplest of cases -- it is a simple IDENTIFIER, hence we do not need to validate anything else and can
-            // just grab the string and run with it.
-            return firstChild.getText();
-        }
-
-        if (firstChild instanceof UnquotedStringContext) {
-            // Simple case, just grab the text, as ANTLR has done all the heavy lifting
-            final String str = firstChild.getText();
-            checkUnquoted(str, ref);
-            return str;
-        }
-
-        verify(firstChild instanceof QuotedStringContext, "Unexpected shape of %s", context);
-        if (context.getChildCount() == 1) {
-            // No concatenation needed, special-case
-            return unquoteString((QuotedStringContext) firstChild, ref);
+    final @NonNull String stringFromStringContext(final IRArgument argument, final StatementSourceReference ref) {
+        if (argument instanceof Single) {
+            final Single single = (Single) argument;
+            final String str = single.string();
+            if (single.needQuoteCheck()) {
+                checkUnquoted(str, ref);
+            }
+            return single.needUnescape() ? unescape(str, ref) : str;
         }
 
-        // Potentially-complex case of string quoting, escaping and concatenation.
-        return concatStrings(context, ref);
+        verify(argument instanceof Concatenation, "Unexpected argument %s", argument);
+        return concatStrings(((Concatenation) argument).parts(), ref);
     }
 
-    private String unquoteString(final QuotedStringContext context, final StatementSourceReference ref) {
-        final ParseTree secondChild = context.getChild(1);
-        verify(secondChild instanceof TerminalNode, "Unexpected shape of %s", context);
-        final Token secondToken = ((TerminalNode) secondChild).getSymbol();
-        final int type = secondToken.getType();
-        switch (type) {
-            case YangStatementParser.DQUOT_END:
-            case YangStatementParser.SQUOT_END:
-                // We are missing actual body, hence this is an empty string
-                return "";
-            case YangStatementParser.SQUOT_STRING:
-                return secondChild.getText();
-            case YangStatementParser.DQUOT_STRING:
-                // We should be looking at the first token, which is DQUOT_START, but since it is a single-character
-                // token, let's not bother.
-                return normalizeDoubleQuoted(secondChild.getText(), secondToken.getCharPositionInLine() - 1, ref);
-            default:
-                throw new VerifyException("Unhandled token type " + type);
-        }
-    }
-
-    private String concatStrings(final ArgumentContext context, final StatementSourceReference ref) {
-        /*
-         * We have multiple fragments. Just search the tree. This code is equivalent to
-         *
-         *    context.quotedString().forEach(stringNode -> sb.append(unquoteString(stringNode, ref))
-         *
-         * except we minimize allocations which that would do.
-         */
+    private @NonNull String concatStrings(final List<? extends Single> parts, final StatementSourceReference ref) {
         final StringBuilder sb = new StringBuilder();
-        for (ParseTree child : context.children) {
-            if (child instanceof TerminalNode) {
-                final TerminalNode childNode = (TerminalNode) child;
-                switch (childNode.getSymbol().getType()) {
-                    case YangStatementParser.SEP:
-                    case YangStatementParser.PLUS:
-                        // Operator, which we are handling by concat
-                        break;
-                    default:
-                        throw new VerifyException("Unexpected symbol in " + childNode);
-                }
-            } else {
-                verify(child instanceof QuotedStringContext, "Unexpected fragment component %s", child);
-                sb.append(unquoteString((QuotedStringContext) child, ref));
-            }
+        for (Single part : parts) {
+            final String str = part.string();
+            sb.append(part.needUnescape() ? unescape(str, ref) : str);
         }
         return sb.toString();
     }
 
-    private String normalizeDoubleQuoted(final String str, final int dquot, final StatementSourceReference ref) {
-        // Whitespace normalization happens irrespective of further handling and has no effect on the result
-        final String stripped = trimWhitespace(str, dquot);
-
-        // Now we need to perform some amount of unescaping. This serves as a pre-check before we dispatch
-        // validation and processing (which will reuse the work we have done)
-        final int backslash = stripped.indexOf('\\');
-        return backslash == -1 ? stripped : unescape(ref, stripped, backslash);
-    }
-
     /*
      * NOTE: Enforcement and transformation logic done by these methods should logically reside in the lexer and ANTLR
      *       account the for it with lexer modes. We do not want to force a re-lexing phase in the parser just because
@@ -205,10 +137,17 @@ abstract class ArgumentContextUtils {
 
     abstract void checkUnquoted(String str, StatementSourceReference ref);
 
+    private @NonNull String unescape(final String str, final StatementSourceReference ref) {
+        // Now we need to perform some amount of unescaping. This serves as a pre-check before we dispatch
+        // validation and processing (which will reuse the work we have done)
+        final int backslash = str.indexOf('\\');
+        return backslash == -1 ? str : unescape(ref, str, backslash);
+    }
+
     /*
      * Unescape escaped double quotes, tabs, new line and backslash in the inner string and trim the result.
      */
-    private String unescape(final StatementSourceReference ref, final String str, final int backslash) {
+    private @NonNull String unescape(final StatementSourceReference ref, final String str, final int backslash) {
         checkDoubleQuoted(str, ref, backslash);
         StringBuilder sb = new StringBuilder(str.length());
         unescapeBackslash(sb, str, backslash);
@@ -255,79 +194,4 @@ abstract class ArgumentContextUtils {
                 sb.append(str, backslash, nextAfterBackslash + 1);
         }
     }
-
-    @VisibleForTesting
-    static String trimWhitespace(final String str, final int dquot) {
-        final int firstBrk = str.indexOf('\n');
-        if (firstBrk == -1) {
-            return str;
-        }
-
-        // Okay, we may need to do some trimming, set up a builder and append the first segment
-        final int length = str.length();
-        final StringBuilder sb = new StringBuilder(length);
-
-        // Append first segment, which needs only tail-trimming
-        sb.append(str, 0, trimTrailing(str, 0, firstBrk)).append('\n');
-
-        // With that out of the way, setup our iteration state. The string segment we are looking at is
-        // str.substring(start, end), which is guaranteed not to include any line breaks, i.e. end <= brk unless we are
-        // at the last segment.
-        int start = firstBrk + 1;
-        int brk = str.indexOf('\n', start);
-
-        // Loop over inner strings
-        while (brk != -1) {
-            trimLeadingAndAppend(sb, dquot, str, start, trimTrailing(str, start, brk)).append('\n');
-            start = brk + 1;
-            brk = str.indexOf('\n', start);
-        }
-
-        return trimLeadingAndAppend(sb, dquot, str, start, length).toString();
-    }
-
-    private static StringBuilder trimLeadingAndAppend(final StringBuilder sb, final int dquot, final String str,
-            final int start, final int end) {
-        int offset = start;
-        int pos = 0;
-
-        while (pos <= dquot) {
-            if (offset == end) {
-                // We ran out of data, nothing to append
-                return sb;
-            }
-
-            final char ch = str.charAt(offset);
-            if (ch == '\t') {
-                // tabs are to be treated as 8 spaces
-                pos += 8;
-            } else if (WHITESPACE_MATCHER.matches(ch)) {
-                pos++;
-            } else {
-                break;
-            }
-
-            offset++;
-        }
-
-        // We have expanded beyond double quotes, push equivalent spaces
-        while (pos - 1 > dquot) {
-            sb.append(' ');
-            pos--;
-        }
-
-        return sb.append(str, offset, end);
-    }
-
-    private static int trimTrailing(final String str, final int start, final int end) {
-        int ret = end;
-        while (ret > start) {
-            final int prev = ret - 1;
-            if (!WHITESPACE_MATCHER.matches(str.charAt(prev))) {
-                break;
-            }
-            ret = prev;
-        }
-        return ret;
-    }
 }
index 8f2659f97ed44f509b9ff07d08ea03b5051dcf92..eff3d111c1e6db3bba00c45facabfa5ff1a601c4 100644 (file)
@@ -7,21 +7,18 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.repo;
 
-import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.base.VerifyException;
 import java.util.Optional;
-import org.antlr.v4.runtime.Token;
-import org.antlr.v4.runtime.tree.ParseTree;
 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.common.YangVersion;
 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.ArgumentContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.KeywordContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Qualified;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
@@ -45,8 +42,8 @@ class StatementContextVisitor {
         this.prefixes = prefixes;
     }
 
-    void visit(final StatementContext context) {
-        processStatement(0, context);
+    void visit(final IRStatement stmt) {
+        processStatement(0, stmt);
     }
 
     /**
@@ -60,31 +57,29 @@ class StatementContextVisitor {
      * @param ref Source reference
      * @return valid QName for declared statement to be written, or null
      */
-    QName getValidStatementDefinition(final KeywordContext keyword, final StatementSourceReference ref) {
-        switch (keyword.getChildCount()) {
-            case 1:
-                final StatementDefinition def = stmtDef.get(QName.create(YangConstants.RFC6020_YIN_MODULE,
-                    keyword.getChild(0).getText()));
-                return def != null ? def.getStatementName() : null;
-            case 3:
-                if (prefixes == null) {
-                    // No prefixes to look up from
-                    return null;
-                }
+    QName getValidStatementDefinition(final IRKeyword keyword, final StatementSourceReference ref) {
+        if (keyword instanceof Qualified) {
+            return getValidStatementDefinition((Qualified) keyword, ref);
+        }
+        final StatementDefinition def = stmtDef.get(QName.create(YangConstants.RFC6020_YIN_MODULE,
+            keyword.identifier()));
+        return def != null ? def.getStatementName() : null;
+    }
 
-                final String prefix = keyword.getChild(0).getText();
-                final QNameModule qNameModule = prefixes.get(prefix);
-                if (qNameModule == null) {
-                    // Failed to look the namespace
-                    return null;
-                }
+    private QName getValidStatementDefinition(final Qualified keyword, final StatementSourceReference ref) {
+        if (prefixes == null) {
+            // No prefixes to look up from
+            return null;
+        }
 
-                final String localName = keyword.getChild(2).getText();
-                final StatementDefinition foundStmtDef = resolveStatement(qNameModule, localName);
-                return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
-            default:
-                throw new VerifyException("Unexpected shape of " + keyword);
+        final QNameModule qNameModule = prefixes.get(keyword.prefix());
+        if (qNameModule == null) {
+            // Failed to look the namespace
+            return null;
         }
+
+        final StatementDefinition foundStmtDef = resolveStatement(qNameModule, keyword.identifier());
+        return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
     }
 
     StatementDefinition resolveStatement(final QNameModule module, final String localName) {
@@ -92,40 +87,37 @@ class StatementContextVisitor {
     }
 
     // Normal entry point, checks for potential resume
-    private boolean processStatement(final int myOffset, final StatementContext ctx) {
+    private boolean processStatement(final int myOffset, final IRStatement stmt) {
         final Optional<? extends ResumedStatement> optResumed = writer.resumeStatement(myOffset);
         if (optResumed.isPresent()) {
             final ResumedStatement resumed = optResumed.get();
-            return resumed.isFullyDefined() || doProcessStatement(ctx, resumed.getSourceReference());
+            return resumed.isFullyDefined() || doProcessStatement(stmt, resumed.getSourceReference());
         }
-        return processNewStatement(myOffset, ctx);
+        return processNewStatement(myOffset, stmt);
     }
 
     // Slow-path allocation of a new statement
-    private boolean processNewStatement(final int myOffset, final StatementContext ctx) {
-        final Token start = ctx.getStart();
-        final StatementSourceReference ref = DeclarationInTextSource.atPosition(sourceName, start.getLine(),
-            start.getCharPositionInLine());
-        final QName def = getValidStatementDefinition(verifyNotNull(ctx.getChild(KeywordContext.class, 0)), ref);
+    private boolean processNewStatement(final int myOffset, final IRStatement stmt) {
+        final StatementSourceReference ref = DeclarationInTextSource.atPosition(sourceName, stmt.startLine(),
+            stmt.startColumn());
+        final QName def = getValidStatementDefinition(stmt.keyword(), ref);
         if (def == null) {
             return false;
         }
 
-        final ArgumentContext argumentCtx = ctx.getChild(ArgumentContext.class, 0);
+        final IRArgument argumentCtx = stmt.argument();
         final String argument = argumentCtx == null ? null : utils.stringFromStringContext(argumentCtx, ref);
         writer.startStatement(myOffset, def, argument, ref);
-        return doProcessStatement(ctx, ref);
+        return doProcessStatement(stmt, ref);
     }
 
     // Actual processing
-    private boolean doProcessStatement(final StatementContext ctx, final StatementSourceReference ref) {
+    private boolean doProcessStatement(final IRStatement stmt, final StatementSourceReference ref) {
         int childOffset = 0;
         boolean fullyDefined = true;
-        if (ctx.children != null) {
-            for (ParseTree s : ctx.children) {
-                if (s instanceof StatementContext && !processStatement(childOffset++, (StatementContext) s)) {
-                    fullyDefined = false;
-                }
+        for (IRStatement substatement : stmt.statements()) {
+            if (!processStatement(childOffset++, substatement)) {
+                fullyDefined = false;
             }
         }
 
index 4d593813c39188b530f50c8fca0a4d2a48fd7ee4..dc450fba3a3ca43b3470b72c8edf71b52b003dac 100644 (file)
@@ -43,7 +43,6 @@ public final class TextToASTTransformer extends SchemaSourceTransformer<YangText
 
         // TODO: missing validation (YangModelBasicValidationListener should be re-implemented to new parser)
 
-        return ASTSchemaSource.create(text.getIdentifier(), text.getSymbolicName().orElse(null),
-            src.statementContext());
+        return ASTSchemaSource.create(text.getIdentifier(), text.getSymbolicName().orElse(null), src.rootStatement());
     }
 }
index 61f35683254bf4fb3dbf744a614d5bab3ad52c07..b5f2d437e60171afcaff532931d9fdf9ef29335e 100644 (file)
@@ -16,11 +16,9 @@ import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import org.antlr.v4.runtime.ParserRuleContext;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.yangtools.concepts.SemVer;
@@ -32,8 +30,10 @@ import org.opendaylight.yangtools.yang.model.api.YangStmtMapping;
 import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException;
 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.ArgumentContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword.Unqualified;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
 import org.opendaylight.yangtools.yang.parser.spi.source.DeclarationInTextSource;
 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
 
@@ -166,28 +166,20 @@ public abstract class YangModelDependencyInfo {
      * Extracts {@link YangModelDependencyInfo} from an abstract syntax tree of a YANG model.
      *
      * @param source Source identifier
-     * @param tree Abstract syntax tree
+     * @param rootStatement root statement
      * @return {@link YangModelDependencyInfo}
-     * @throws YangSyntaxErrorException If the AST is not a valid YANG module/submodule
+     * @throws IllegalArgumentException If the AST is not a valid YANG module/submodule
      */
-    static @NonNull YangModelDependencyInfo fromAST(final SourceIdentifier source, final ParserRuleContext tree)
-            throws YangSyntaxErrorException {
-
-        if (tree instanceof StatementContext) {
-            final StatementContext rootStatement = (StatementContext) tree;
-            return parseAST(rootStatement, source);
-        }
-
-        throw new YangSyntaxErrorException(source, 0, 0, "Unknown YANG text type");
-    }
-
-    private static @NonNull YangModelDependencyInfo parseAST(final StatementContext rootStatement,
+    static @NonNull YangModelDependencyInfo parseAST(final IRStatement rootStatement,
             final SourceIdentifier source) {
-        final String keyWordText = rootStatement.keyword().getText();
-        if (MODULE.equals(keyWordText)) {
+        final IRKeyword keyword = rootStatement.keyword();
+        checkArgument(keyword instanceof Unqualified, "Invalid root statement %s", keyword);
+
+        final String arg = keyword.identifier();
+        if (MODULE.equals(arg)) {
             return parseModuleContext(rootStatement, source);
         }
-        if (SUBMODULE.equals(keyWordText)) {
+        if (SUBMODULE.equals(arg)) {
             return parseSubmoduleContext(rootStatement, source);
         }
         throw new IllegalArgumentException("Root of parsed AST must be either module or submodule");
@@ -210,10 +202,10 @@ public abstract class YangModelDependencyInfo {
             throws IOException, YangSyntaxErrorException {
         final YangStatementStreamSource source = YangStatementStreamSource.create(
             YangTextSchemaSource.forResource(refClass, resourceName));
-        return parseAST(source.statementContext(), source.getIdentifier());
+        return parseAST(source.rootStatement(), source.getIdentifier());
     }
 
-    private static @NonNull YangModelDependencyInfo parseModuleContext(final StatementContext module,
+    private static @NonNull YangModelDependencyInfo parseModuleContext(final IRStatement module,
             final SourceIdentifier source) {
         final String name = safeStringArgument(source, module, "module name");
         final String latestRevision = getLatestRevision(module, source);
@@ -224,28 +216,27 @@ public abstract class YangModelDependencyInfo {
         return new ModuleDependencyInfo(name, latestRevision, imports, includes, semVer);
     }
 
-    private static ImmutableSet<ModuleImport> parseImports(final StatementContext module,
+    private static ImmutableSet<ModuleImport> parseImports(final IRStatement module,
             final SourceIdentifier source) {
         final Set<ModuleImport> result = new HashSet<>();
-        for (final StatementContext subStatementContext : module.statement()) {
-            if (IMPORT.equals(subStatementContext.keyword().getText())) {
-                final String importedModuleName = safeStringArgument(source, subStatementContext,
-                    "imported module name");
-                final String revisionDateStr = getRevisionDateString(subStatementContext, source);
+        for (final IRStatement substatement : module.statements()) {
+            if (isBuiltin(substatement, IMPORT)) {
+                final String importedModuleName = safeStringArgument(source, substatement, "imported module name");
+                final String revisionDateStr = getRevisionDateString(substatement, source);
                 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
-                final SemVer importSemVer = findSemanticVersion(subStatementContext, source);
+                final SemVer importSemVer = findSemanticVersion(substatement, source);
                 result.add(new ModuleImportImpl(importedModuleName, revisionDate, importSemVer));
             }
         }
         return ImmutableSet.copyOf(result);
     }
 
-    private static SemVer findSemanticVersion(final StatementContext statement, final SourceIdentifier source) {
+    private static SemVer findSemanticVersion(final IRStatement statement, final SourceIdentifier source) {
         String semVerString = null;
-        for (final StatementContext subStatement : statement.statement()) {
-            final String subStatementName = trimPrefix(subStatement.keyword().getText());
-            if (OPENCONFIG_VERSION.equals(subStatementName)) {
-                semVerString = safeStringArgument(source,  subStatement, "version string");
+        for (final IRStatement substatement : statement.statements()) {
+            // FIXME: this should also check we are using a prefix
+            if (OPENCONFIG_VERSION.equals(substatement.keyword().identifier())) {
+                semVerString = safeStringArgument(source,  substatement, "version string");
                 break;
             }
         }
@@ -253,24 +244,17 @@ public abstract class YangModelDependencyInfo {
         return Strings.isNullOrEmpty(semVerString) ? null : SemVer.valueOf(semVerString);
     }
 
-
-    private static String trimPrefix(final String identifier) {
-        final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
-        if (namesParts.size() == 2) {
-            return namesParts.get(1);
-        }
-        return identifier;
+    private static boolean isBuiltin(final IRStatement stmt, final String localName) {
+        final IRKeyword keyword = stmt.keyword();
+        return keyword instanceof Unqualified && localName.equals(keyword.identifier());
     }
 
-
-    private static ImmutableSet<ModuleImport> parseIncludes(final StatementContext module,
-            final SourceIdentifier source) {
+    private static ImmutableSet<ModuleImport> parseIncludes(final IRStatement module, final SourceIdentifier source) {
         final Set<ModuleImport> result = new HashSet<>();
-        for (final StatementContext subStatementContext : module.statement()) {
-            if (INCLUDE.equals(subStatementContext.keyword().getText())) {
-                final String revisionDateStr = getRevisionDateString(subStatementContext, source);
-                final String IncludeModuleName = safeStringArgument(source, subStatementContext,
-                    "included submodule name");
+        for (final IRStatement substatement : module.statements()) {
+            if (isBuiltin(substatement, INCLUDE)) {
+                final String revisionDateStr = getRevisionDateString(substatement, source);
+                final String IncludeModuleName = safeStringArgument(source, substatement, "included submodule name");
                 final Revision revisionDate = Revision.ofNullable(revisionDateStr).orElse(null);
                 result.add(new ModuleImportImpl(IncludeModuleName, revisionDate));
             }
@@ -278,21 +262,21 @@ public abstract class YangModelDependencyInfo {
         return ImmutableSet.copyOf(result);
     }
 
-    private static String getRevisionDateString(final StatementContext importStatement, final SourceIdentifier source) {
+    private static String getRevisionDateString(final IRStatement importStatement, final SourceIdentifier source) {
         String revisionDateStr = null;
-        for (final StatementContext importSubStatement : importStatement.statement()) {
-            if (REVISION_DATE.equals(importSubStatement.keyword().getText())) {
-                revisionDateStr = safeStringArgument(source, importSubStatement, "imported module revision-date");
+        for (final IRStatement substatement : importStatement.statements()) {
+            if (isBuiltin(substatement, REVISION_DATE)) {
+                revisionDateStr = safeStringArgument(source, substatement, "imported module revision-date");
             }
         }
         return revisionDateStr;
     }
 
-    public static String getLatestRevision(final StatementContext module, final SourceIdentifier source) {
+    public static String getLatestRevision(final IRStatement module, final SourceIdentifier source) {
         String latestRevision = null;
-        for (final StatementContext subStatementContext : module.statement()) {
-            if (REVISION.equals(subStatementContext.keyword().getText())) {
-                final String currentRevision = safeStringArgument(source, subStatementContext, "revision date");
+        for (final IRStatement substatement : module.statements()) {
+            if (isBuiltin(substatement, REVISION)) {
+                final String currentRevision = safeStringArgument(source, substatement, "revision date");
                 if (latestRevision == null || latestRevision.compareTo(currentRevision) < 0) {
                     latestRevision = currentRevision;
                 }
@@ -301,7 +285,7 @@ public abstract class YangModelDependencyInfo {
         return latestRevision;
     }
 
-    private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final StatementContext submodule,
+    private static @NonNull YangModelDependencyInfo parseSubmoduleContext(final IRStatement submodule,
             final SourceIdentifier source) {
         final String name = safeStringArgument(source, submodule, "submodule name");
         final String belongsTo = parseBelongsTo(submodule, source);
@@ -313,28 +297,25 @@ public abstract class YangModelDependencyInfo {
         return new SubmoduleDependencyInfo(name, latestRevision, belongsTo, imports, includes);
     }
 
-    private static String parseBelongsTo(final StatementContext submodule, final SourceIdentifier source) {
-        for (final StatementContext subStatementContext : submodule.statement()) {
-            if (BELONGS_TO.equals(subStatementContext.keyword().getText())) {
-                return safeStringArgument(source, subStatementContext, "belongs-to module name");
+    private static String parseBelongsTo(final IRStatement submodule, final SourceIdentifier source) {
+        for (final IRStatement substatement : submodule.statements()) {
+            if (isBuiltin(substatement, BELONGS_TO)) {
+                return safeStringArgument(source, substatement, "belongs-to module name");
             }
         }
         return null;
     }
 
-    private static String safeStringArgument(final SourceIdentifier source, final StatementContext stmt,
-            final String desc) {
+    private static String safeStringArgument(final SourceIdentifier source, final IRStatement stmt, final String desc) {
         final StatementSourceReference ref = getReference(source, stmt);
-        final ArgumentContext arg = stmt.argument();
+        final IRArgument arg = stmt.argument();
         checkArgument(arg != null, "Missing %s at %s", desc, ref);
         // TODO: we probably need to understand yang version first....
         return ArgumentContextUtils.rfc6020().stringFromStringContext(arg, ref);
     }
 
-    private static StatementSourceReference getReference(final SourceIdentifier source,
-            final StatementContext context) {
-        return DeclarationInTextSource.atPosition(source.getName(), context.getStart().getLine(),
-            context.getStart().getCharPositionInLine());
+    private static StatementSourceReference getReference(final SourceIdentifier source, final IRStatement stmt) {
+        return DeclarationInTextSource.atPosition(source.getName(), stmt.startLine(), stmt.startColumn());
     }
 
     /**
index 98d3aac98d6112264f990566e15c613671e36bfd..77e46b8e451ee0ed818601b39715ce537f643c51 100644 (file)
@@ -7,21 +7,13 @@
  */
 package org.opendaylight.yangtools.yang.parser.rfc7950.repo;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.io.InputStream;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.ParserRuleContext;
-import org.antlr.v4.runtime.tree.ErrorNode;
-import org.antlr.v4.runtime.tree.ParseTreeListener;
-import org.antlr.v4.runtime.tree.ParseTreeWalker;
-import org.antlr.v4.runtime.tree.TerminalNode;
 import org.opendaylight.yangtools.concepts.AbstractIdentifiable;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
@@ -33,9 +25,9 @@ import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
 import org.opendaylight.yangtools.yang.parser.antlr.YangStatementLexer;
 import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser;
 import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.FileContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.KeywordContext;
-import org.opendaylight.yangtools.yang.parser.antlr.YangStatementParser.StatementContext;
 import org.opendaylight.yangtools.yang.parser.rfc7950.antlr.CompactYangStatementLexer;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRKeyword;
+import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRStatement;
 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
@@ -52,35 +44,13 @@ import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter;
 @Beta
 public final class YangStatementStreamSource extends AbstractIdentifiable<SourceIdentifier>
         implements StatementStreamSource {
-    private static final ParseTreeListener MAKE_IMMUTABLE_LISTENER = new ParseTreeListener() {
-        @Override
-        public void enterEveryRule(final ParserRuleContext ctx) {
-            // No-op
-        }
-
-        @Override
-        public void exitEveryRule(final ParserRuleContext ctx) {
-            ctx.children = ctx.children == null ? ImmutableList.of() : ImmutableList.copyOf(ctx.children);
-        }
-
-        @Override
-        public void visitTerminal(final TerminalNode node) {
-            // No-op
-        }
-
-        @Override
-        public void visitErrorNode(final ErrorNode node) {
-            // No-op
-        }
-    };
-
-    private final StatementContext context;
+    private final IRStatement rootStatement;
     private final String sourceName;
 
-    private YangStatementStreamSource(final SourceIdentifier identifier,  final StatementContext context,
+    private YangStatementStreamSource(final SourceIdentifier identifier,  final IRStatement rootStatement,
             final String sourceName) {
         super(identifier);
-        this.context = requireNonNull(context);
+        this.rootStatement = requireNonNull(rootStatement);
         this.sourceName = sourceName;
     }
 
@@ -94,12 +64,13 @@ public final class YangStatementStreamSource extends AbstractIdentifiable<Source
      */
     public static YangStatementStreamSource create(final YangTextSchemaSource source) throws IOException,
             YangSyntaxErrorException {
-        final StatementContext context;
+        final IRStatement rootStatement;
         try (InputStream stream = source.openStream()) {
-            context = parseYangSource(source.getIdentifier(), stream);
+            rootStatement = parseYangSource(source.getIdentifier(), stream);
         }
 
-        return new YangStatementStreamSource(source.getIdentifier(), context, source.getSymbolicName().orElse(null));
+        return new YangStatementStreamSource(source.getIdentifier(), rootStatement,
+            source.getSymbolicName().orElse(null));
     }
 
     /**
@@ -109,20 +80,17 @@ public final class YangStatementStreamSource extends AbstractIdentifiable<Source
      * @return A new {@link YangStatementStreamSource}
      */
     public static YangStatementStreamSource create(final ASTSchemaSource source) {
-        final ParserRuleContext ast = source.getAST();
-        checkArgument(ast instanceof StatementContext,
-                "Unsupported context class %s for source %s", ast.getClass(), source.getIdentifier());
-        return create(source.getIdentifier(), (StatementContext) ast, source.getSymbolicName().orElse(null));
+        return create(source.getIdentifier(), source.getRootStatement(), source.getSymbolicName().orElse(null));
     }
 
-    public static YangStatementStreamSource create(final SourceIdentifier identifier, final StatementContext context,
-        final String symbolicName) {
-        return new YangStatementStreamSource(identifier, context, symbolicName);
+    public static YangStatementStreamSource create(final SourceIdentifier identifier, final IRStatement rootStatement,
+            final String symbolicName) {
+        return new YangStatementStreamSource(identifier, rootStatement, symbolicName);
     }
 
     @Override
     public void writePreLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
-        new StatementContextVisitor(sourceName, writer, stmtDef, null, YangVersion.VERSION_1).visit(context);
+        new StatementContextVisitor(sourceName, writer, stmtDef, null, YangVersion.VERSION_1).visit(rootStatement);
     }
 
     @Override
@@ -133,13 +101,13 @@ public final class YangStatementStreamSource extends AbstractIdentifiable<Source
             StatementDefinition resolveStatement(final QNameModule module, final String localName) {
                 return stmtDef.getByNamespaceAndLocalName(module.getNamespace(), localName);
             }
-        }.visit(context);
+        }.visit(rootStatement);
     }
 
     @Override
     public void writeLinkageAndStatementDefinitions(final StatementWriter writer,
             final QNameToStatementDefinition stmtDef, final PrefixToModule prefixes, final YangVersion yangVersion) {
-        new StatementContextVisitor(sourceName, writer, stmtDef, prefixes, yangVersion).visit(context);
+        new StatementContextVisitor(sourceName, writer, stmtDef, prefixes, yangVersion).visit(rootStatement);
     }
 
     @Override
@@ -147,22 +115,22 @@ public final class YangStatementStreamSource extends AbstractIdentifiable<Source
             final PrefixToModule prefixes, final YangVersion yangVersion) {
         new StatementContextVisitor(sourceName, writer, stmtDef, prefixes, yangVersion) {
             @Override
-            QName getValidStatementDefinition(final KeywordContext keyword, final StatementSourceReference ref) {
+            QName getValidStatementDefinition(final IRKeyword keyword, final StatementSourceReference ref) {
                 final QName ret = super.getValidStatementDefinition(keyword, ref);
                 if (ret == null) {
                     throw new SourceException(ref, "%s is not a YANG statement or use of extension.",
-                        keyword.getText());
+                        keyword.asStringDeclaration());
                 }
                 return ret;
             }
-        }.visit(context);
+        }.visit(rootStatement);
     }
 
-    StatementContext statementContext() {
-        return context;
+    IRStatement rootStatement() {
+        return rootStatement;
     }
 
-    private static StatementContext parseYangSource(final SourceIdentifier source, final InputStream stream)
+    private static IRStatement parseYangSource(final SourceIdentifier source, final InputStream stream)
             throws IOException, YangSyntaxErrorException {
         final YangStatementLexer lexer = new CompactYangStatementLexer(CharStreams.fromStream(stream));
         final YangStatementParser parser = new YangStatementParser(new CommonTokenStream(lexer));
@@ -176,12 +144,6 @@ public final class YangStatementStreamSource extends AbstractIdentifiable<Source
 
         final FileContext result = parser.file();
         errorListener.validate();
-
-        // Walk the resulting tree and replace each children with an immutable list, lowering memory requirements
-        // and making sure the resulting tree will not get accidentally modified. An alternative would be to use
-        // org.antlr.v4.runtime.Parser.TrimToSizeListener, but that does not make the tree immutable.
-        ParseTreeWalker.DEFAULT.walk(MAKE_IMMUTABLE_LISTENER, result);
-
-        return verifyNotNull(result.statement());
+        return IRStatement.forContext(result.statement());
     }
 }
diff --git a/yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/StatementFactoryTest.java b/yang/yang-parser-rfc7950/src/test/java/org/opendaylight/yangtools/yang/parser/rfc7950/ir/StatementFactoryTest.java
new file mode 100644 (file)
index 0000000..a845fdb
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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
+ */
+package org.opendaylight.yangtools.yang.parser.rfc7950.ir;
+
+import static org.junit.Assert.assertEquals;
+import static org.opendaylight.yangtools.yang.parser.rfc7950.ir.StatementFactory.trimWhitespace;
+
+import org.junit.Test;
+
+public class StatementFactoryTest {
+    @Test
+    public void testTrimWhitespace() {
+        assertEquals("\n", trimWhitespace("\n", 0));
+        assertEquals("\n", trimWhitespace("\n", 5));
+        assertEquals("\n\n\n\n", trimWhitespace("\n\n\n\n", 0));
+        assertEquals("\n\n\n\n", trimWhitespace("\n\n\n\n", 5));
+        assertEquals("abc\n\n", trimWhitespace("abc \n  \n", 0));
+        assertEquals("abc\n\n", trimWhitespace("abc \n  \n", 1));
+        assertEquals("abc\n  ", trimWhitespace("abc\n   ", 0));
+        assertEquals("abc\n", trimWhitespace("abc\n   ", 2));
+        assertEquals("abc\n\n", trimWhitespace("abc\n   \n", 2));
+        assertEquals("abc\n        ", trimWhitespace("abc\n\t ", 0));
+        assertEquals("abc\n      ", trimWhitespace("abc\n\t ", 2));
+        assertEquals("abc\n    ", trimWhitespace("abc\n\t ", 4));
+        assertEquals("abc\n    ", trimWhitespace("abc\n \t", 4));
+        assertEquals("abc\n   a\n    a\n", trimWhitespace("abc\n\ta\n\t a\n", 4));
+        assertEquals("abc\n\n    a\n", trimWhitespace("abc\n\t\n\t a\n", 4));
+        assertEquals("   \ta\n", trimWhitespace("   \ta\n", 3));
+        assertEquals("   \ta\n", trimWhitespace("   \ta\n  ", 3));
+        assertEquals("   \ta\n", trimWhitespace("   \ta\n   ", 3));
+        assertEquals("   \ta\n", trimWhitespace("   \ta\n    ", 3));
+        assertEquals("   \ta\n ", trimWhitespace("   \ta\n     ", 3));
+    }
+}
index fba1589b9116826a52f2c3b497a876f8fb7ee113..72d0d0b217cf70ecb01eda011b03e84c60d425c3 100644 (file)
@@ -9,7 +9,6 @@ package org.opendaylight.yangtools.yang.parser.rfc7950.repo;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.opendaylight.yangtools.yang.parser.rfc7950.repo.ArgumentContextUtils.trimWhitespace;
 import static org.opendaylight.yangtools.yang.parser.rfc7950.repo.ArgumentContextUtils.unescapeBackslash;
 
 import java.io.File;
@@ -20,30 +19,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.stmt.StmtTestUtils;
 
 public class ArgumentContextUtilsTest {
-    @Test
-    public void testTrimWhitespace() {
-        assertEquals("\n", trimWhitespace("\n", 0));
-        assertEquals("\n", trimWhitespace("\n", 5));
-        assertEquals("\n\n\n\n", trimWhitespace("\n\n\n\n", 0));
-        assertEquals("\n\n\n\n", trimWhitespace("\n\n\n\n", 5));
-        assertEquals("abc\n\n", trimWhitespace("abc \n  \n", 0));
-        assertEquals("abc\n\n", trimWhitespace("abc \n  \n", 1));
-        assertEquals("abc\n  ", trimWhitespace("abc\n   ", 0));
-        assertEquals("abc\n", trimWhitespace("abc\n   ", 2));
-        assertEquals("abc\n\n", trimWhitespace("abc\n   \n", 2));
-        assertEquals("abc\n        ", trimWhitespace("abc\n\t ", 0));
-        assertEquals("abc\n      ", trimWhitespace("abc\n\t ", 2));
-        assertEquals("abc\n    ", trimWhitespace("abc\n\t ", 4));
-        assertEquals("abc\n    ", trimWhitespace("abc\n \t", 4));
-        assertEquals("abc\n   a\n    a\n", trimWhitespace("abc\n\ta\n\t a\n", 4));
-        assertEquals("abc\n\n    a\n", trimWhitespace("abc\n\t\n\t a\n", 4));
-        assertEquals("   \ta\n", trimWhitespace("   \ta\n", 3));
-        assertEquals("   \ta\n", trimWhitespace("   \ta\n  ", 3));
-        assertEquals("   \ta\n", trimWhitespace("   \ta\n   ", 3));
-        assertEquals("   \ta\n", trimWhitespace("   \ta\n    ", 3));
-        assertEquals("   \ta\n ", trimWhitespace("   \ta\n     ", 3));
-    }
-
     @Test
     public void testUnescapeNew() {
         //      a\b -----> a\b  (invalid for 7950)