Passed
Push — master ( 2a6b88...c849ec )
by Sergey
06:29
created

Tokenizer::removeComments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * @author: Viskov Sergey
4
 * @date: 3/10/16
5
 * @time: 1:33 PM
6
 */
7
8
namespace LTDBeget\sphinx;
9
10
11
use BadMethodCallException;
12
use LTDBeget\ascii\AsciiChar;
13
use LTDBeget\stringstream\StringStream;
14
15
/**
16
 * Class SphinxConfigurationParser
17
 * @package LTDBeget\sphinx\configurator\parser
18
 */
19
final class Tokenizer
20
{
21
    /**
22
     * parse and tokenize input string
23
     * @param string $plainData
24
     * @throws SyntaxErrorException
25
     * @throws BadMethodCallException
26
     * @return array
27
     * @throws \Hoa\Ustring\Exception
28 9
     * @throws \InvalidArgumentException
29
     * @throws \LogicException
30 9
     */
31
    public static function tokenize(string $plainData) : array
32
    {
33
        $tokens = [];
34
35
        if(! empty($plainData)) {
36
            $tokens = (new self($plainData))->tokenizeInternal()->tokens;
37
        }
38
39 9
        return $tokens;
40
    }
41 9
42 9
    /**
43 9
     * SphinxConfigurationParser constructor.
44
     * @internal
45
     * @param string $string
46
     * @throws BadMethodCallException
47
     * @throws \Hoa\Ustring\Exception
48
     */
49
    private function __construct(string $string)
50 9
    {
51
        $string       = $this->removeComments($string);
52 9
        $this->stream = new StringStream($string);
53
    }
54
55
    /**
56
     * @internal
57
     * @param string $string
58
     * @return string
59
     */
60 9
    private function removeComments(string $string) : string
61
    {
62
        return preg_replace("/(^#| #|	#).*\n/im", "\n", $string);
63 9
    }
64 2
65
    /**
66 2
     * @internal
67
     * @return Tokenizer
68 2
     * @throws \LogicException
69
     * @throws \InvalidArgumentException
70
     * @throws SyntaxErrorException
71
     */
72
    private function tokenizeInternal() : Tokenizer
73
    {
74
        do {
75 9
            $this->extractSection();
76
            $this->saveCurrentSection();
77 9
78
        } while (!$this->stream->isEnd());
79 8
80
        return $this;
81
    }
82 8
83
    /**
84 8
     * @internal
85 7
     * @throws SyntaxErrorException
86
     * @throws \InvalidArgumentException
87
     * @throws \LogicException
88
     */
89 2
    private function extractSection()
90
    {
91
        $this->stream->ignoreWhitespace();
92
93
        $this->extractSectionType();
94 7
95
        switch ($this->currentSection['type']) {
96
            case 'source':
97 2
            case 'index':
98 2
                $this->stream->ignoreHorizontalSpace();
99
                $this->extractSectionName();
100
101
                $this->extractInheritance();
102
                break;
103
            case 'indexer':
104 9
            case 'searchd':
105
            case 'common':
106 9
                break;
107
            default:
108 9
                throw new SyntaxErrorException($this->stream);
109 9
        }
110 9
111 9
        $this->stream->ignoreWhitespace();
112 9
        
113 9
        $this->extractOptions();
114 8
        
115
        $this->stream->ignoreWhitespace();
116 1
    }
117
118
    /**
119
     * @internal
120
     * @throws SyntaxErrorException
121
     * @throws \InvalidArgumentException
122
     * @throws \LogicException
123
     */
124 8
    private function extractSectionType()
125
    {
126 8
        start:
127
        $char = $this->stream->currentAscii();
128
        if ($char->isLetter()) {
129 8
            $this->currentSection['type'] .= $this->stream->current();
130 8
            $this->stream->next();
131
            goto start;
132 8
        } elseif ($char->isWhiteSpace()) {
133 8
            return;
134 8
        } else {
135 8
            throw new SyntaxErrorException($this->stream);
136 8
        }
137 1
    }
138 1
139
    /**
140 1
     * @internal
141
     * @throws SyntaxErrorException
142
     * @throws \InvalidArgumentException
143
     * @throws \LogicException
144
     */
145 View Code Duplication
    private function extractSectionName()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
    {
147
        start:
148
        $char = $this->stream->currentAscii();
149
        
150 8
        if ($char->isLetter() || $char->isDigit() || $char->is(AsciiChar::UNDERSCORE)) {
151
            $this->currentSection['name'] .= $this->stream->current();
152 8
            $this->stream->next();
153
            goto start;
154 8
        } elseif ($char->isWhiteSpace() || $char->is(AsciiChar::COLON)) {
155
            return;
156 8
        } else {
157 6
            throw new SyntaxErrorException($this->stream);
158
        }
159
    }
160 4
161 3
    /**
162 3
     * @internal
163
     * @throws SyntaxErrorException
164 1
     * @throws \InvalidArgumentException
165
     * @throws \LogicException
166 3
     */
167
    private function extractInheritance()
168
    {
169
        $this->stream->ignoreHorizontalSpace();
170
        
171
        $char = $this->stream->currentAscii();
172 3
        
173
        if ($char->isVerticalSpace() || $char->is(AsciiChar::OPENING_BRACE())) {
174 3
            return;
175
        }
176 3
177 3
        if ($char->is(AsciiChar::COLON())) {
178
            $this->stream->next();
179 3
            $this->stream->ignoreHorizontalSpace();
180 3
            $this->extractInheritanceName();
181 3
        } else {
182 3
            throw new SyntaxErrorException($this->stream);
183 3
        }
184
    }
185
186
    /**
187
     * @internal
188
     * @throws SyntaxErrorException
189
     * @throws \InvalidArgumentException
190
     * @throws \LogicException
191
     */
192 View Code Duplication
    private function extractInheritanceName()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193 7
    {
194
        start:
195 7
        $char = $this->stream->currentAscii();
196
        
197 7
        if ($char->isLetter() || $char->isDigit() || $char->is(AsciiChar::UNDERSCORE)) {
198 6
            $this->currentSection['inheritance'] .= $this->stream->current();
199
            $this->stream->next();
200
            goto start;
201 6
        } elseif ($char->isWhiteSpace()) {
202
            return;
203 6
        } else {
204 1
            throw new SyntaxErrorException($this->stream);
205
        }
206
    }
207 6
208 2
    /**
209
     * @internal
210 2
     * @throws SyntaxErrorException
211
     * @throws \LogicException
212 6
     * @throws \InvalidArgumentException
213 5
     */
214
    private function extractOptions()
215 1
    {
216
        if ($this->stream->currentAscii()->is(AsciiChar::OPENING_BRACE)) {
217
            $this->stream->next();
218
219
            start:
220
            $this->stream->ignoreWhitespace();
221
222
            if($this->stream->isEnd()) {
223 6
                throw new SyntaxErrorException($this->stream);
224
            }
225 6
226 6
            if ($this->stream->currentAscii()->is(AsciiChar::CLOSING_BRACE)) {
227 5
                $this->stream->next();
228 5
229
                return;
230
            }
231
            $this->extractOption();
232
            goto start;
233
        } else {
234 6
            throw new SyntaxErrorException($this->stream);
235
        }
236 6
    }
237
238
    /**
239 6
     * @internal
240 6
     * @throws SyntaxErrorException
241
     * @throws \InvalidArgumentException
242 6
     * @throws \LogicException
243 6
     */
244 6
    private function extractOption()
245 6
    {
246 6
        $this->extractOptionName();
247
        $this->stream->ignoreHorizontalSpace();
248 2
        
249
        if (!$this->stream->currentAscii()->is(AsciiChar::EQUALS)) {
250
            throw new SyntaxErrorException($this->stream);
251
        }
252
        
253
        $this->stream->next();
254
        $this->stream->ignoreHorizontalSpace();
255
        
256 6
        $this->extractOptionValue();
257
        $this->saveCurrentOption();
258 6
    }
259
260 6
    /**
261 6
     * @internal
262
     * @throws SyntaxErrorException
263 6
     * @throws \InvalidArgumentException
264 1
     * @throws \LogicException
265
     */
266 View Code Duplication
    private function extractOptionName()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267 5
    {
268
        start:
269
        $char = $this->stream->currentAscii();
270 5
271 5
        if ($char->isLetter() || $char->isDigit() || $char->is(AsciiChar::UNDERSCORE)) {
272
            $this->currentOption['name'] .= $this->stream->current();
273 5
            $this->stream->next();
274
            goto start;
275
        } elseif ($char->isHorizontalSpace()) {
276
            return;
277 5
        } else {
278
            throw new SyntaxErrorException($this->stream);
279 5
        }
280 5
    }
281
282 5
    /**
283 5
     * @internal
284 5
     * @throws SyntaxErrorException
285 5
     * @throws \LogicException
286 5
     * @throws \InvalidArgumentException
287
     */
288 1
    private function extractOptionValue()
289 1
    {
290
        start:
291
        $char = $this->stream->currentAscii();
292 5
        
293 5
        if($this->stream->isEnd()) {
294
            throw new SyntaxErrorException($this->stream);
295 5
        }
296 5
297
        if ($char->isPrintableChar() || $char->isHorizontalSpace()) {
298
299
            if ($char->is(AsciiChar::BACKSLASH)) { // if possibility of multi-line
300
                $this->stream->next();
301
302
                if ($this->stream->currentAscii()->isVerticalSpace()) { // multi-line opened
303
                    $this->currentOption['value'] .= chr(AsciiChar::BACKSLASH);
304
                    $this->currentOption['value'] .= $this->stream->current();
305 2
                    $this->stream->next();
306
                    goto start;
307 2
                } else { // backslash as mean symbol
308 2
                    $this->currentOption['value'] .= chr(AsciiChar::BACKSLASH);
309 2
                    goto start;
310 2
                }
311
            } else {
312
                $this->currentOption['value'] .= $this->stream->current();
313
                $this->stream->next();
314
                goto start;
315 5
            }
316
        } elseif ($char->isVerticalSpace()) {
317 5
            return;
318 5
        } else {
319 5
            throw new SyntaxErrorException($this->stream);
320
        }
321
    }
322
323
    /**
324
     * @internal
325 2
     */
326
    private function saveCurrentSection()
327
    {
328 2
        $this->currentSection = array_filter($this->currentSection);
329
        $this->tokens[]       = $this->currentSection;
330
        $this->currentSection = $this->getEmptySectionData();
331
    }
332
333
    /**
334
     * @internal
335
     */
336
    private function saveCurrentOption()
337
    {
338
        $this->currentSection['options'][] = $this->currentOption;
339 5
        $this->currentOption               = $this->getEmptyOptionData();
340
    }
341
342 5
    /**
343
     * @internal
344
     * @return array
345
     */
346
    private function getEmptySectionData() : array
347
    {
348
        return [
349
            'type'        => '',
350
            'name'        => '',
351
            'inheritance' => '',
352
            'options'     => []
353
        ];
354
    }
355
356
    /**
357
     * @internal
358
     * @return array
359
     */
360
    private function getEmptyOptionData() : array
361
    {
362
        return [
363
            'name'  => '',
364
            'value' => ''
365
        ];
366
    }
367
368
    /**
369
     * @var StringStream
370
     */
371
    private $stream;
372
373
    /**
374
     * Result of tokenize input string
375
     * @var array
376
     */
377
    private $tokens = [];
378
379
    /**
380
     * temporary storage of tokens for one section
381
     * @var array
382
     */
383
    private $currentSection = [
384
        'type'        => '',
385
        'name'        => '',
386
        'inheritance' => '',
387
        'options'     => []
388
    ];
389
    /**
390
     * temporary storage of tokens for one option
391
     * @var array
392
     */
393
    private $currentOption = [
394
        'name'  => '',
395
        'value' => ''
396
    ];
397
}