1 /*
2  * Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
3  * Use of this file is governed by the BSD 3-clause license that
4  * can be found in the LICENSE.txt file in the project root.
5  */
6 
7 module antlr.v4.runtime.RuntimeMetaData;
8 
9 import std.algorithm : min;
10 import std.stdio : stderr;
11 import std.string : indexOf;
12 
13 /**
14  * This class provides access to the current version of the ANTLR 4 runtime
15  * library as compile-time and runtime constants, along with methods for
16  * checking for matching version numbers and notifying listeners in the case
17  * where a version mismatch is detected.
18  *
19  * <p>
20  * The runtime version information is provided by {@link #VERSION} and
21  * {@link #getRuntimeVersion()}. Detailed information about these values is
22  * provided in the documentation for each member.</p>
23  *
24  * <p>
25  * The runtime version check is implemented by {@link #checkVersion}. Detailed
26  * information about incorporating this call into user code, as well as its use
27  * in generated code, is provided in the documentation for the method.</p>
28  *
29  * <p>
30  * Version strings x.y and x.y.z are considered "compatible" and no error
31  * would be generated. Likewise, version strings x.y-SNAPSHOT and x.y.z are
32  * considered "compatible" because the major and minor components x.y
33  * are the same in each.</p>
34  *
35  * <p>
36  * To trap any error messages issued by this code, use System.setErr()
37  * in your main() startup code.
38  * </p>
39  */
40 class RuntimeMetaData
41 {
42 
43     /**
44      * A compile-time constant containing the current version of the ANTLR 4
45      * runtime library.
46      *
47      * <p>
48      * This compile-time constant value allows generated parsers and other
49      * libraries to include a literal reference to the version of the ANTLR 4
50      * runtime library the code was compiled against. At each release, we
51      * change this value.</p>
52      *
53      * <p>Version numbers are assumed to have the form
54      *
55      * <em>major</em>.<em>minor</em>.<em>patch</em>.<em>revision</em>-<em>suffix</em>,
56      *
57      * with the individual components defined as follows.</p>
58      *
59      * <ul>
60      * <li><em>major</em> is a required non-negative integer, and is equal to
61      * {@code 4} for ANTLR 4.</li>
62      * <li><em>minor</em> is a required non-negative integer.</li>
63      * <li><em>patch</em> is an optional non-negative integer. When
64      * <em>patch</em> is omitted, the {@code .} (dot) appearing before it is
65      * also omitted.</li>
66      * <li><em>revision</em> is an optional non-negative integer, and may only
67      * be included when <em>patch</em> is also included. When <em>revision</em>
68      * is omitted, the {@code .} (dot) appearing before it is also omitted.</li>
69      * <li><em>suffix</em> is an optional string. When <em>suffix</em> is
70      * omitted, the {@code -} (hyphen-minus) appearing before it is also
71      * omitted.</li>
72      * </ul>
73      * @read
74      */
75     public static immutable string VERSION = "4.7.1";
76 
77     /**
78      * This method provides the ability to detect mismatches between the version
79      * of ANTLR 4 used to generate a parser, the version of the ANTLR runtime a
80      * parser was compiled against, and the version of the ANTLR runtime which
81      * is currently executing.
82      *
83      * <p>
84      * The version check is designed to detect the following two specific
85      * scenarios.</p>
86      *
87      * <ul>
88      * <li>The ANTLR Tool version used for code generation does not match the
89      * currently executing runtime version.</li>
90      * <li>The ANTLR Runtime version referenced at the time a parser was
91      * compiled does not match the currently executing runtime version.</li>
92      * </ul>
93      *
94      * <p>
95      * Starting with ANTLR 4.3, the code generator emits a call to this method
96      * using two constants in each generated lexer and parser: a hard-coded
97      * constant indicating the version of the tool used to generate the parser
98      * and a reference to the compile-time constant {@link #VERSION}. At
99      * runtime, this method is called during the initialization of the generated
100      * parser to detect mismatched versions, and notify the registered listeners
101      * prior to creating instances of the parser.</p>
102      *
103      * <p>
104      * This method does not perform any detection or filtering of semantic
105      * changes between tool and runtime versions. It simply checks for a
106      * version match and emits an error to stderr if a difference
107      * is detected.</p>
108      *
109      * <p>
110      * Note that some breaking changes between releases could result in other
111      * types of runtime exceptions, such as a {@link LinkageError}, prior to
112      * calling this method. In these cases, the underlying version mismatch will
113      * not be reported here. This method is primarily intended to
114      * notify users of potential semantic changes between releases that do not
115      * result in binary compatibility problems which would be detected by the
116      * class loader. As with semantic changes, changes that break binary
117      * compatibility between releases are mentioned in the release notes
118      * accompanying the affected release.</p>
119      *
120      * <p>
121      * <strong>Additional note for target developers:</strong> The version check
122      * implemented by this class is designed to address specific compatibility
123      * concerns that may arise during the execution of Java applications. Other
124      * targets should consider the implementation of this method in the context
125      * of that target's known execution environment, which may or may not
126      * resemble the design provided for the Java target.</p>
127      *
128      *  @param generatingToolVersion The version of the tool used to generate a parser.
129      * This value may be null when called from user code that was not generated
130      * by, and does not reference, the ANTLR 4 Tool itself.
131      *  @param compileTimeVersion The version of the runtime the parser was
132      * compiled against. This should always be passed using a direct reference
133      * to {@link #VERSION}.
134      */
135     public static void checkVersion(string generatingToolVersion, string compileTimeVersion)
136     {
137         string runtimeVersion = VERSION;
138         bool runtimeConflictsWithGeneratingTool = false;
139         bool runtimeConflictsWithCompileTimeTool = false;
140 
141         if (generatingToolVersion) {
142             runtimeConflictsWithGeneratingTool =
143                 (runtimeVersion != generatingToolVersion) &&
144                 (getMajorMinorVersion(runtimeVersion) != getMajorMinorVersion(generatingToolVersion));
145         }
146 
147         runtimeConflictsWithCompileTimeTool =
148             (runtimeVersion != compileTimeVersion) &&
149             (getMajorMinorVersion(runtimeVersion) != getMajorMinorVersion(compileTimeVersion));
150 
151         if (runtimeConflictsWithGeneratingTool) {
152             stderr.writefln("ANTLR Tool version %s used for code generation does not match the current runtime version %s",
153                               generatingToolVersion, runtimeVersion);
154         }
155         if ( runtimeConflictsWithCompileTimeTool ) {
156             stderr.writefln("ANTLR Runtime version %s used for parser compilation does not match the current runtime version %s",
157                               compileTimeVersion, runtimeVersion);
158         }
159 
160     }
161 
162     /**
163      * Gets the major and minor version numbers from a version string. For
164      * details about the syntax of the input {@code version}.
165      * E.g., from x.y.z return x.y.
166      *
167      *  @param version The complete version string.
168      *  @return A string of the form <em>major</em>.<em>minor</em> containing
169      *  only the major and minor components of the version string.
170      */
171     public static string getMajorMinorVersion(string antlr_version)
172     {
173         size_t firstDot = antlr_version.indexOf('.');
174         size_t secondDot = firstDot >= 0 ? antlr_version.indexOf('.', firstDot + 1) : -1;
175         size_t firstDash = antlr_version.indexOf('-');
176         size_t referenceLength = antlr_version.length;
177         if (secondDot >= 0) {
178             referenceLength = min(referenceLength, secondDot);
179         }
180 
181         if (firstDash >= 0) {
182             referenceLength = min(referenceLength, firstDash);
183         }
184 
185         return antlr_version[0..referenceLength];
186     }
187 
188 }
189 
190 version(unittest) {
191 
192     import dshould : be, equal, not, should;
193     import unit_threaded;
194 
195     class Test {
196 
197         @Tags("mt", "reg")
198         @("compareMetaDataVersion")
199         unittest {
200             "4.7".should.equal(RuntimeMetaData.getMajorMinorVersion(RuntimeMetaData.VERSION));
201             RuntimeMetaData.checkVersion("4.7", "4.7.1");
202         }
203     }
204 }