Passed
Push — master ( f247da...c8939d )
by Kacper
02:56
created

Language::parse()   D

Complexity

Conditions 15
Paths 27

Size

Total Lines 85
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 56
CRAP Score 15

Importance

Changes 20
Bugs 0 Features 1
Metric Value
c 20
b 0
f 1
dl 0
loc 85
ccs 56
cts 56
cp 1
rs 4.9121
cc 15
eloc 49
nc 27
nop 3
crap 15

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Highlighter
4
 *
5
 * Copyright (C) 2015, Some right reserved.
6
 *
7
 * @author Kacper "Kadet" Donat <[email protected]>
8
 *
9
 * Contact with author:
10
 * Xmpp: [email protected]
11
 * E-mail: [email protected]
12
 *
13
 * From Kadet with love.
14
 */
15
16
namespace Kadet\Highlighter\Language;
17
18
use Kadet\Highlighter\Matcher\WholeMatcher;
19
use Kadet\Highlighter\Parser\LanguageToken;
20
use Kadet\Highlighter\Parser\MetaToken;
21
use Kadet\Highlighter\Parser\Rule;
22
use Kadet\Highlighter\Parser\Token;
23
use Kadet\Highlighter\Parser\TokenFactory;
24
use Kadet\Highlighter\Parser\TokenIterator;
25
use Kadet\Highlighter\Parser\TokenList;
26
use Kadet\Highlighter\Utils\ArrayHelper;
27
28
/**
29
 * Class Language
30
 *
31
 * @package Kadet\Highlighter\Language
32
 */
