Passed
Push — master ( 7f8b3f...7ac9f0 )
by Observer
01:45
created

VLFParser::parseArguments()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 18
c 0
b 0
f 0
nc 7
nop 1
dl 0
loc 35
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace VoidEngine;
4
5
class VLFParser
6
{
7
    public string $divider = "\n"; // Разделитель строк
8
9
    public bool $strong_line_parser            = true; // Использовать ли строгий парсер слов (только алфавит и цифры)
10
    public bool $ignore_postobject_info        = false; // Игнорировать ли символы после скобок объектов
11
    public bool $ignore_unexpected_method_args = false; // Игнорировать ли отсутствующие перечисления аргументов методов
12
13
    public bool $use_caching = false; // Кэшировать ли деревья
14
    public bool $debug_mode  = false; // Выводить ли дебаг-сообщения парсера
15
16
    protected $tree; // АСД (Абстрактное Синтаксическое Дерево)
17
    protected $links; // Список ссылок объект -> индекс в АСД
18
19
    /**
20
     * * Конструктор парсера
21
     * Выполняет парсинг АСД из VLF разметки, если надо - кэширует его
22
     * 
23
     * @param string $content - VLF разметка или путь до файла разметки
24
     * [@param array $settings = []] - список настроек и их значений (настройка => значение)
25
     */
26
    public function __construct (string $content, array $settings = [])
27
    {
28
        if (file_exists ($content))
29
            $content = file_get_contents ($content);
30
31
        // Зачем? Так надо!
32
        $content = "# VLF begin\n\n$content\n\n# VLF end";
33
34
        foreach ($settings as $name => $setting)
35
        {
36
            if (isset ($this->$name))
37
                $this->$name = $setting;
38
39
            else throw new \Exception ('Trying to setting up undefined property "'. $name .'"');
40
        }
41
42
        if ($this->use_caching && file_exists ($file = VLF_EXT_DIR .'/cache/'. sha1 ($content) .'.cache'))
43
        {
44
            $info = unserialize (gzinflate (file_get_contents ($file)));
45
46
            if ($info[0] == sha1 (file_get_contents (__FILE__)))
47
            {
48
                $this->tree  = $info[1][0];
49
                $this->links = $info[1][1];
50
51
                return;
52
            }
53
54
            else unlink ($file);
55
        }
56
57
        $info = $this->generateSyntaxTree ($content);
58
59
        $this->tree  = $info[0];
60
        $this->links = $info[1];
61
62
        if ($this->use_caching)
63
        {
64
            if (!is_dir (dirname (__DIR__) .'/cache'))
65
                mkdir (dirname (__DIR__) .'/cache');
66
67
            file_put_contents (VLF_EXT_DIR .'/cache/'. sha1 ($content) .'.cache', gzdeflate (serialize ([sha1 (file_get_contents (__FILE__)), $info])));
68
        }
69
    }
70
71
    /**
72
     * * Генератор АСД
73
     * Конвертирует VLF разметку в АСД
74
     * 
75
     * @param string $content - VLF разметка
76
     * 
77
     * @return array - возвращает АСД
78
     */
79
    protected function generateSyntaxTree (string $content): array
80
    {
81
        $lines          = $this->linesFilter ($untouched_lines = explode ($this->divider, $content));
82
        $current_object = null;
83
        $parent_objects = [];
84
        $skip_at        = -1;
85
        $tree           = [];
86
        $links          = [];
87
88
        if ($this->debug_mode)
89
            pre ($lines);
90
91
        foreach ($lines as $id => $line)
92
        {
93
            if ($skip_at > $id)
94
                continue;
95
96
            $height = $this->getLineHeight ($line);
97
            $words  = $this->linesFilter (explode (' ', $line));
98
99
            if ($this->debug_mode)
100
                pre ($words);
101
102
            /**
103
             * Высокоинтеллектуальный фикс
104
             * Редирект из объектов более высокого уровня к более низкому уровню
105
             * 
106
             * Form MainForm
107
             *     Button MainButton1
108
             *         ...
109
             * 
110
             *     caption: 'MainForm'
111
             * 
112
             * Нужно для того, чтобы указатель с объекта MainButton1 спрыгнул обратно на MainForm
113
             * 
114
             * subparent_link нужен цикл while для того, чтобы перебрать некоторые подобъекты, у которых в аргументах
115
             * не используются ссылки на оригиналы объектов
116
             */
117
            while ($current_object !== null && $tree[$current_object]['hard'] >= $height)
118
            {
119
                $updated = false;
120
121
                if ($this->debug_mode)
122
                    pre ($current_object);
123
124
                while (isset ($tree[$current_object]['info']['subparent_link']) && $tree[$link = $tree[$current_object]['info']['subparent_link']->link]['hard'] < $tree[$current_object]['hard'])
125
                {
126
                    $current_object = $link;
127
                    $updated        = true;
128
129
                    if ($this->debug_mode)
130
                        pre ($current_object);
131
                }
132
133
                if (
134
                    !$updated &&
135
                    isset ($tree[$current_object]['info']['arguments']) &&
136
                    isset ($tree[$current_object]['info']['arguments'][0]) &&
137
                    $tree[$current_object]['info']['arguments'][0] instanceof VLFLink &&
138
                    $tree[$tree[$current_object]['info']['arguments'][0]->link]['hard'] < $tree[$current_object]['hard']
139
                ) $current_object = $tree[$current_object]['info']['arguments'][0]->link;
140
141
                elseif (!$updated)
142
                    break;
143
144
                if ($this->debug_mode)
145
                    pre ($current_object);
146
            }
147
148
            /**
149
             * Button ...
150
             */
151
            if (class_exists ($words[0]) || class_exists ('\VoidEngine\\'. $words[0]))
152
            {
153
                if (!isset ($words[1]))
154
                    throw new \Exception ('Object name mustn\'t be empty at line "'. $line .'"');
155
156
                /**
157
                 * Button NewButton
158
                 * ...
159
                 * 
160
                 * Button NewButton
161
                 *     text: 123
162
                 */
163
                if (isset ($links[$words[1]]))
164
                {
165
                    $tree[$id] = [
166
                        'type'  => VLF_OBJECT_REDIRECTION,
167
                        'line'  => $line,
168
                        'hard'  => $height,
169
                        'words' => $words,
170
171
                        'info' => [
172
                            'object_class' => $words[0],
173
                            'object_name'  => $words[1]
174
                        ],
175
176
                        'syntax_nodes' => []
177
                    ];
178
179
                    $current_object = $id;
180
181
                    continue;
182
                }
183
184
                else
185
                {
186
                    $tree[$id] = [
187
                        'type'  => VLF_OBJECT_DEFINITION,
188
                        'line'  => $line,
189
                        'hard'  => $height,
190
                        'words' => $words,
191
192
                        'info' => [
193
                            'object_class' => $words[0],
194
                            'object_name'  => $words[1]
195
                        ],
196
197
                        'syntax_nodes' => []
198
                    ];
199
200
                    if (($begin = strpos ($line, '(')) !== false)
201
                    {
202
                        ++$begin;
203
                        
204
                        $end = strrpos ($line, ')');
205
206
                        if ($end === false)
207
                            throw new \Exception ('Line "'. $line .'" have arguments list initialization, but not have list ending');
208
209
                        elseif ($begin < $end)
210
                        {
211
                            $arguments = [];
212
                            // $parsed    = explode (',', substr ($line, $begin, $end - $begin));
213
                            $parsed    = $this->parseArguments (substr ($line, $begin, $end - $begin));
214
215
                            foreach ($parsed as $argument_id => $argument)
216
                            {
217
                                $argument = trim ($argument);
218
219
                                if (strlen ($argument) > 0)
220
                                    $arguments[] = isset ($links[$argument]) ?
221
                                        new VLFLink ($argument, $links[$argument]) :
222
                                        $argument;
223
224
                                else throw new \Exception ('Argument '. ($argument_id + 1) .' mustn\'t have zero length at line "'. $line .'"');
225
                            }
226
227
                            $tree[$id]['info']['arguments'] = $arguments;
228
229
                            if (!$this->ignore_postobject_info && trim (substr ($line, $end)) > 0)
230
                                throw new \Exception ('You mustn\'t write any chars after arguments definition');
231
                        }
232
233
                        $tree[$id]['info']['subparent_link'] = new VLFLink ($tree[$current_object]['info']['object_name'], $current_object);
234
                    }
235
236
                    /**
237
                     * Form MainForm
238
                     *     Button MainButton
239
                     */
240
                    elseif ($current_object !== null && $tree[$current_object]['hard'] < $height)
241
                    {
242
                        $tree[$id]['info']['arguments'] = [
243
                            new VLFLink ($tree[$current_object]['info']['object_name'], $current_object)
244
                        ];
245
246
                        $parent_objects[$id] = $current_object;
247
                    }
248
249
                    /**
250
                     * Если высота блока будет выше, чем высота текущего объекта, то текущий объект будет обработан кодом выше
251
                     * Если высота блока будет ниже, чем высота текущего объекта, то он создан вне блока текущего объекта и вообще не обрабатывается
252
                     * Если же высоты совпадают, то мы дописываем текущему объекту в аргументы 
253
                     * 
254
                     * ? Вариант с одинаковыми высотами временно отключен -_-
255
                     */
256
257
                    /*elseif (
258
                        $current_object !== null &&
259
                        $tree[$current_object]['hard'] == $height
260
                    )
261
                    {
262
                        $tree[$id]['info']['arguments'] = [
263
                            new VLFLink ($tree[$current_object]['info']['object_name'], $current_object)
264
                        ];
265
266
                        $parent_objects[$id] = $current_object;
267
                    }*/
268
269
                    $links[$tree[$id]['info']['object_name']] = $id;
270
                    $current_object = $id;
271
                }
272
            }
273
274
            /**
275
             * # ALALALAHAAHAH
276
             * 
277
             * #^ ALALA
278
             *    HAHAHA
279
             * 
280
             *    SUPER COMMENT 3000
281
             */
282
            elseif ($words[0][0] == '#')
283
            {
284
                $comment = $line;
285
286
                if (isset ($words[0][1]))
287
                {
288
                    if ($words[0][1] == '^')
289
                    {
290
                        $parsed = $this->parseSubText ($untouched_lines, $id, $height);
291
292
                        $comment .= $parsed[0];
293
                        $skip_at  = $parsed[1];
294
                    }
295
296
                    else throw new \Exception ('Unknown char founded after syntax-control symbol at line "'. $line .'"');
297
                }
298
                
299
                if ($this->debug_mode)
300
                    pre ("Comment:\n\n$comment");
301
            }
302
303
            /**
304
             * % VoidEngine\pre (123);
305
             * 
306
             * % namespace VoidEngine;
307
             * 
308
             *   pre (123);
309
             */
310
            elseif ($words[0][0] == '%')
311
            {
312
                $code = substr ($line, strlen ($words[0]));
0 ignored issues
show
Bug introduced by
$words[0] of type array is incompatible with the type string expected by parameter $string of strlen(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

312
                $code = substr ($line, strlen (/** @scrutinizer ignore-type */ $words[0]));
Loading history...
313
314
                if (isset ($words[0][1]))
315
                {
316
                    if ($words[0][1] == '^')
317
                    {
318
                        $parsed = $this->parseSubText ($untouched_lines, $id, $height);
319
320
                        $code   .= $parsed[0];
321
                        $skip_at = $parsed[1];
322
                    }
323
324
                    else throw new \Exception ('Unknown char founded after syntax-control symbol at line "'. $line .'"');
325
                }
326
                
327
                $tree[$id] = [
328
                    'type'  => VLF_RUNTIME_EXECUTABLE,
329
                    'line'  => $line,
330
                    'hard'  => $height,
331
                    'words' => $words,
332
333
                    'info' => [
334
                        'code' => $code
335
                    ],
336
337
                    'syntax_nodes' => []
338
                ];
339
            }
340
341
            /**
342
             * property_name: property_value
343
             * 
344
             * ->method_name ([method_arguments])
345
             * 
346
             * Form MyForm
347
             *     Button MyButton
348
             */
349
            elseif (is_int ($current_object) && isset ($tree[$current_object]['hard']))
350
            {
351
                if ($height <= $tree[$current_object]['hard'] && isset ($parent_objects[$current_object]))
352
                {
353
                    $redirect = $parent_objects[$current_object];
354
355
                    $tree[$id] = [
356
                        'type'  => VLF_OBJECT_REDIRECTION,
357
                        'line'  => $line,
358
                        'hard'  => $height,
359
                        'words' => $words,
360
361
                        'info' => [
362
                            'object_class' => $tree[$redirect]['info']['object_class'],
363
                            'object_name'  => $tree[$redirect]['info']['object_name']
364
                        ],
365
366
                        'syntax_nodes' => []
367
                    ];
368
369
                    $current_object = $id;
370
                }
371
372
                /**
373
                 * property_name: property_value
374
                 */
375
                $postChar = substr ($words[0], strlen ($words[0]) - 1);
0 ignored issues
show
Bug introduced by
$words[0] of type array is incompatible with the type string expected by parameter $string of substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

375
                $postChar = substr (/** @scrutinizer ignore-type */ $words[0], strlen ($words[0]) - 1);
Loading history...
376
377
                if ($postChar == ':' || $postChar == '^')
378
                {
379
                    if (!isset ($words[1]))
380
                        throw new \Exception ('Property value mustn\'t be empty at line "'. $line .'"');
381
382
                    $propertyName     = substr ($words[0], 0, -1);
383
                    $propertyValue    = implode (' ', array_slice ($words, 1));
384
                    $propertyRawValue = ltrim (substr ($line, strlen ($words[0])));
385
386
                    /**
387
                     * property_name:^ property_value_1
388
                     *                 property_value_2
389
                     */
390
                    if ($postChar == '^')
391
                    {
392
                        $parsed = $this->parseSubText ($untouched_lines, $id, $height);
393
394
                        $skip_at           = $parsed[1];
395
                        $propertyName      = substr ($propertyName, 0, -1);
396
                        $propertyRawValue .= $parsed[0];
397
                        $propertyValue     = $propertyRawValue;
398
                    }
399
400
                    $info = [
401
                        'type'  => VLF_PROPERTY_SET,
402
                        'line'  => $line,
403
                        'hard'  => $height,
404
                        'words' => $words,
405
406
                        'info' => [
407
                            'property_name'      => $propertyName,
408
                            'property_value'     => $propertyValue,
409
                            'property_raw_value' => $propertyRawValue
410
                        ],
411
412
                        'syntax_nodes' => []
413
                    ];
414
415
                    if (isset ($links[$info['info']['property_value']]))
416
                        $info['info']['property_value'] = new VLFLink ($info['info']['property_value'], $links[$info['info']['property_value']]);
417
418
                    $tree[$current_object]['syntax_nodes'][] = $info;
419
                }
420
421
                /**
422
                 * ->method_name ([method_arguments])
423
                 */
424
                elseif (substr ($words[0], 0, 2) == '->')
425
                {
426
                    $arguments = [];
427
                    
428
                    if (($begin = strpos ($line, '(')) !== false)
429
                    {
430
                        ++$begin;
431
                        
432
                        $end = strrpos ($line, ')');
433
434
                        if ($end === false)
435
                            throw new \Exception ('Line "'. $line .'" have arguments list initialization, but not have list ending');
436
437
                        elseif ($begin < $end)
438
                        {
439
                            // $parsed = explode (',', substr ($line, $begin, $end - $begin));
440
                            $parsed = $this->parseArguments (substr ($line, $begin, $end - $begin));
441
442
                            foreach ($parsed as $argument_id => $argument)
443
                            {
444
                                $argument = trim ($argument);
445
446
                                if (strlen ($argument) > 0)
447
                                    $arguments[] = isset ($links[$argument]) ?
448
                                        new VLFLink ($argument, $links[$argument]) :
449
                                        $argument;
450
451
                                else throw new \Exception ('Argument '. ($argument_id + 1) .' mustn\'t have zero length at line "'. $line .'"');
452
                            }
453
454
                            if (!$this->ignore_postobject_info && trim (substr ($line, $end)) > 0)
455
                                throw new \Exception ('You mustn\'t write any chars after arguments definition');
456
                        }
457
                    }
458
459
                    /**
460
                     * ->show
461
                     */
462
                    elseif (!$this->ignore_unexpected_method_args)
463
                        throw new \Exception ('Unexpected method arguments list at line "'. $line .'"');
464
465
                    $tree[$current_object]['syntax_nodes'][] = [
466
                        'type'  => VLF_METHOD_CALL,
467
                        'line'  => $line,
468
                        'hard'  => $height,
469
                        'words' => $words,
470
471
                        'info' => [
472
                            'method_name'      => substr ($words[0], 2),
473
                            'method_arguments' => $arguments
474
                        ],
475
476
                        'syntax_nodes' => []
477
                    ];
478
                }
479
480
                /**
481
                 * ...я хз что тут должно быть, но первоначально это должно было работать так:
482
                 * 
483
                 * Form MainForm
484
                 *     Button MainButton
485
                 *         ...
486
                 * 
487
                 * И вот весь этот Button и всё, что после него - это и есть VLF_SUBOBJECT_DEFINITION
488
                 * Но на практике я придумал какой-то дикий костыль в блоке с VLF_OBJECT_DEFINITION
489
                 * Не вините меня ;D
490
                 * 
491
                 * ? UPD: я чекнул АСД главной формы VoidStudio и заметил, что там всё-же где-то да есть эта штука, так что лучше её не трогать и всё оставить как есть ;D
492
                 */
493
                else
494
                {
495
                    $parsed  = $this->parseSubText ($untouched_lines, $id, $height);
496
                    $skip_at = $parsed[1];
497
                    
498
                    $tree[$id] = [
499
                        'type'  => VLF_SUBOBJECT_DEFINITION,
500
                        'line'  => $line,
501
                        'hard'  => $height,
502
                        'words' => $words,
503
504
                        'info' => [
505
                            'object_vlf_info' => $line ."\n". $parsed[0]
506
                        ],
507
508
                        'syntax_nodes' => []
509
                    ];
510
                }
511
            }
512
513
            /**
514
             * Что-то загадочное, таинственное, неизвестное человечеству
515
             */
516
            else throw new \Exception ('Unknown structures founded at line "'. $line .'"');
517
        }
518
519
        return [$tree, $links];
520
    }
521
522
    /**
523
     * * Парсер подстрок
524
     * Парсит текст, уровень которого выше, чем указанный
525
     * 
526
     * @param array $lines - массив строк
527
     * @param mixed $begin_id - начальный индекс для парсинга
528
     * @param int $down_height - нижняя высота, после которой текст парситься не будет
529
     * 
530
     * @return array - возвращает спарсенные подстроки
531
     */
532
    protected function parseSubText (array $lines, $begin_id, int $down_height): array
533
    {
534
        $parsed = "\n";
535
536
        foreach ($lines as $line_id => $line)
537
        {
538
            if ($line_id <= $begin_id)
539
                continue;
540
541
            if (!(bool)(trim ($line)))
542
            {
543
                $parsed .= "\n";
544
            
545
                continue;
546
            }
547
548
            $height = $this->getLineHeight ($line);
549
550
            if ($this->debug_mode)
551
                pre ("$height, $down_height, $line");
552
553
            if ($height > $down_height)
554
                $parsed .= "$line\n";
555
556
            else break;
557
        }
558
559
        return [$parsed, $line_id];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $line_id seems to be defined by a foreach iteration on line 536. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
560
    }
561
562
    public function __get ($name) // Возвращалка переменных парсера
563
    {
564
        return isset ($this->$name) ?
565
            $this->$name : false;
566
    }
567
568
    /**
569
     * * Подсчёт высоты строки
570
     * Производит подсчёт высоты строки и удаляет пустые текстовые символы с обоих её концов
571
     * 
572
     * @param string &$line - строка для подсчёта высоты
573
     * 
574
     * @return int - высота строки
575
     */
576
    protected function getLineHeight (string &$line): int
577
    {
578
        $i = 0;
579
580
        while (isset ($line[$i]) && $line[$i] == "\t")
581
            ++$i;
582
        
583
        return strlen ($line = str_repeat ('    ', $i) . substr ($line, $i)) - strlen ($line = trim ($line));
584
    }
585
586
    /**
587
     * * Фильтр строк
588
     * Удаляет все пустые значения в массиве
589
     * 
590
     * @param array $segments - массив строк
591
     * 
592
     * @return array - возвращает очищенный массив
593
     */
594
    protected function linesFilter (array $segments): array
595
    {
596
        return array_filter ($segments, function ($text)
597
        {
598
            if ($this->strong_line_parser && preg_match ('/[^a-z0-9]/i', $text))
599
                throw new \Exception  ('Line "'. $text .'" mustn\'t have any not-alphabet or not-numeric characters');
600
            
601
            return strlen (trim ($text)) > 0;
602
        });
603
    }
604
605
    protected function parseArguments (string $arguments): array
606
    {
607
        $args = [];
608
609
        $split1   = $split2 = false;
610
        $canSplit = -1;
611
612
        $t = '';
613
614
        for ($i = 0, $len = strlen ($arguments); $i < $len; ++$i)
615
        {
616
            $t .= $arguments[$i];
617
            
618
            if ($arguments[$i] == '\\')
619
                $canSplit = $i + 1;
620
621
            elseif ($canSplit < $i)
622
            {
623
                if ($arguments[$i] == '\'' && !$split2)
624
                    $split1 = !$split1;
0 ignored issues
show
introduced by
The condition $split1 is always false.
Loading history...
625
626
                elseif ($arguments[$i] == '"' && !$split1)
627
                    $split2 = !$split2;
628
629
                elseif (!$split1 && !$split2 && $arguments[$i] == ',')
630
                {
631
                    $args[] = substr ($t, 0, -1);
632
                    $t = '';
633
                }
634
            }
635
        }
636
637
        $args[] = $t;
638
639
        return $args;
640
    }
641
}
642