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