Passed
Push — master ( b31131...79f37b )
by Kacper
04:02
created

Language   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 94.29%

Importance

Changes 29
Bugs 1 Features 1
Metric Value
wmc 28
c 29
b 1
f 1
lcom 1
cbo 7
dl 0
loc 226
rs 10
ccs 99
cts 105
cp 0.9429

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
getRules() 0 1 ?
C parse() 0 81 13
C _tokens() 0 32 7
A tokenize() 0 7 1
getIdentifier() 0 1 ?
A getOpenClose() 0 12 1
A getEmbedded() 0 3 1
A embed() 0 3 1
A __get() 0 3 2
A __set() 0 3 1
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\Rule;
21
use Kadet\Highlighter\Parser\Token;
22
use Kadet\Highlighter\Parser\TokenFactory;
23
use Kadet\Highlighter\Parser\TokenIterator;
24
use Kadet\Highlighter\Utils\ArrayHelper;
25
26
/**
27
 * Class Language
28
 *
29
 * @package Kadet\Highlighter\Language
30
 */
31
abstract class Language
32
{
33
    /**
34
     * Tokenizer rules
35
     *
36
     * @var Rule[]
37
     */
38
    private $_rules;
39
40
    /**
41
     * @var array
42
     */
43
    protected $_options = [];
44
45
    /**
46
     * Language constructor.
47
     *
48
     * @param array $options
49
     */
50 19
    public function __construct(array $options = []) {
51 19
        $this->_options  = array_merge([
52 19
            'embedded' => [],
53 19
        ], $this->_options, $options);
54
55 19
        $this->_rules    = $this->getRules();
56 19
    }
57
58
    /**
59
     * Tokenization rules definition
60
     *
61
     * @return array
62
     */
63
    public abstract function getRules();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
64
65
    /**
66
     * Parses source and removes wrong tokens.
67
     *
68
     * @param TokenIterator|string $tokens
69
     *
70
     * @param array                $additional
71
     * @param bool                 $embedded
72
     *
73
     * @return TokenIterator
74
     */
75 12
    public function parse($tokens = null, $additional = [], $embedded = false)
76
    {
77 11
        if (is_string($tokens)) {
78 11
            $tokens = $this->tokenize($tokens, $additional, $embedded);
1 ignored issue
show
Documentation introduced by
$embedded is of type boolean, but the function expects a integer.

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...
79 11
        } elseif(!$tokens instanceof TokenIterator) {
80
            // Todo: Own Exceptions
81
            throw new \InvalidArgumentException('$tokens must be string or TokenIterator');
82
        }
83
84 11
        $start = $tokens->current();
85
86
87 11
        $context = [];
88
89 11
        /** @var Token[] $result */ $result = [$start];
90 11
        /** @var Token[] $all */    $all    = [];
91
92
        /** @var Token $token */
93 11
        for($tokens->next(); $tokens->valid(); $tokens->next()) {
94 11
            $token = $tokens->current();
95
96 11
            if (!$token->isValid($this, $context)) {
97 3
                continue;
98
            }
99
100 11
            if ($token->isStart()) {
101 10
                if ($token instanceof LanguageToken) {
102
                    /** @var LanguageToken $token */
103 2
                    $result = array_merge(
104 2
                        $result,
105 2
                        $token->getInjected()->parse($tokens)->getTokens()
106 2
                    );
107 2
                } else {
108 10
                    $all[spl_object_hash($token)] = $result[] = $token;
109 10
                    $context[spl_object_hash($token)] = $token->name;
110
                }
111 10
            } else {
112 11
                $start = $token->getStart();
113
114
                /** @noinspection PhpUndefinedMethodInspection bug */
115 11
                if ($token instanceof LanguageToken && $token->getLanguage() === $this) {
116 11
                    $result[0]->setEnd($token);
117
118 11
                    if($result[0]->getRule()->postProcess) {
119 12
                        $source = substr($tokens->getSource(), $result[0]->pos, $result[0]->getLength());
120
121 1
                        $tokens = $this->tokenize($source, $result, $result[0]->pos, true);
122 1
                        $result = $this->parse($tokens)->getTokens();
123 1
                    }
124
125
                    # closing unclosed tokens
126 11
                    foreach(array_reverse($context) as $hash => $name) {
127 1
                        $end = new Token([$name, 'pos' => $token->pos]);
128 1
                        $all[$hash]->setEnd($end);
129 1
                        $result[] = $end;
130 11
                    }
131
132 11
                    $result[] = $token;
133 11
                    break;
134
                } else {
135 9
                    if ($start !== null) {
136 8
                        unset($context[spl_object_hash($start)]);
137 8
                    } else {
138
                        /** @noinspection PhpUnusedParameterInspection */
139 1
                        $start = ArrayHelper::find(array_reverse($context), function ($k, $v) use ($token) {
140 1
                            return $v === $token->name;
141 1
                        });
142
143 1
                        if ($start !== false) {
144 1
                            $token->setStart($all[$start]);
145 1
                            unset($context[$start]);
146 1
                        }
147
                    }
148
149 9
                    $result[] = $token;
150
                }
151
            }
152 10
        }
153
154 11
        return new TokenIterator($result, $tokens->getSource());
155
    }
156
157
    /**
158
     * Tokenize source
159
     *
160
     * @param       $source
161
     *
162
     * @param int   $offset
163
     * @param array $additional
164
     *
165
     * @param bool  $embedded
166
     *
167
     * @return array
168
     */
169 12
    private function _tokens($source, $offset = 0, $additional = [], $embedded = false)
170
    {
171 12
        $all = $this->_rules;
172 12
        if(!$embedded) {
173 12
            $all['language.' . $this->getIdentifier()] = $this->getOpenClose();
174 12
        }
175
176 12
        $result = [];
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 12
                $rule->factory->setOffset($offset);
190
191 12
                $result = array_merge($result, $rule->match($source));
192 12
            }
193 12
        }
