Passed
Push — master ( 6c305a...f9f5fe )
by Kacper
02:57
created

Language::_handleToken()   C

Complexity

Conditions 10
Paths 12

Size

Total Lines 55
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 10

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 55
ccs 38
cts 38
cp 1
rs 6.8372
cc 10
eloc 34
nc 12
nop 1
crap 10

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\Result;
22
use Kadet\Highlighter\Parser\Rule;
23
use Kadet\Highlighter\Parser\Token;
24
use Kadet\Highlighter\Parser\TokenFactory;
25
use Kadet\Highlighter\Parser\TokenIterator;
26
use Kadet\Highlighter\Parser\UnprocessedTokens;
27
use Kadet\Highlighter\Utils\ArrayHelper;
28
29
/**
30
 * Class Language
31
 *
32
 * @package Kadet\Highlighter\Language
33
 */
34
abstract class Language
35
{
36
    /**
37
     * @var array
38
     */
39
    protected $_options = [];
40
41
    /**
42
     * Tokenizer rules
43
     *
44
     * @var Rule[]|Rule[][]
45
     */
46
    private $_rules;
47
48
    /**
49
     * @var array
50
     */
51
    private $_context;
52
53
    /**
54
     * @var Result
55
     */
56
    private $_result;
57
58
    /**
59
     * @var TokenIterator
60
     */
61
    private $_iterator;
62
63
    /**
64
     * @var LanguageToken
65
     */
66
    private $_start;
67
68
    /**
69
     * Language constructor.
70
     *
71
     * @param array $options
72
     */
73 22
    public function __construct(array $options = [])
74
    {
75 22
        $this->_options = array_merge([
76 22
            'embedded' => [],
77 22
        ], $this->_options, $options);
78
79 22
        $this->_rules = $this->getRules();
80 22
    }
81
82
    /**
83
     * Tokenization rules definition
84
     *
85
     * @return Rule[]|Rule[][]
86
     */
87
    abstract public function getRules();
88
89
    /**
90
     * Parses source and removes wrong tokens.
91
     *
92
     * @param TokenIterator|string $tokens
93
     *
94
     * @param array                $additional
95
     * @param bool                 $embedded
96
     *
97
     * @return TokenIterator
98
     */
99 12
    public function parse($tokens = null, $additional = [], $embedded = false)
100
    {
101 12
        if (is_string($tokens)) {
102 11
            $tokens = $this->tokenize($tokens, $additional, $embedded);
103 12
        } elseif (!$tokens instanceof TokenIterator) {
104
            // Todo: Own Exceptions
105 1
            throw new \InvalidArgumentException('$tokens must be string or TokenIterator');
106
        }
107
108
        // Reset variables to default state
109 11
        $this->_start    = $tokens->current();
110 11
        $this->_context  = [];
111 11
        $this->_result   = new Result($tokens->getSource(), [
112 11
            $this->_start
113 11
        ]);
114 11
        $this->_iterator = $tokens;
115
116
        /** @var Token $token */
117 11
        for ($tokens->next(); $tokens->valid(); $tokens->next()) {
118 11
            $token = $tokens->current();
119
120 12
            if ($token->isValid($this, $this->_context)) {
121 11
                if($this->_handleToken($token) === false) {
122 11
                    break;
123
                };
124 10
            }
125 10
        }
126
127 11
        return $this->_result;
128
    }
129
130 12
    public function tokenize($source, $additional = [], $offset = 0, $embedded = false)
131
    {
132 12
        return new TokenIterator(
133 12
            $this->_tokens($source, $offset, $additional, $embedded)->sort()->toArray(), $source
134 12
        );
135
    }
136
137
    /**
138
     * Tokenize source
139
     *
140
     * @param       $source
141
     *
142
     * @param int   $offset
143
     * @param array $additional
144
     *
145
     * @param bool  $embedded
146
     *
147
     * @return UnprocessedTokens
148
     */
149 12
    private function _tokens($source, $offset = 0, $additional = [], $embedded = false)
150
    {
151 12
        $result = new UnprocessedTokens();
152
153
        /** @var Language $language */
154 12
        foreach ($this->_rules($embedded) as $rule) {
155 12
            $rule->factory->setOffset($offset);
156 12
            foreach ($rule->match($source) as $token) {
157 12
                $result->add($token);
158 12
            }
159 12
        }
160
161 12
        return $result->batch($additional);
162
    }
163
164
    /**
165
     * @param bool $embedded
166
     *
167
     * @return Rule[]
168
     */
169 12
    private function _rules($embedded = false)
170
    {
171 12
        $all = $this->_rules;
172 12
        if (!$embedded) {
173 12
            $all['language.' . $this->getIdentifier()] = $this->getOpenClose();
174 12
        }
175
176
        // why this code sucks so much? Because RecursiveIterator performance such a lot more.
177 12
        foreach ($all as $name => $rules) {
178 12
            if (!is_array($rules)) {
179 12
                $rules = [$rules];
180 12
            }
181
182
            /** @var Rule $rule */
183 12
            foreach ($rules as $rule) {
184 12
                if ($rule->language === false) {
185 11
                    $rule->language = $this;
186 11
                }
187
188 12
                $rule->factory->setBase($name);
189
190 12
                yield $rule;
191 12
            }
192 12
        }
193
194 12
        foreach ($this->getEmbedded() as $language) {
195 1
            foreach ($language->_rules() as $rule) {
196 1
                yield $rule;
197 1
            }
198 12
        }
199 12
    }
200
201
    /**
202
     * Unique language identifier, for example 'php'
203
     *
204
     * @return string
205
     */
206
    abstract public function getIdentifier();
207
208
    /**
209
     * Language range Rule(s)
210
     *
211
     * @return Rule|Rule[]
212
     */
213 16
    public function getOpenClose()
214
    {
215 16
        return new Rule(
216 16
            new WholeMatcher(), [
217 16
                'priority' => 1000,
218 16
                'factory'  => new TokenFactory(LanguageToken::class),
219 16
                'inject'   => $this,
220 16
                'language' => null,
221 16
                'context'  => Rule::everywhere(),
222
            ]
223 16
        );
224
    }
225
226
    /**
227
     * @return Language[]
228
     */
229 13
    public function getEmbedded()
230
    {
231 13
        return $this->_options['embedded'];
232
    }
233
234
    /**
235
     * @param Language $lang
236
     */
237 1
    public function embed(Language $lang)
238
    {
239 1
        $this->_options['embedded'][] = $lang;
240 1
    }
241
242 1
    public function __get($name)
243
    {
244 1
        return isset($this->_options[$name]) ? $this->_options[$name] : null;
245
    }
246
247 1
    public function __set($name, $value)
248
    {
249 1
        $this->_options[$name] = $value;
250 1
    }
251
252 11
    protected function _handleToken(Token $token)
253
    {
254 11
        if ($token->isStart()) {
255 10
            if ($token instanceof LanguageToken) {
256 2
                $this->_result->merge($token->getInjected()->parse($this->_iterator));
257 2
            } else {
258 10
                $this->_result[] = $token;
259 10
                $this->_context[$this->_iterator->key()] = $token->name;
260
            }
261 10
        } else {
262 11
            $start = $token->getStart();
263
264
            /** @noinspection PhpUndefinedMethodInspection bug */
265 11
            if ($token instanceof LanguageToken && $token->getLanguage() === $this) {
266 11
                $this->_start->setEnd($token);
267
268 11
                if ($this->_start->postProcess) {
0 ignored issues
show
Documentation introduced by
The property postProcess does not exist on object<Kadet\Highlighter\Parser\LanguageToken>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
269 1
                    $source = substr($this->_iterator->getSource(), $this->_start->pos, $this->_start->getLength());
270
271 1
                    $tokens = $this->tokenize($source, $this->_result, $this->_start->pos, true);
0 ignored issues
show
Documentation introduced by
$this->_result is of type object<Kadet\Highlighter\Parser\Result>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
272 1
                    $this->_result = $this->parse($tokens);
273 1
                }
274
275
                # closing unclosed tokens
276 11
                foreach (array_reverse($this->_context) as $hash => $name) {
277 1
                    $end = new Token([$name, 'pos' => $token->pos]);
278 1
                    $this->_iterator[$hash]->setEnd($end);
279 1
                    $this->_result[] = $end;
280 11
                }
281
282 11
                $this->_result[] = $token;
283 11
                return false;
284
            } else {
285 9
                if ($start) {
286 8
                    unset($this->_context[spl_object_hash($start)]);
287 8
                } else {
288 1
                    $start = ArrayHelper::find(array_reverse($this->_context), function ($k, $v) use ($token) {
289 1
                        return $v === $token->name;
290 1
                    });
291
292 1
                    if ($start !== false) {
293 1
                        $token->setStart($this->_iterator[$start]);
294 1
                        unset($this->_context[$start]);
295 1
                        $start = $this->_iterator[$start];
296 1
                    }
297
                }
298
299 9
                if (!$start instanceof MetaToken) {
300 9
                    $this->_result[] = $token;
301 9
                }
302
            }
303
        }
304
305 10
        return true;
306
    }
307
}
308