Completed
Branch 0.8-dev (8dbfb7)
by Kacper
06:35
created

GreedyLanguage::_rules()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 6
eloc 9
nc 12
nop 1
1
<?php
2
/**
3
 * Highlighter
4
 *
5
 * Copyright (C) 2016, 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\Context;
20
use Kadet\Highlighter\Parser\Result;
21
use Kadet\Highlighter\Parser\Rule;
22
use Kadet\Highlighter\Parser\Rules;
23
use Kadet\Highlighter\Parser\Token\LanguageToken;
24
use Kadet\Highlighter\Parser\TokenFactory;
25
use Kadet\Highlighter\Parser\TokenIterator;
26
use Kadet\Highlighter\Parser\Tokens;
27
use Kadet\Highlighter\Parser\UnprocessedTokens;
28
use Kadet\Highlighter\Parser\Validator\Validator;
29
30
/**
31
 * Greedy Language
32
 *
33
 * Implements greedy syntax highlighting.
34
 *
35
 * @package Kadet\Highlighter\Language
36
 */
37
abstract class GreedyLanguage implements Language
38
{
39
40
    /**
41
     * @var array
42
     */
43
    protected $_options = [];
44
45
    /**
46
     * Tokenizer rules
47
     *
48
     * @var Rules
49
     */
50
    public $rules;
51
    
52
    /**
53
     * Language constructor.
54
     *
55
     * @param array $options
56
     */
57
    public function __construct(array $options = [])
58
    {
59
60
        $this->_options = array_merge([
61
            'embedded' => []
62
        ], $this->_options, $options);
63
64
        $this->rules = new Rules($this);
65
        $this->setupRules();
66
    }
67
68
    /**
69
     * Tokenization rules setup
70
     */
71
    abstract public function setupRules();
72
73
    /**
74
     * Parses source and removes wrong tokens.
75
     *
76
     * @param TokenIterator|string $tokens
77
     *
78
     * @param array                $additional
79
     * @param bool                 $embedded
80
     *
81
     * @return Tokens
82
     */
83
    public function parse($tokens = null, $additional = [], $embedded = false)
84
    {
85
        if (is_string($tokens)) {
86
            $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...
87
        } elseif (!$tokens instanceof TokenIterator) {
88
            // Todo: Own Exceptions
89
            throw new \InvalidArgumentException('$tokens must be string or TokenIterator');
90
        }
91
92
        return $this->_process($tokens);
93
    }
94
95
    private function _process(TokenIterator $tokens) {
96
        $context  = new Context($this);
97
        $result   = new Result($tokens->getSource(), $tokens->current());
98
99
        for ($tokens->next(); $tokens->valid(); $tokens->next()) {
100
            if(!$tokens->current()->process($context, $this, $result, $tokens)) {
101
                break;
102
            }
103
        }
104
105
        return $result;
106
    }
107
108
    public function tokenize($source, $additional = [], $offset = 0, $embedded = false)
109
    {
110
        return new TokenIterator(
111
            $this->_tokens($source, $offset, $additional, $embedded)->sort()->toArray(), $source
112
        );
113
    }
114
115
    /**
116
     * Tokenize source
117
     *
118
     * @param                    $source
119
     *
120
     * @param int                $offset
121
     * @param array|\Traversable $additional
122
     *
123
     * @param bool               $embedded
124
     *
125
     * @return UnprocessedTokens
126
     */
127
    private function _tokens($source, $offset = 0, $additional = [], $embedded = false)
128
    {
129
        $result = new UnprocessedTokens();
130
131
        /** @var Language $language */
132
        foreach ($this->_rules($embedded) as $rule) {
133
            $rule->factory->setOffset($offset);
134
            foreach ($rule->match($source) as $token) {
135
                $result->add($token);
136
            }
137
        }
138
139
        return $result->batch($additional);
140
    }
141
142
    /**
143
     * @param bool $embedded
144
     *
145
     * @return Rule[]
146
     */
147
    private function _rules($embedded = false)
148
    {
149
        $rules = clone $this->rules;
150
        if(is_bool($embedded)) {
151
            $rules->addMany(['language.'.$this->getIdentifier() => $this->getEnds($embedded)]);
152
        }
153
154
        foreach ($rules->all() as $rule) {
155
            yield $rule;
156
        }
157
158
        // todo: interface
159
        foreach ($this->getEmbedded() as $language) {
160
            foreach ($language instanceof GreedyLanguage ? $language->_rules(true) : $language->getEnds(true) as $rule) {
1 ignored issue
show
Bug introduced by
The expression $language instanceof \Ka...language->getEnds(true) of type object<Kadet\Highlighter...ule>>|object<Generator> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
161
                yield $rule;
162
            }
163
        }
164
    }
165
166
    /**
167
     * Unique language identifier, for example 'php'
168
     *
169
     * @return string
170
     */
171
    abstract public function getIdentifier();
172
173
    /**
174
     * Language range Rule(s)
175
     *
176
     * @param $embedded
177
     *
178
     * @return Rule|\Kadet\Highlighter\Parser\Rule[]
179
     */
180
    public function getEnds($embedded = false)
181
    {
182
        return new Rule(
183
            new WholeMatcher(), [
184
                'priority' => 10000,
185
                'factory'  => new TokenFactory(LanguageToken::class),
186
                'inject'   => $this,
187
                'language' => null,
188
                'context'  => Validator::everywhere(),
189
            ]
190
        );
191
    }
192
193
    /**
194
     * @return Language[]
195
     */
196
    public function getEmbedded()
197
    {
198
        return $this->_options['embedded'];
199
    }
200
201
    /**
202
     * @param Language $lang
203
     */
204
    public function embed(Language $lang)
205
    {
206
        $this->_options['embedded'][] = $lang;
207
    }
208
209
    public function __get($name)
210
    {
211
        return isset($this->_options[$name]) ? $this->_options[$name] : null;
212
    }
213
214
    public function __set($name, $value)
215
    {
216
        $this->_options[$name] = $value;
217
    }
218
}
219