33
abstract class Language
34
{
35
    /**
36
     * @var array
37
     */
38
    protected $_options = [];
39
    /**
40
     * Tokenizer rules
41
     *
42
     * @var Rule[]
43
     */
44
    private $_rules;
45
46
    /**
47
     * Language constructor.
48
     *
49
     * @param array $options
50
     */
51 22
    public function __construct(array $options = [])
52
    {
53 22
        $this->_options = array_merge(
54
            [
55 22
                'embedded' => [],
56 22
            ], $this->_options, $options
57 22
        );
58
59 22
        $this->_rules = $this->getRules();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getRules() of type array<integer,object<Kad...hlighter\Parser\Rule>>> is incompatible with the declared type array<integer,object<Kad...ghlighter\Parser\Rule>> of property $_rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
60 22
    }
61
62
    /**
63
     * Tokenization rules definition
64
     *
65
     * @return Rule[]|Rule[][]
66
     */
67
    abstract public function getRules();
68
69
    /**
70
     * Parses source and removes wrong tokens.
71
     *
72
     * @param TokenIterator|string $tokens
73
     *
74
     * @param array                $additional
75
     * @param bool                 $embedded
76
     *
77
     * @return TokenIterator
78
     */
79 12
    public function parse($tokens = null, $additional = [], $embedded = false)
80
    {
81 12
        if (is_string($tokens)) {
82 11
            $tokens = $this->tokenize($tokens, $additional, $embedded);
83 12
        } elseif (!$tokens instanceof TokenIterator) {
84
            // Todo: Own Exceptions
85 1
            throw new \InvalidArgumentException('$tokens must be string or TokenIterator');
86
        }
87
88 11
        $start   = $tokens->current();
89 11
        $context = [];
90
91
        /** @var Token[] $result */
92 11
        $result = [$start];
93
94
        /** @var Token $token */
95 11
        for ($tokens->next(); $tokens->valid(); $tokens->next()) {
96 11
            $token = $tokens->current();
97
98 11
            if (!$token->isValid($this, $context)) {
99 3
                continue;
100
            }
101
102 11
            if ($token->isStart()) {
103 10
                if ($token instanceof LanguageToken) {
104
                    /** @var LanguageToken $token */
105 2
                    $result = array_merge(
106 2
                        $result,
107 2
                        $token->getInjected()->parse($tokens)->getTokens()
108 2
                    );
109 2
                } else {
110 10
                    if (!$token instanceof MetaToken) {
111 10
                        $result[] = $token;
112 10
                    }
113 10
                    $context[$tokens->key()] = $token->name;
114
                }
115 10
            } else {
116 11
                $start = $token->getStart();
117
118
                /** @noinspection PhpUndefinedMethodInspection bug */
119 11
                if ($token instanceof LanguageToken && $token->getLanguage() === $this) {
120 12
                    $result[0]->setEnd($token);
121
122 11
                    if ($result[0]->postProcess) {
123 1
                        $source = substr($tokens->getSource(), $result[0]->pos, $result[0]->getLength());
124
125 1
                        $tokens = $this->tokenize($source, $result, $result[0]->pos, true);
126 1
                        $result = $this->parse($tokens)->getTokens();
127 1
                    }
128
129
                    # closing unclosed tokens
130 11
                    foreach (array_reverse($context) as $hash => $name) {
131 1
                        $end = new Token([$name, 'pos' => $token->pos]);
132 1
                        $tokens[$hash]->setEnd($end);
133 1
                        $result[] = $end;
134 11
                    }
135
136 11
                    $result[] = $token;
137 11
                    break;
138
                } else {
139 9
                    if ($start) {
140 8
                        unset($context[spl_object_hash($start)]);
141 8
                    } else {
142
                        /** @noinspection PhpUnusedParameterInspection */
143 1
                        $start = ArrayHelper::find(
144 1
                            array_reverse($context), function ($k, $v) use ($token) {
145 1
                            return $v === $token->name;
146 1
                        });
147
148 1
                        if ($start !== false) {
149 1
                            $token->setStart($tokens[$start]);
150 1
                            unset($context[$start]);
151 1
                            $start = $tokens[$start];
152 1
                        }
153
                    }
154
155 9
                    if (!$start instanceof MetaToken) {
156 9
                        $result[] = $token;
157 9
                    }
158
                }
159
            }
160 10
        }
161
162 11
        return new TokenIterator($result, $tokens->getSource());
163
    }
164
165 12
    public function tokenize($source, $additional = [], $offset = 0, $embedded = false)
166
    {
167 12
        $iterator = new TokenIterator(
168 12
            $this->_tokens($source, $offset, $additional, $embedded)->sort()->toArray(), $source
169 12
        );
170
171 12
        return $iterator;
172
    }
173
174
    /**
175
     * Tokenize source
176
     *
177
     * @param       $source
178
     *
179
     * @param int   $offset
180
     * @param array $additional
181
     *
182
     * @param bool  $embedded
183
     *
184
     * @return TokenList
185
     */
186 12
    private function _tokens($source, $offset = 0, $additional = [], $embedded = false)
187
    {
188 12
        $result = new TokenList();
189
190
        /** @var Language $language */
191 12
        foreach ($this->_rules($embedded) as $rule) {
192 12
            $rule->factory->setOffset($offset);
193 12
            foreach ($rule->match($source) as $token) {
194 12
                $result->add($token);
195 12
            }
196 12
        }
197
198 12
        return $result->batch($additional);
199
    }
200
201
    /**
202
     * @param bool $embedded
203
     *
204
     * @return Rule[]
205
     */
206 12
    private function _rules($embedded = false)
207
    {
208 12
        $all = $this->_rules;
209 12
        if (!$embedded) {
210 12
            $all['language.' . $this->getIdentifier()] = $this->getOpenClose();
211 12
        }
212
213
        // why this code sucks so much? Because RecursiveIterator performance such a lot more.
214 12
        foreach ($all as $name => $rules) {
215 12
            if (!is_array($rules)) {
216 12
                $rules = [$rules];
217 12
            }
218
219
            /** @var Rule $rule */
220 12
            foreach ($rules as $rule) {
221 12
                if ($rule->language === false) {
222 11
                    $rule->language = $this;
223 11
                }
224
225 12
                $rule->factory->setBase($name);
226
227 12
                yield $rule;
228 12
            }
229 12
        }
230
231 12
        foreach ($this->getEmbedded() as $language) {
232 1
            foreach ($language->_rules() as $rule) {
233 1
                yield $rule;
234 1
            }
235 12
        }
236 12
    }
237
238
    /**
239
     * Unique language identifier, for example 'php'
240
     *
241
     * @return string
242
     */
243
    abstract public function getIdentifier();
244
245
    /**
246
     * Language range Rule(s)
247
     *
248
     * @return Rule|Rule[]
249
     */
250 16
    public function getOpenClose()
251
    {
252 16
        return new Rule(
253 16
            new WholeMatcher(), [
254 16
                'priority' => 1000,
255 16
                'factory'  => new TokenFactory(LanguageToken::class),
256 16
                'inject'   => $this,
257 16
                'language' => null,
258 16
                'context'  => Rule::everywhere(),
259
            ]
260 16
        );
261
    }
262
263
    /**
264
     * @return Language[]
265
     */
266 13
    public function getEmbedded()
267
    {
268 13
        return $this->_options['embedded'];
269
    }
270
271
    /**
272
     * @param Language $lang
273
     */
274 1
    public function embed(Language $lang)
275
    {
276 1
        $this->_options['embedded'][] = $lang;
277 1
    }
278
279 1
    public function __get($name)
280
    {
281 1
        return isset($this->_options[$name]) ? $this->_options[$name] : null;
282
    }
283
284 1
    public function __set($name, $value)
285
    {
286 1
        $this->_options[$name] = $value;
287 1
    }
288
}
289