Passed
Push — master ( 079f1e...99c93a )
by Observer
01:36
created

Interpreter   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 232
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
eloc 120
c 0
b 0
f 0
dl 0
loc 232
rs 8.8798

2 Methods

Rating   Name   Duplication   Size   Complexity  
B formatLine() 0 52 9
D run() 0 155 35

How to fix   Complexity   

Complex Class

Complex classes like Interpreter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Interpreter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace VLF;
4
5
/**
6
 * Интерпретатор AST VLF разметки
7
 */
8
class Interpreter
9
{
10
    static array $objects = []; // Массив созданных объектов (название => объект)
11
12
    static bool $throw_errors = true; // Выводить ли ошибки интерпретации
13
    static bool $allow_multimethods_calls = true; // Можно ли использовать многоуровневые вызовы методов (->method1->method2)
14
15
    /**
16
     * Интерпретирование синтаксического дерева
17
     * 
18
     * @param AST $tree - Абстрактное Синтаксическое Дерево (АСД), сгенерированное VLF Parser'ом
19
     * [@param array $parent = null] - нода-родитель дерева (системная настройка)
20
     * 
21
     * @return array - возвращает список созданных объектов
22
     */
23
    public static function run (AST $tree, Node $parent = null): array
24
    {
25
        foreach ($tree->getNodes () as $id => $node)
26
        {
27
            switch ($node->type)
28
            {
29
                case OBJECT_DEFINITION:
30
                    $class = $node->args['class'];
31
                    $name  = $node->args['name'];
32
                    $args  = [];
33
34
                    if (isset (self::$objects[$name]))
35
                        break;
36
37
                    if (isset ($node->args['args']))
38
                    {
39
                        $args = $node->args['args'];
40
41
                        foreach ($args as $arg_id => $arg)
42
                            $args[$arg_id] = self::formatLine ($arg, self::$objects);
43
                    }
44
45
                    try
46
                    {
47
                        self::$objects[$name] = eval ("namespace VoidEngine; return new $class (". implode (', ', $args) .");");
1 ignored issue
show
introduced by
The use of eval() is discouraged.
Loading history...
48
49
                        try
50
                        {
51
                            self::$objects[$name]->name = $name;
52
                        }
53
54
                        catch (\Throwable $e) {}
1 ignored issue
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
55
                    }
56
57
                    catch (\Throwable $e)
58
                    {
59
                        if (self::$throw_errors)
60
                            throw new \Exception ('Interpeter couldn\'t create object "'. $class .'" with name "'. $name .'" at line "'. $node->line .'". Exception info:'. "\n\n". (string) $e, 0, $e);
61
                    }
62
                break;
63
64
                case PROPERTY_SET:
65
                    if ($parent !== null)
66
                    {
67
                        $name = $parent->args['name'];
68
69
                        $propertyName  = $node->args['name'];
70
                        $propertyValue = $node->args['value'];
71
                        $preset        = '';
72
73
                        if (preg_match ('/function \((.*)\) use \((.*)\)/', $propertyValue))
74
                        {
75
                            $use = substr ($propertyValue, strpos ($propertyValue, 'use'));
76
                            $use = $ouse = substr ($use, ($pos = strpos ($use, '(') + 1), strpos ($use, ')') - $pos);
77
                            $use = explode (' ', $use);
78
79
                            foreach ($use as $id => $useParam)  
0 ignored issues
show
Comprehensibility Bug introduced by
$id is overwriting a variable from outer foreach loop.
Loading history...
80
                                if (isset (self::$objects[$useParam]) && $use[$id + 1][0] == '$')
81
                                {
82
                                    $fname = $use[$id + 1];
83
84
                                    if (substr ($fname, strlen ($fname) - 1) == ',')
85
                                        $fname = substr ($fname, 0, -1);
86
87
                                    $preset .= "$fname = $useParam; ";
88
89
                                    unset ($use[$id]);
90
                                }
91
92
                            $preset        = self::formatLine ($preset, self::$objects);
93
                            $propertyValue = self::formatLine (str_replace ($ouse, implode (' ', $use), $propertyValue), self::$objects);
94
                        }
95
96
                        else $propertyValue = self::formatLine ($propertyValue, self::$objects);
97
98
                        try
99
                        {
100
                            self::$objects[$name]->$propertyName = eval ("namespace VoidEngine; $preset return $propertyValue;");
1 ignored issue
show
introduced by
The use of eval() is discouraged.
Loading history...
101
                        }
102
103
                        catch (\Throwable $e)
104
                        {
105
                            if (self::$throw_errors)
106
                                throw new \Exception ('Interpeter couldn\'t set property "'. $propertyName .'" with value "'. $propertyValue .'" at line "'. $node->line .'". Exception info:'. "\n\n". (string) $e, 0, $e);
107
                        }
108
                    }
109
110
                    elseif (self::$throw_errors)
111
                        throw new \Exception ('Setting property to an non-object at line "'. $node->line);
112
                break;
113
114
                case METHOD_CALL:
115
                    if ($parent !== null)
116
                    {
117
                        $name = $parent->args['name'];
118
119
                        $methodName = $node->args['name'];
120
                        $methodArgs = $node->args['args'];
121
122
                        foreach ($methodArgs as $arg_id => $arg)
123
                            $methodArgs[$arg_id] = self::formatLine ($arg, self::$objects);
124
125
                        try
126
                        {
127
                            if (strpos ($methodName, '->') !== false && self::$allow_multimethods_calls)
128
                                eval ('namespace VoidEngine; _c('. self::$objects[$name]->selector .')->'. $methodName .' ('. implode (', ', $methodArgs) .');');
1 ignored issue
show
introduced by
The use of eval() is discouraged.
Loading history...
129
130
                            elseif (sizeof ($methodArgs) > 0)
131
                                self::$objects[$name]->$methodName (...eval ('namespace VoidEngine; return ['. implode (', ', $methodArgs) .'];'));
1 ignored issue
show
introduced by
The use of eval() is discouraged.
Loading history...
132
133
                            else self::$objects[$name]->$methodName ();
134
                        }
135
136
                        catch (\Throwable $e)
137
                        {
138
                            if (self::$throw_errors)
139
                                throw new \Exception ('Interpeter couldn\'t call method "'. $methodName .'" with arguments '. json_encode ($methodArgs) .' at line "'. $node->line .'". Exception info:'. "\n\n". (string) $e, 0, $e);
140
                        }
141
                    }
142
143
                    elseif (self::$throw_errors)
144
                        throw new \Exception ('Calling method to an non-object at line "'. $node->line .'"');
145
                break;
146
147
                case STYLES_IMPORTING:
148
                    foreach ($node->args['imports'] as $style)
149
                    {
150
                        $path = eval ('namespace VoidEngine; return '. self::formatLine ($style, self::$objects) .';');
1 ignored issue
show
introduced by
The use of eval() is discouraged.
Loading history...
151
152
                        if (!file_exists ($path))
153
                            throw new \Exception ('Trying to import nonexistent style at line "'. $node->line .'"');
154
                        
155
                        \VLF\VST\Interpreter::run (\VLF\VST\Parser::parse (file_get_contents ($path)));
156
                    }
157
                break;
158
159
                case RUNTIME_EXECUTION:
160
                    eval (self::formatLine ($node->args['code'], self::$objects));
1 ignored issue
show
introduced by
The use of eval() is discouraged.
Loading history...
161
                break;
162
            }
163
164
            $nodes = $node->getNodes ();
165
166
            if (isset ($node->args['styles']))
167
                foreach ($node->args['styles'] as $style)
168
                    if (isset (\VLF\VST\Interpreter::$styles[$style]))
169
                        $nodes = array_merge ($nodes, \VLF\VST\Interpreter::$styles[$style]);
170
171
                    else throw new \Exception ('Trying to set undefined style to object at line "'. $node->line .'"');
172
173
            self::$objects = self::run (new AST (array_map (
174
                fn ($node) => $node->export (), $nodes)), $node);
175
        }
176
177
        return self::$objects;
178
    }
179
180
    /**
181
     * Форматирование строки
182
     * 
183
     * @param string $line - строка для форматирования
184
     * [@param array $objects = []] - список объектов, которые будут участвовать в форматировании
185
     * 
186
     * @return string - возвращает форматированную строку
187
     */
188
    public static function formatLine (string $line, array $objects = []): string
189
    {
190
        if (sizeof ($objects) > 0)
191
        {
192
            $len     = strlen ($line);
193
            $newLine = '';
194
195
            $replacement = array_map (function ($object)
196
            {
197
                return \VoidEngine\Components::exists ($object->selector) !== false ? 
198
                    '\VoidEngine\_c('. $object->selector .')' :
199
                    'unserialize (\''. serialize ($object) .'\')';
200
            }, $objects);
201
202
            $replacement = array_map (function ($name)
203
            {
204
                return strlen ($name = trim ($name)) + substr_count ($name, '_');
205
            }, $omap = array_flip ($replacement));
0 ignored issues
show
Bug introduced by
It seems like $omap = array_flip($replacement) can also be of type null; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

205
            }, /** @scrutinizer ignore-type */ $omap = array_flip ($replacement));
Loading history...
206
207
            arsort ($replacement);
208
209
            $nReplacement = [];
210
211
            foreach ($replacement as $replaceTo => $nLn)
212
                $nReplacement[$omap[$replaceTo]] = $replaceTo;
213
214
            $replacement = $nReplacement;
215
            $blacklist   = array_flip (['\'', '"', '$']);
216
217
            for ($i = 0; $i < $len; ++$i)
218
            {
219
                $replaced = false;
220
221
                foreach ($replacement as $name => $replaceAt)
222
                    if (substr ($line, $i, ($l = strlen ($name))) == $name && !isset ($blacklist[$line[$i - 1]]))
223
                    {
224
                        $newLine .= $replaceAt;
225
226
                        $i += $l - 1;
227
                        $replaced = true;
228
229
                        break;
230
                    }
231
232
                if (!$replaced)
233
                    $newLine .= $line[$i];
234
            }
235
236
            $line = $newLine;
237
        }
238
239
        return $line;
240
    }
241
}
242