194
195 12
        foreach($this->getEmbedded() as $language) {
196 1
            $result = array_merge($result, $language->_tokens($source, $offset));
197 12
        }
198
199 12
        return array_merge($result, $additional);
200
    }
201
202 12
    public function tokenize($source, $additional = [], $offset = 0, $embedded = false)
203
    {
204 12
        $iterator = new TokenIterator($this->_tokens($source, $offset, $additional, $embedded), $source);
205 12
        $iterator->sort();
206 12
        $iterator->rewind();
207 12
        return $iterator;
208
    }
209
210
    /**
211
     * Unique language identifier, for example 'php'
212
     *
213
     * @return string
214
     */
215
    public abstract function getIdentifier();
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
216
217
    /**
218
     * Language range Rule(s)
219
     *
220
     * @return Rule|Rule[]
221
     */
222 14
    public function getOpenClose()
223
    {
224 14
        return new Rule(
225 14
            new WholeMatcher(), [
226 14
                'priority' => 1000,
227 14
                'factory'  => new TokenFactory('Kadet\\Highlighter\\Parser\\LanguageToken'),
228 14
                'inject'   => $this,
229 14
                'language' => null,
230 14
                'context'  => ['!!']
231 14
            ]
232 14
        );
233
    }
234
235
    /**
236
     * @return Language[]
237
     */
238 13
    public function getEmbedded() {
239 13
        return $this->_options['embedded'];
240
    }
241
242
    /**
243
     * @param Language $lang
244
     */
245 1
    public function embed(Language $lang) {
246 1
        $this->_options['embedded'][] = $lang;
247 1
    }
248
249
    public function __get($name) {
250
        return isset($this->_options[$name]) ? $this->_options[$name] : null;
251
    }
252
253
    public function __set($name, $value) {
254
        $this->_options[$name] = $value;
255
    }
256
}
257