1<?php
2
3declare(strict_types=1);
4
5namespace Antlr\Antlr4\Runtime;
6
7/**
8 * This class provides a default implementation of the {@see Vocabulary}
9 * interface.
10 *
11 * @author Sam Harwell
12 */
13final class VocabularyImpl implements Vocabulary
14{
15    /** @var array<string|null> */
16    private $literalNames;
17
18    /** @var array<string|null> */
19    private $symbolicNames;
20
21    /** @var array<string|null> */
22    private $displayNames;
23
24    /** @var int */
25    private $maxTokenType;
26
27    /**
28     * Constructs a new instance from the specified literal, symbolic
29     * and display token names.
30     *
31     * @param array<string|null> $literalNames  The literal names assigned
32     *                                          to tokens, or `null` if no
33     *                                          literal names are assigned.
34     * @param array<string|null> $symbolicNames The symbolic names assigned
35     *                                          to tokens, or `null` if
36     *                                          no symbolic names are assigned.
37     * @param array<string|null> $displayNames  The display names assigned
38     *                                          to tokens, or `null` to use
39     *                                          the values in literalNames` and
40     *                                          `symbolicNames` as the source
41     *                                          of display names, as described
42     *                                          in {@see VocabularyImpl::getDisplayName()}.
43     */
44    public function __construct(array $literalNames = [], array $symbolicNames = [], array $displayNames = [])
45    {
46        $this->literalNames = $literalNames;
47        $this->symbolicNames = $symbolicNames;
48        $this->displayNames = $displayNames;
49
50        // See note here on -1 part: https://github.com/antlr/antlr4/pull/1146
51        $this->maxTokenType = \max(
52            \count($this->displayNames),
53            \count($this->literalNames),
54            \count($this->symbolicNames)
55        ) - 1;
56    }
57
58    /**
59     * Gets an empty {@see Vocabulary} instance.
60     *
61     * No literal or symbol names are assigned to token types, so
62     * {@see Vocabulary::getDisplayName()} returns the numeric value for
63     * all tokens except {@see Token::EOF}.
64     */
65    public static function emptyVocabulary() : self
66    {
67        static $empty;
68
69        return $empty ?? ($empty = new self());
70    }
71
72    /**
73     * Returns a {@see VocabularyImpl} instance from the specified set
74     * of token names. This method acts as a compatibility layer for the single
75     * `tokenNames` array generated by previous releases of ANTLR.
76     *
77     * The resulting vocabulary instance returns `null` for
78     * {@see VocabularyImpl::getLiteralName()} and {@see VocabularyImpl::getSymbolicName()},
79     * and the value from `tokenNames` for the display names.
80     *
81     * @param array<string|null> $tokenNames The token names, or `null` if
82     *                                       no token names are available.
83     *
84     * @return Vocabulary A {@see Vocabulary} instance which uses `tokenNames`
85     *                    for the display names of tokens.
86     */
87    public static function fromTokenNames(array $tokenNames = []) : Vocabulary
88    {
89        if (\count($tokenNames) === 0) {
90            return self::emptyVocabulary();
91        }
92
93        $literalNames = $tokenNames; // copy array
94        $symbolicNames = $tokenNames; // copy array
95
96        foreach ($tokenNames as $i => $tokenName) {
97            if ($tokenName === null) {
98                continue;
99            }
100
101            if ($tokenName !== '') {
102                $firstChar = $tokenName[0];
103
104                if ($firstChar === '\'') {
105                    $symbolicNames[$i] = null;
106
107                    continue;
108                }
109
110                if (\ctype_upper($firstChar)) {
111                    $literalNames[$i] = null;
112
113                    continue;
114                }
115            }
116
117            // wasn't a literal or symbolic name
118            $literalNames[$i] = null;
119            $symbolicNames[$i] = null;
120        }
121
122        return new VocabularyImpl($literalNames, $symbolicNames, $tokenNames);
123    }
124
125    public function getMaxTokenType() : int
126    {
127        return $this->maxTokenType;
128    }
129
130    public function getLiteralName(int $tokenType) : ?string
131    {
132        if ($tokenType >= 0 && $tokenType < \count($this->literalNames)) {
133            return $this->literalNames[$tokenType];
134        }
135
136        return null;
137    }
138
139    public function getSymbolicName(int $tokenType) : ?string
140    {
141        if ($tokenType >= 0 && $tokenType < \count($this->symbolicNames)) {
142            return $this->symbolicNames[$tokenType];
143        }
144
145        if ($tokenType === Token::EOF) {
146            return 'EOF';
147        }
148
149        return null;
150    }
151
152    public function getDisplayName(int $tokenType) : string
153    {
154        if ($tokenType >= 0 && $tokenType < \count($this->displayNames)) {
155            $displayName = $this->displayNames[$tokenType];
156
157            if ($displayName !== null) {
158                return $displayName;
159            }
160        }
161
162        $literalName = $this->getLiteralName($tokenType);
163
164        if ($literalName !== null) {
165            return $literalName;
166        }
167
168        $symbolicName = $this->getSymbolicName($tokenType);
169
170        if ($symbolicName !== null) {
171            return $symbolicName;
172        }
173
174        return (string) $tokenType;
175    }
176}
177