Passed
Push — master ( c01dfe...f39ce2 )
by Edward
04:04
created

TokenMatcherSpec::addTokenSpec()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
nc 3
nop 2
dl 0
loc 11
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Remorhaz\UniLex\Lexer;
6
7
use ReflectionClass;
8
use ReflectionException;
9
use ReflectionMethod;
10
use Remorhaz\UniLex\Exception;
11
12
class TokenMatcherSpec
13
{
14
15
    private $targetClassName;
16
17
    private $templateClassName;
18
19
    private $templateClass;
20
21
    private $usedClassList;
22
23
    private $targetNamespaceName;
24
25
    private $targetShortName;
26
27
    private $fileCommentList = [];
28
29
    private $header = '';
30
31
    private $beforeMatch = '';
32
33
    private $onError = '';
34
35
    private $onTransition = '';
36
37
    private $onToken = '';
38
39
    private $tokenSpecList = [];
40
41
    public function __construct(string $targetClassName, string $templateClassName)
42
    {
43
        $this->targetClassName = $targetClassName;
44
        $this->templateClassName = $templateClassName;
45
    }
46
47
    /**
48
     * @return ReflectionClass
49
     * @throws ReflectionException
50
     */
51
    public function getTemplateClass(): ReflectionClass
52
    {
53
        if (!isset($this->templateClass)) {
54
            $this->templateClass = new ReflectionClass($this->templateClassName);
55
        }
56
57
        return $this->templateClass;
58
    }
59
60
    public function getTargetClassName(): string
61
    {
62
        return $this->targetClassName;
63
    }
64
65
    /**
66
     * @param string      $name
67
     * @param string|null $alias
68
     * @return TokenMatcherSpec
69
     * @throws ReflectionException
70
     */
71
    public function addUsedClass(string $name, string $alias = null): self
72
    {
73
        $this->initUsedClassList();
74
        if (in_array($name, $this->usedClassList)) {
75
            return $this;
76
        }
77
        $class = new ReflectionClass($name);
78
        if ($this->getTargetNamespaceName() == $class->getNamespaceName()) {
79
            return $this;
80
        }
81
        if (isset($alias)) {
82
            $this->usedClassList[$alias] = $name;
83
84
            return $this;
85
        }
86
        $this->usedClassList[] = $name;
87
88
        return $this;
89
    }
90
91
    /**
92
     * @return array
93
     * @throws ReflectionException
94
     */
95
    public function getUsedClassList(): array
96
    {
97
        $this->initUsedClassList();
98
        $usedClassList = $this->usedClassList;
99
        asort($usedClassList);
100
101
        return $usedClassList;
102
    }
103
104
    public function addFileComment(string ...$textLineList): self
105
    {
106
        foreach ($textLineList as $textLine) {
107
            $this->fileCommentList[] = $textLine;
108
        }
109
110
        return $this;
111
    }
112
113
    public function getFileComment(): string
114
    {
115
        return implode("\n", $this->fileCommentList);
116
    }
117
118
    public function getTargetNamespaceName(): string
119
    {
120
        if (!isset($this->targetNamespaceName)) {
121
            [0 => $namespaceName] = $this->splitTargetClassName();
122
            $this->targetNamespaceName = $namespaceName;
123
        }
124
125
        return $this->targetNamespaceName;
126
    }
127
128
    public function getTargetShortName(): string
129
    {
130
        if (!isset($this->targetShortName)) {
131
            [1 => $shortName] = $this->splitTargetClassName();
132
            $this->targetShortName = $shortName;
133
        }
134
135
        return $this->targetShortName;
136
    }
137
138
    /**
139
     * @return ReflectionMethod
140
     * @throws ReflectionException
141
     */
142
    public function getMatchMethod(): ReflectionMethod
143
    {
144
        return $this->getTemplateClass()->getMethod('match');
145
    }
146
147
    public function setHeader(string $code): self
148
    {
149
        $this->header = $code;
150
151
        return $this;
152
    }
153
154
    public function getHeader(): string
155
    {
156
        return $this->header;
157
    }
158
159
    public function setBeforeMatch(string $code): self
160
    {
161
        $this->beforeMatch = $code;
162
163
        return $this;
164
    }
165
166
    public function getBeforeMatch(): string
167
    {
168
        return $this->beforeMatch;
169
    }
170
171
    public function setOnError(string $code): self
172
    {
173
        $this->onError = $code;
174
175
        return $this;
176
    }
177
178
    public function getOnError(): string
179
    {
180
        return $this->onError;
181
    }
182
183
    public function setOnTransition(string $code): self
184
    {
185
        $this->onTransition = $code;
186
187
        return $this;
188
    }
189
190
    public function getOnTransition(): string
191
    {
192
        return $this->onTransition;
193
    }
194
195
    public function setOnToken(string $code): self
196
    {
197
        $this->onToken = $code;
198
199
        return $this;
200
    }
201
202
    public function getOnToken(): string
203
    {
204
        return $this->onToken;
205
    }
206
207
    /**
208
     * @param string    $context
209
     * @param TokenSpec ...$tokenSpecList
210
     * @return TokenMatcherSpec
211
     * @throws Exception
212
     */
213
    public function addTokenSpec(string $context, TokenSpec ...$tokenSpecList): self
214
    {
215
        foreach ($tokenSpecList as $tokenSpec) {
216
            $regExp = $tokenSpec->getRegExp();
217
            if (isset($this->tokenSpecList[$context][$regExp])) {
218
                throw new Exception("Token spec for pattern {$regExp} is already set");
219
            }
220
            $this->tokenSpecList[$context][$regExp] = $tokenSpec;
221
        }
222
223
        return $this;
224
    }
225
226
    /**
227
     * @param string $mode
228
     * @return TokenSpec[]
229
     */
230
    public function getTokenSpecList(string $mode): array
231
    {
232
        return $this->tokenSpecList[$mode] ?? [];
233
    }
234
235
    public function getTokenSpec(string $mode, string $regExp): TokenSpec
236
    {
237
        foreach ($this->getTokenSpecList($mode) ?? [] as $tokenSpec) {
238
            if ($tokenSpec->getRegExp() == $regExp) {
239
                return $tokenSpec;
240
            }
241
        }
242
243
        throw new Exception("Token spec not found: {$regExp}");
244
    }
245
246
    /**
247
     * @return string[]
248
     */
249
    public function getModeList(): array
250
    {
251
        return array_keys($this->tokenSpecList);
252
    }
253
254
    private function splitTargetClassName(): array
255
    {
256
        $nameSpaceSeparator = '\\';
257
        $classNameParts = explode($nameSpaceSeparator, $this->getTargetClassName());
258
        $className = array_pop($classNameParts);
259
        $namespaceName = implode($nameSpaceSeparator, $classNameParts);
260
261
        return [$namespaceName, $className];
262
    }
263
264
    /**
265
     * @throws ReflectionException
266
     */
267
    private function initUsedClassList(): void
268
    {
269
        if (isset($this->usedClassList)) {
270
            return;
271
        }
272
        $this->usedClassList = [];
273
        foreach ($this->getMatchMethod()->getParameters() as $parameter) {
274
            if ($parameter->hasType() && !$parameter->getType()->isBuiltin()) {
275
                $this->addUsedClass($parameter->getType()->getName());
276
            }
277
        }
278
        $this->addUsedClass($this->getTemplateClass()->getName());
279
    }
280
}
281