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
|
13 |
|
public function __construct(Flow $flow) |
72
|
|
|
{ |
73
|
13 |
|
$this->types = Property::get('types'); |
74
|
13 |
|
$this->addFolder(\dirname(__DIR__, 2) . '/lexemes'); |
75
|
13 |
|
$this->flow = $flow; |
76
|
13 |
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param string $path |
80
|
|
|
* |
81
|
|
|
* @return self |
82
|
|
|
*/ |
83
|
13 |
|
public function addFolder(string $path): self |
84
|
|
|
{ |
85
|
13 |
|
Arr::unShift($this->folders, $path); |
86
|
|
|
|
87
|
13 |
|
return $this; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @return Lexer |
92
|
|
|
*/ |
93
|
13 |
|
protected function lexer(): Lexer |
94
|
|
|
{ |
95
|
13 |
|
if (!$this->lexer) |
96
|
|
|
{ |
97
|
13 |
|
$this->lexer = new Lexer(); |
98
|
|
|
} |
99
|
|
|
|
100
|
13 |
|
return $this->lexer; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @param $file |
105
|
|
|
* |
106
|
|
|
* @return FileLoader\DataInterface|null |
107
|
|
|
*/ |
108
|
13 |
|
protected function loader($file) |
109
|
|
|
{ |
110
|
13 |
|
foreach ($this->folders as $folder) |
111
|
|
|
{ |
112
|
13 |
|
foreach (FileLoader::extensions() as $ext) |
113
|
|
|
{ |
114
|
|
|
try |
115
|
|
|
{ |
116
|
13 |
|
return FileLoader::load($folder . '/' . $file . '.' . $ext); |
117
|
|
|
} |
118
|
8 |
|
catch (\Throwable $throwable) |
119
|
8 |
|
{ |
120
|
|
|
// skip... |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
8 |
|
return null; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* @param array $props |
130
|
|
|
* @param string $key |
131
|
|
|
* |
132
|
|
|
* @return array |
133
|
|
|
*/ |
134
|
11 |
|
public function property(array $props, string $key): array |
135
|
|
|
{ |
136
|
11 |
|
$self = $this; |
137
|
11 |
|
$prop = &$props[$key]; |
138
|
|
|
|
139
|
11 |
|
if (empty($this->props[$key])) |
140
|
|
|
{ |
141
|
11 |
|
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
|
11 |
|
$this->props[$key] = $prop; |
154
|
|
|
} |
155
|
|
|
|
156
|
11 |
|
return $this->props[$key]; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* @param array $props |
161
|
|
|
* |
162
|
|
|
* @return array |
163
|
|
|
*/ |
164
|
13 |
|
protected function properties(array $props): array |
165
|
|
|
{ |
166
|
13 |
|
foreach ($props as $key => $prop) |
167
|
|
|
{ |
168
|
11 |
|
$this->property($props, $key); |
169
|
|
|
} |
170
|
|
|
|
171
|
13 |
|
return $this->props; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @param array $types |
176
|
|
|
* |
177
|
|
|
* @return array |
178
|
|
|
*/ |
179
|
13 |
|
protected function types(array $types): array |
180
|
|
|
{ |
181
|
13 |
|
$results = []; |
182
|
13 |
|
foreach ($types as $type) |
183
|
|
|
{ |
184
|
13 |
|
$results[] = $this->types[$type]; |
185
|
|
|
} |
186
|
|
|
|
187
|
13 |
|
return $results; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* @param string $key |
192
|
|
|
* |
193
|
|
|
* @return string |
194
|
|
|
*/ |
195
|
13 |
|
protected function fragment(string $key): string |
196
|
|
|
{ |
197
|
13 |
|
$property = $this->props[$key] ?? $this->default; |
198
|
|
|
|
199
|
13 |
|
return '(?<' . $key . '>(' . |
200
|
13 |
|
implode('|', $this->types($property['types'])) . |
201
|
13 |
|
'))'; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* @param string $key |
206
|
|
|
* @param array $syntax |
207
|
|
|
* |
208
|
|
|
* @return array |
209
|
|
|
*/ |
210
|
13 |
|
public function syntax2Array(string $key, array $syntax): array |
211
|
|
|
{ |
212
|
13 |
|
$props = $this->props[$key] ?? null; |
213
|
13 |
|
$closed = $this->closed[$key] ?? false; |
214
|
|
|
|
215
|
13 |
|
return Cache::get(__CLASS__ . $key, function () use ($syntax, $props, $closed) { |
216
|
|
|
return [ |
217
|
13 |
|
'syntax' => $syntax, |
218
|
13 |
|
'props' => $props, |
219
|
13 |
|
'closed' => $closed, |
220
|
|
|
]; |
221
|
13 |
|
}); |
222
|
|
|
} |
223
|
|
|
|
224
|
13 |
|
protected function tryLoad(string $key) |
225
|
|
|
{ |
226
|
13 |
|
if (empty($this->data[$key])) |
227
|
|
|
{ |
228
|
13 |
|
$item = Cache::getItem(__CLASS__ . $key); |
229
|
|
|
|
230
|
13 |
|
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
|
13 |
|
return null; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @param string $key |
247
|
|
|
* @param array $data |
248
|
|
|
* |
249
|
|
|
* @return array|mixed |
250
|
|
|
*/ |
251
|
13 |
|
protected function getLexemes(string $key, array $data) |
252
|
|
|
{ |
253
|
13 |
|
$syntax = $this->tryLoad($key); |
254
|
|
|
|
255
|
13 |
|
if (empty($this->data[$key])) |
256
|
|
|
{ |
257
|
13 |
|
$syntax = $this->syntax($key, $data); |
258
|
13 |
|
$this->syntax2Array($key, $syntax); |
259
|
|
|
} |
260
|
|
|
|
261
|
13 |
|
return $syntax; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @param string $key |
266
|
|
|
* @param array $data |
267
|
|
|
* |
268
|
|
|
* @return array |
269
|
|
|
*/ |
270
|
13 |
|
protected function syntax(string $key, array $data) |
271
|
|
|
{ |
272
|
13 |
|
foreach ($data['directives'] ?? [] as $_key => $directive) |
273
|
|
|
{ |
274
|
6 |
|
$this->data($_key, $directive ?: []); |
275
|
|
|
} |
276
|
|
|
|
277
|
13 |
|
$this->closed[$key] = $data['closed'] ?? false; |
278
|
13 |
|
$this->properties($data['properties'] ?? []); |
279
|
|
|
|
280
|
13 |
|
$syntax = []; |
281
|
|
|
|
282
|
13 |
|
foreach ($data['syntax'] ?? [] as $text) |
283
|
|
|
{ |
284
|
13 |
|
$tokens = $this->lexer()->tokens($text); |
|
|
|
|
285
|
13 |
|
$vars = $tokens[Lexer::PRINTER] ?? []; |
286
|
13 |
|
$code = \str_replace( |
287
|
13 |
|
['\\(', '\\)', ','], |
288
|
13 |
|
['\\( ', ' \\)', ' ,'], |
289
|
13 |
|
$text |
290
|
|
|
); |
291
|
|
|
|
292
|
13 |
|
foreach ($vars as $var) |
293
|
|
|
{ |
294
|
13 |
|
$code = \str_replace( |
295
|
13 |
|
$var['code'], |
296
|
13 |
|
$this->fragment($var['fragment']), |
297
|
13 |
|
$code |
298
|
|
|
); |
299
|
|
|
} |
300
|
|
|
|
301
|
13 |
|
$syntax[] = [ |
302
|
13 |
|
'vars' => $vars, |
303
|
13 |
|
'regexp' => '~^' . $key . ' ' . $code . '$~ui' |
304
|
|
|
]; |
305
|
|
|
} |
306
|
|
|
|
307
|
13 |
|
return $syntax; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* @param string $key |
312
|
|
|
* @param array|null $data |
313
|
|
|
* |
314
|
|
|
* @return array|bool|mixed |
315
|
|
|
*/ |
316
|
13 |
|
protected function get(string $key, array $data = null) |
317
|
|
|
{ |
318
|
|
|
/** |
319
|
|
|
* @var $loader mixed |
320
|
|
|
*/ |
321
|
13 |
|
$loader = $this->loader($key) ?: $data; |
322
|
|
|
|
323
|
13 |
|
if (null === $loader) |
324
|
|
|
{ |
325
|
7 |
|
return true; |
326
|
|
|
} |
327
|
|
|
|
328
|
13 |
|
$mixed = []; |
329
|
|
|
|
330
|
13 |
|
if ($loader) |
331
|
|
|
{ |
332
|
13 |
|
$mixed = $loader; |
333
|
|
|
|
334
|
13 |
|
if (\is_object($mixed)) |
335
|
|
|
{ |
336
|
13 |
|
$mixed = $loader->asArray(); |
337
|
|
|
} |
338
|
|
|
} |
339
|
|
|
|
340
|
13 |
|
return $this->getLexemes($key, $mixed); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @param string $key |
345
|
|
|
* |
346
|
|
|
* @return bool |
347
|
|
|
*/ |
348
|
13 |
|
public function closed(string $key): bool |
349
|
|
|
{ |
350
|
13 |
|
$this->data($key); |
351
|
|
|
|
352
|
13 |
|
return $this->closed[$key] ?? false; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* @param string $key |
357
|
|
|
* @param array $data |
358
|
|
|
* |
359
|
|
|
* @return array|bool |
360
|
|
|
*/ |
361
|
13 |
|
public function data(string $key, array $data = null) |
362
|
|
|
{ |
363
|
13 |
|
$this->tryLoad($key); |
364
|
|
|
|
365
|
13 |
|
if (!\array_key_exists($key, $this->data)) |
366
|
|
|
{ |
367
|
13 |
|
$this->data[$key] = $this->get($key, $data); |
368
|
|
|
} |
369
|
|
|
|
370
|
13 |
|
return $this->data[$key]; |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* @param string $value |
375
|
|
|
* |
376
|
|
|
* @return array |
377
|
|
|
*/ |
378
|
13 |
|
public function lexerApply(string $value): array |
379
|
|
|
{ |
380
|
13 |
|
$name = __FUNCTION__ . $value; |
381
|
13 |
|
$item = Cache::getItem($name); |
382
|
|
|
|
383
|
13 |
|
if ($item && $item->isHit()) |
384
|
|
|
{ |
385
|
|
|
return $item->get(); |
386
|
|
|
} |
387
|
|
|
|
388
|
13 |
|
$value = '{{ ' . $value . ' }}'; |
389
|
13 |
|
$tokens = $this->flow->lexer()->tokens($value); |
|
|
|
|
390
|
13 |
|
$_lexer = \current($tokens[Lexer::PRINTER]); |
391
|
|
|
|
392
|
|
|
$store = [ |
393
|
13 |
|
'lexer' => $_lexer, |
394
|
13 |
|
'code' => $this->flow->build($_lexer) |
395
|
|
|
]; |
396
|
|
|
|
397
|
13 |
|
return Cache::get($name, function () use ($store) { |
398
|
13 |
|
return $store; |
399
|
13 |
|
}); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* @param string $key |
404
|
|
|
* @param string $tpl |
405
|
|
|
* |
406
|
|
|
* @return array|null |
407
|
|
|
*/ |
408
|
13 |
|
public function apply(string $key, string $tpl) |
409
|
|
|
{ |
410
|
13 |
|
$lexData = $this->data($key); |
411
|
13 |
|
$data = null; |
412
|
|
|
|
413
|
13 |
|
if (true === $lexData) |
414
|
|
|
{ |
415
|
|
|
return $data; |
416
|
|
|
} |
417
|
|
|
|
418
|
13 |
|
foreach ($lexData as $datum) |
419
|
|
|
{ |
420
|
13 |
|
if (\preg_match($datum['regexp'], $tpl, $outs)) |
421
|
|
|
{ |
422
|
13 |
|
$data = Arr::filter($outs, function (...$args) { |
423
|
13 |
|
return \is_string(\end($args)); |
424
|
13 |
|
}); |
425
|
|
|
|
426
|
13 |
|
$data = Arr::map($data, [$this, 'lexerApply']); |
427
|
13 |
|
break; |
428
|
|
|
} |
429
|
|
|
} |
430
|
|
|
|
431
|
13 |
|
return $data; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
} |
435
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.