Passed
Push — master ( 8efe9c...2a6b88 )
by Sergey
02:33
created

Tokenizer::extractOptionValue()   C

Complexity

Conditions 7
Paths 0

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7.0084
Metric Value
dl 0
loc 34
ccs 17
cts 18
cp 0.9444
rs 5.5
cc 7
eloc 24
nc 0
nop 0
crap 7.0084
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
        return (new self($plainData))->tokenizeInternal()->tokens;
34
    }
35
36
    /**
37
     * SphinxConfigurationParser constructor.
38
     * @internal
39 9
     * @param string $string
40
     * @throws BadMethodCallException
41 9
     * @throws \Hoa\Ustring\Exception
42 9
     */
43 9
    private function __construct(string $string)
44
    {
45
        $string       = $this->removeComments($string);
46
        $this->stream = new StringStream($string);
47
    }
48
49
    /**
50 9
     * @internal
51
     * @param string $string
52 9
     * @return string
53
     */
54
    private function removeComments(string $string) : string
55
    {
56
        return preg_replace("/(^#| #|	#).*\n/im", "\n", $string);
57
    }
58
59
    /**
60 9
     * @internal
61
     * @return Tokenizer
62
     * @throws \LogicException
63 9
     * @throws \InvalidArgumentException
64 2
     * @throws SyntaxErrorException
65
     */
66 2
    private function tokenizeInternal() : Tokenizer
67
    {
68 2
        do {
69
            $this->extractSection();
70
            $this->saveCurrentSection();
71
72
        } while (!$this->stream->isEnd());
73
74
        return $this;
75 9
    }
76
77 9
    /**
78
     * @internal
79 8
     * @throws SyntaxErrorException
80
     * @throws \InvalidArgumentException
81
     * @throws \LogicException
82 8
     */
83
    private function extractSection()
84 8
    {
85 7
        $this->stream->ignoreWhitespace();
86
87
        $this->extractSectionType();
88
89 2
        switch ($this->currentSection['type']) {
90
            case 'source':
91
            case 'index':
92
                $this->stream->ignoreHorizontalSpace();
93
                $this->extractSectionName();
94 7
95
                $this->extractInheritance();
96
                break;
97 2
            case 'indexer':
98 2
            case 'searchd':
99
            case 'common':
100
                break;
101
            default:
102
                throw new SyntaxErrorException($this->stream);
103
        }
104 9
105
        $this->stream->ignoreWhitespace();
106 9
        
107
        $this->extractOptions();
108 9
        
109 9
        $this->stream->ignoreWhitespace();
110 9
    }
111 9
112 9
    /**
113 9
     * @internal
114 8
     * @throws SyntaxErrorException
115
     * @throws \InvalidArgumentException
116 1
     * @throws \LogicException
117
     */
118
    private function extractSectionType()
119
    {
120
        start:
121
        $char = $this->stream->currentAscii();
122
        if ($char->isLetter()) {
123
            $this->currentSection['type'] .= $this->stream->current();
124 8
            $this->stream->next();
125
            goto start;
126 8
        } elseif ($char->isWhiteSpace()) {
127
            return;
128
        } else {
129 8
            throw new SyntaxErrorException($this->stream);
130 8
        }
131
    }
132 8
133 8
    /**
134 8
     * @internal
135 8
     * @throws SyntaxErrorException
136 8
     * @throws \InvalidArgumentException
137 1
     * @throws \LogicException
138 1
     */
139 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...
140 1
    {
141
        start:
142
        $char = $this->stream->currentAscii();
143
        
144
        if ($char->isLetter() || $char->isDigit() || $char->is(AsciiChar::UNDERSCORE)) {
145
            $this->currentSection['name'] .= $this->stream->current();
146
            $this->stream->next();
147
            goto start;
148
        } elseif ($char->isWhiteSpace() || $char->is(AsciiChar::COLON)) {
149
            return;
150 8
        } else {
151
            throw new SyntaxErrorException($this->stream);
152 8
        }
153
    }
154 8
155
    /**
156 8
     * @internal
157 6
     * @throws SyntaxErrorException
158
     * @throws \InvalidArgumentException
159
     * @throws \LogicException
160 4
     */
161 3
    private function extractInheritance()
162 3
    {
163
        $this->stream->ignoreHorizontalSpace();
164 1
        
165
        $char = $this->stream->currentAscii();
166 3
        
167
        if ($char->isVerticalSpace() || $char->is(AsciiChar::OPENING_BRACE())) {
168
            return;
169
        }
170
171
        if ($char->is(AsciiChar::COLON())) {
172 3
            $this->stream->next();
173
            $this->stream->ignoreHorizontalSpace();
174 3
            $this->extractInheritanceName();
175
        } else {
176 3
            throw new SyntaxErrorException($this->stream);
177 3
        }
178
    }
179 3
180 3
    /**
181 3
     * @internal
182 3
     * @throws SyntaxErrorException
183 3
     * @throws \InvalidArgumentException
184
     * @throws \LogicException
185
     */
186 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...
187
    {
188
        start:
189
        $char = $this->stream->currentAscii();
190
        
191
        if ($char->isLetter() || $char->isDigit() || $char->is(AsciiChar::UNDERSCORE)) {
192
            $this->currentSection['inheritance'] .= $this->stream->current();
193 7
            $this->stream->next();
194
            goto start;
195 7
        } elseif ($char->isWhiteSpace()) {
196
            return;
197 7
        } else {
198 6
            throw new SyntaxErrorException($this->stream);
199
        }
200
    }
