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