Passed
Branch master (c8b954)
by Бабичев
18:08 queued 01:15
created

Lexeme::loader()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 7
cts 7
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 4
nop 1
crap 4
1
<?php
2
3
namespace Bavix\Flow;
4
5
use Bavix\Helpers\Arr;
6
use Bavix\Lexer\Lexer;
7
use Bavix\SDK\FileLoader;
8
9
/**
10
 * Class Lexeme
11
 *
12
 * @package Bavix\Flow
13
 */
14
class Lexeme
15
{
16
17
    /**
18
     * @var array
19
     */
20
    protected $types;
21
22
    /**
23
     * @var array
24
     */
25
    protected $default = [
26
        'types' => [
27
            'variable'
28
        ]
29
    ];
30
31
    /**
32
     * @var array
33
     */
34
    protected $props = [];
35
36
    /**
37
     * @var array
38
     */
39
    protected $data = [];
40
41
    /**
42
     * @var array
43
     */
44
    protected $closed = [];
45
46
    /**
47
     * @var array
48
     */
49
    protected $items = [];
50
51
    /**
52
     * @var string[]
53
     */
54
    protected $folders = [];
55
56
    /**
57
     * @var Lexer
58
     */
59
    protected $lexer;
60
61
    /**
62
     * @var Flow
63
     */
64
    protected $flow;
65
66
    /**
67
     * Lexeme constructor.
68
     *
69
     * @param Flow $flow
70
     */
71 11
    public function __construct(Flow $flow)
72
    {
73 11
        $this->types = Property::get('types');
74 11
        $this->addFolder(\dirname(__DIR__, 2) . '/lexemes');
75 11
        $this->flow = $flow;
76 11
    }
77
78
    /**
79
     * @param string $path
80
     *
81
     * @return self
82
     */
83 11
    public function addFolder(string $path): self
84
    {
85 11
        Arr::unShift($this->folders, $path);
86
87 11
        return $this;
88
    }
89
90
    /**
91
     * @return Lexer
92
     */
93 11
    protected function lexer(): Lexer
94
    {
95 11
        if (!$this->lexer)
96
        {
97 11
            $this->lexer = new Lexer();
98
        }
99
100 11
        return $this->lexer;
101
    }
102
103
    /**
104
     * @param $file
105
     *
106
     * @return FileLoader\DataInterface|null
107
     */
108 11
    protected function loader($file)
109
    {
110 11
        foreach ($this->folders as $folder)
111
        {
112 11
            foreach (FileLoader::extensions() as $ext)
113
            {
114
                try
115
                {
116 11
                    return FileLoader::load($folder . '/' . $file . '.' . $ext);
117
                }
118 11
                catch (\Throwable $throwable)
119 11
                {
120
                    // skip...
121
                }
122
            }
123
        }
124
125 7
        return null;
126
    }
127
128
    /**
129
     * @param array  $props
130
     * @param string $key
131
     *
132
     * @return array
133
     */
134 10
    public function property(array $props, string $key): array
135
    {
136 10
        $self = $this;
137 10
        $prop = &$props[$key];
138
139 10
        if (empty($this->props[$key]))
140
        {
141 10
            if (isset($prop['extends']))
142
            {
143
                $extends = Arr::map((array)$prop['extends'], function ($extend) use ($self, &$props) {
144
                    return $self->property($props, $extend);
145
                });
146
147
                $prop = \array_merge_recursive(
148
                    $prop,
149
                    ...$extends
150
                );
151
            }
152
153 10
            $this->props[$key] = $prop;
154
        }
155
156 10
        return $this->props[$key];
157
    }
158
159
    /**
160
     * @param array $props
161
     *
162
     * @return array
163
     */
164 11
    protected function properties(array $props): array
165
    {
166 11
        foreach ($props as $key => $prop)
167
        {
168 10
            $this->property($props, $key);
169
        }
170
171 11
        return $this->props;
172
    }
173
174
    /**
175
     * @param array $types
176
     *
177
     * @return array
178
     */
179 11
    protected function types(array $types): array
180
    {
181 11
        $results = [];
182 11
        foreach ($types as $type)
183
        {
184 11
            $results[] = $this->types[$type];
185
        }
186
187 11
        return $results;
188
    }
189
190
    /**
191
     * @param string $key
192
     *
193
     * @return string
194
     */
195 11
    protected function fragment(string $key): string
196
    {
197 11
        $property = $this->props[$key] ?? $this->default;
198
199 11
        return '(?<' . $key . '>(' .
200 11
            implode('|', $this->types($property['types'])) .
201 11
            '))';
202
    }
203
204
    /**
205
     * @param string $key
206
     * @param array  $syntax
207
     *
208
     * @return array
209
     */
210 11
    public function syntax2Array(string $key, array $syntax): array
211
    {
212 11
        $props  = $this->props[$key] ?? null;
213 11
        $closed = $this->closed[$key] ?? false;
214
215 11
        return Cache::get(__CLASS__ . $key, function () use ($syntax, $props, $closed) {
216
            return [
217 11
                'syntax' => $syntax,
218 11
                'props'  => $props,
219 11
                'closed' => $closed,
220
            ];
221 11
        });
222
    }
223
224 11
    protected function tryLoad(string $key)
225
    {
226 11
        if (empty($this->data[$key]))
227
        {
228 11
            $item = Cache::getItem(__CLASS__ . $key);
229
230 11
            if ($item && $item->isHit())
231
            {
232
                $_cache = $item->get();
233
234
                $this->data[$key]   = $_cache['syntax'];
235
                $this->props[$key]  = $_cache['props'];
236
                $this->closed[$key] = $_cache['closed'];
237
238
                return $this->data[$key];
239
            }
240
        }
241
242 11
        return null;
243
    }
244
245
    /**
246
     * @param string $key
247
     * @param array  $data
248
     *
249
     * @return array|mixed
250
     */
251 11
    protected function getLexemes(string $key, array $data)
252
    {
253 11
        $syntax = $this->tryLoad($key);
254
255 11
        if (empty($this->data[$key]))
256
        {
257 11
            $syntax = $this->syntax($key, $data);
258 11
            $this->syntax2Array($key, $syntax);
259
        }
260
261 11
        return $syntax;
262
    }
263
264
    /**
265
     * @param string $key
266
     * @param array  $data
267
     *
268
     * @return array
269
     */
270 11
    protected function syntax(string $key, array $data)
271
    {
272 11
        foreach ($data['directives'] ?? [] as $_key => $directive)
273
        {
274 6
            $this->data($_key, $directive ?: []);
275
        }
276
277 11
        $this->closed[$key] = $data['closed'] ?? false;
278 11
        $this->properties($data['properties'] ?? []);
279
280 11
        $syntax = [];
281
282 11
        foreach ($data['syntax'] ?? [] as $text)
283
        {
284 11
            $tokens = $this->lexer()->tokens($text);
285 11
            $vars   = $tokens[Lexer::PRINTER] ?? [];
286 11
            $code   = \str_replace(
287 11
                ['\\(', '\\)', ','],
288 11
                ['\\( ', ' \\)', ' ,'],
289 11
                $text
290
            );
291
292 11
            foreach ($vars as $var)
293
            {
294 11
                $code = \str_replace(
295 11
                    $var['code'],
296 11
                    $this->fragment($var['fragment']),
297 11
                    $code
298
                );
299
            }
300
301 11
            $syntax[] = [
302 11
                'vars'   => $vars,
303 11
                'regexp' => '~^' . $key . ' ' . $code . '$~ui'
304
            ];
305
        }
306
307 11
        return $syntax;
308
    }
309
310
    /**
311
     * @param string     $key
312
     * @param array|null $data
313
     *
314
     * @return array|bool|mixed
315
     */
316 11
    protected function get(string $key, array $data = null)
317
    {
318
        /**
319
         * @var $loader mixed
320
         */
321 11
        $loader = $this->loader($key) ?: $data;
322
323 11
        if (null === $loader)
324
        {
325 6
            return true;
326
        }
327
328 11
        $mixed = [];
329
330 11
        if ($loader)
331
        {
332 11
            $mixed = $loader;
333
334 11
            if (\is_object($mixed))
335
            {
336 11
                $mixed = $loader->asArray();
337
            }
338
        }
339
340 11
        return $this->getLexemes($key, $mixed);
341
    }
342
343
    /**
344
     * @param string $key
345
     *
346
     * @return bool
347
     */
348 11
    public function closed(string $key): bool
349
    {
350 11
        $this->data($key);
351
352 11
        return $this->closed[$key] ?? false;
353
    }
354
355
    /**
356
     * @param string $key
357
     * @param array  $data
358
     *
359
     * @return array|bool
360
     */
361 11
    public function data(string $key, array $data = null)
362
    {
363 11
        $this->tryLoad($key);
364
365 11
        if (!\array_key_exists($key, $this->data))
366
        {
367 11
            $this->data[$key] = $this->get($key, $data);
368
        }
369
370 11
        return $this->data[$key];
371
    }
372
373
    /**
374
     * @param string $value
375
     *
376
     * @return array
377
     */
378 11
    public function lexerApply(string $value): array
379
    {
380 11
        $name = __FUNCTION__ . $value;
381 11
        $item = Cache::getItem($name);
382
383 11
        if ($item && $item->isHit())
384
        {
385
            return $item->get();
386
        }
387
388 11
        $value  = '{{ ' . $value . ' }}';
389 11
        $tokens = $this->flow->lexer()->tokens($value);
390 11
        $_lexer = \current($tokens[Lexer::PRINTER]);
391
392
        $store = [
393 11
            'lexer' => $_lexer,
394 11
            'code'  => $this->flow->build($_lexer)
395
        ];
396
397 11
        return Cache::get($name, function () use ($store) {
398 11
            return $store;
399 11
        });
400
    }
401
402
    /**
403
     * @param string $key
404
     * @param string $tpl
405
     *
406
     * @return array|null
407
     */
408 11
    public function apply(string $key, string $tpl)
409
    {
410 11
        $lexData = $this->data($key);
411 11
        $data    = null;
412
413 11
        if (true === $lexData)
414
        {
415
            return $data;
416
        }
417
418 11
        foreach ($lexData as $datum)
419
        {
420 11
            if (\preg_match($datum['regexp'], $tpl, $outs))
421
            {
422 11
                $data = Arr::filter($outs, function (...$args) {
423 11
                    return \is_string(\end($args));
424 11
                });
425
426 11
                $data = Arr::map($data, [$this, 'lexerApply']);
427 11
                break;
428
            }
429
        }
430
431 11
        return $data;
432
    }
433
434
}
435