201 6
202
    /**
203 6
     * @internal
204 1
     * @throws SyntaxErrorException
205
     * @throws \LogicException
206
     * @throws \InvalidArgumentException
207 6
     */
208 2
    private function extractOptions()
209
    {
210 2
        if ($this->stream->currentAscii()->is(AsciiChar::OPENING_BRACE)) {
211
            $this->stream->next();
212 6
213 5
            start:
214
            $this->stream->ignoreWhitespace();
215 1
216
            if($this->stream->isEnd()) {
217
                throw new SyntaxErrorException($this->stream);
218
            }
219
220
            if ($this->stream->currentAscii()->is(AsciiChar::CLOSING_BRACE)) {
221
                $this->stream->next();
222
223 6
                return;
224
            }
225 6
            $this->extractOption();
226 6
            goto start;
227 5
        } else {
228 5
            throw new SyntaxErrorException($this->stream);
229
        }
230
    }
231
232
    /**
233
     * @internal
234 6
     * @throws SyntaxErrorException
235
     * @throws \InvalidArgumentException
236 6
     * @throws \LogicException
237
     */
238
    private function extractOption()
239 6
    {
240 6
        $this->extractOptionName();
241
        $this->stream->ignoreHorizontalSpace();
242 6
        
243 6
        if (!$this->stream->currentAscii()->is(AsciiChar::EQUALS)) {
244 6
            throw new SyntaxErrorException($this->stream);
245 6
        }
246 6
        
247
        $this->stream->next();
248 2
        $this->stream->ignoreHorizontalSpace();
249
        
250
        $this->extractOptionValue();
251
        $this->saveCurrentOption();
252
    }
253
254
    /**
255
     * @internal
256 6
     * @throws SyntaxErrorException
257
     * @throws \InvalidArgumentException
258 6
     * @throws \LogicException
259
     */
260 6 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...
261 6
    {
262
        start:
263 6
        $char = $this->stream->currentAscii();
264 1
265
        if ($char->isLetter() || $char->isDigit() || $char->is(AsciiChar::UNDERSCORE)) {
266
            $this->currentOption['name'] .= $this->stream->current();
267 5
            $this->stream->next();
268
            goto start;
269
        } elseif ($char->isHorizontalSpace()) {
270 5
            return;
271 5
        } else {
272
            throw new SyntaxErrorException($this->stream);
273 5
        }
274
    }
275
276
    /**
277 5
     * @internal
278
     * @throws SyntaxErrorException
279 5
     * @throws \LogicException
280 5
     * @throws \InvalidArgumentException
281
     */
282 5
    private function extractOptionValue()
283 5
    {
284 5
        start:
285 5
        $char = $this->stream->currentAscii();
286 5
        
287
        if($this->stream->isEnd()) {
288 1
            throw new SyntaxErrorException($this->stream);
289 1
        }
290
291
        if ($char->isPrintableChar() || $char->isHorizontalSpace()) {
292 5
293 5
            if ($char->is(AsciiChar::BACKSLASH)) { // if possibility of multi-line
294
                $this->stream->next();
295 5
296 5
                if ($this->stream->currentAscii()->isVerticalSpace()) { // multi-line opened
297
                    $this->currentOption['value'] .= chr(AsciiChar::BACKSLASH);
298
                    $this->currentOption['value'] .= $this->stream->current();
299
                    $this->stream->next();
300
                    goto start;
301
                } else { // backslash as mean symbol
302
                    $this->currentOption['value'] .= chr(AsciiChar::BACKSLASH);
303
                    goto start;
304
                }
305 2
            } else {
306
                $this->currentOption['value'] .= $this->stream->current();
307 2
                $this->stream->next();
308 2
                goto start;
309 2
            }
310 2
        } elseif ($char->isVerticalSpace()) {
311
            return;
312
        } else {
313
            throw new SyntaxErrorException($this->stream);
314
        }
315 5
    }
316
317 5
    /**
318 5
     * @internal
319 5
     */
320
    private function saveCurrentSection()
321
    {
322
        $this->currentSection = array_filter($this->currentSection);
323
        $this->tokens[]       = $this->currentSection;
324
        $this->currentSection = $this->getEmptySectionData();
325 2
    }
326
327
    /**
328 2
     * @internal
329
     */
330
    private function saveCurrentOption()
331
    {
332
        $this->currentSection['options'][] = $this->currentOption;
333
        $this->currentOption               = $this->getEmptyOptionData();
334
    }
335
336
    /**
337
     * @internal
338
     * @return array
339 5
     */
340
    private function getEmptySectionData() : array
341
    {
342 5
        return [
343
            'type'        => '',
344
            'name'        => '',
345
            'inheritance' => '',
346
            'options'     => []
347
        ];
348
    }
349
350
    /**
351
     * @internal
352
     * @return array
353
     */
354
    private function getEmptyOptionData() : array
355
    {
356
        return [
357
            'name'  => '',
358
            'value' => ''
359
        ];
360
    }
361
362
    /**
363
     * @var StringStream
364
     */
365
    private $stream;
366
367
    /**
368
     * Result of tokenize input string
369
     * @var array
370
     */
371
    private $tokens = [];
372
373
    /**
374
     * temporary storage of tokens for one section
375
     * @var array
376
     */
377
    private $currentSection = [
378
        'type'        => '',
379
        'name'        => '',
380
        'inheritance' => '',
381
        'options'     => []
382
    ];
383
    /**
384
     * temporary storage of tokens for one option
385
     * @var array
386
     */
387
    private $currentOption = [
388
        'name'  => '',
389
        'value' => ''
390
    ];
391
}