Completed
Push — master ( d70775...825d10 )
by Kacper
02:48
created

GreedyLanguage   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 182
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 23
c 1
b 0
f 0
lcom 1
cbo 10
dl 0
loc 182
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
setupRules() 0 1 ?
A parse() 0 11 3
A _process() 0 12 3
A tokenize() 0 6 1
A _tokens() 0 14 3
B _rules() 0 18 6
getIdentifier() 0 1 ?
A getEnds() 0 12 1
A getEmbedded() 0 4 1
A embed() 0 4 1
A __get() 0 4 2
A __set() 0 4 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);
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) {
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