Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace JmesPath;
3
 
4
/**
5
 * Tree visitor used to compile JMESPath expressions into native PHP code.
6
 */
7
class TreeCompiler
8
{
9
    private $indentation;
10
    private $source;
11
    private $vars;
12
 
13
    /**
14
     * @param array  $ast    AST to compile.
15
     * @param string $fnName The name of the function to generate.
16
     * @param string $expr   Expression being compiled.
17
     *
18
     * @return string
19
     */
20
    public function visit(array $ast, $fnName, $expr)
21
    {
22
        $this->vars = [];
23
        $this->source = $this->indentation = '';
24
        $this->write("<?php\n")
25
            ->write('use JmesPath\\TreeInterpreter as Ti;')
26
            ->write('use JmesPath\\FnDispatcher as Fd;')
27
            ->write('use JmesPath\\Utils;')
28
            ->write('')
29
            ->write('function %s(Ti $interpreter, $value) {', $fnName)
30
            ->indent()
31
                ->dispatch($ast)
32
                ->write('')
33
                ->write('return $value;')
34
            ->outdent()
35
        ->write('}');
36
 
37
        return $this->source;
38
    }
39
 
40
    /**
41
     * @param array $node
42
     * @return mixed
43
     */
44
    private function dispatch(array $node)
45
    {
46
        return $this->{"visit_{$node['type']}"}($node);
47
    }
48
 
49
    /**
50
     * Creates a monotonically incrementing unique variable name by prefix.
51
     *
52
     * @param string $prefix Variable name prefix
53
     *
54
     * @return string
55
     */
56
    private function makeVar($prefix)
57
    {
58
        if (!isset($this->vars[$prefix])) {
59
            $this->vars[$prefix] = 0;
60
            return '$' . $prefix;
61
        }
62
 
63
        return '$' . $prefix . ++$this->vars[$prefix];
64
    }
65
 
66
    /**
67
     * Writes the given line of source code. Pass positional arguments to write
68
     * that match the format of sprintf.
69
     *
70
     * @param string $str String to write
71
     * @return $this
72
     */
73
    private function write($str)
74
    {
75
        $this->source .= $this->indentation;
76
        if (func_num_args() == 1) {
77
            $this->source .= $str . "\n";
78
            return $this;
79
        }
80
        $this->source .= vsprintf($str, array_slice(func_get_args(), 1)) . "\n";
81
        return $this;
82
    }
83
 
84
    /**
85
     * Decreases the indentation level of code being written
86
     * @return $this
87
     */
88
    private function outdent()
89
    {
90
        $this->indentation = substr($this->indentation, 0, -4);
91
        return $this;
92
    }
93
 
94
    /**
95
     * Increases the indentation level of code being written
96
     * @return $this
97
     */
98
    private function indent()
99
    {
100
        $this->indentation .= '    ';
101
        return $this;
102
    }
103
 
104
    private function visit_or(array $node)
105
    {
106
        $a = $this->makeVar('beforeOr');
107
        return $this
108
            ->write('%s = $value;', $a)
109
            ->dispatch($node['children'][0])
110
            ->write('if (!$value && $value !== "0" && $value !== 0) {')
111
                ->indent()
112
                ->write('$value = %s;', $a)
113
                ->dispatch($node['children'][1])
114
                ->outdent()
115
            ->write('}');
116
    }
117
 
118
    private function visit_and(array $node)
119
    {
120
        $a = $this->makeVar('beforeAnd');
121
        return $this
122
            ->write('%s = $value;', $a)
123
            ->dispatch($node['children'][0])
124
            ->write('if ($value || $value === "0" || $value === 0) {')
125
                ->indent()
126
                ->write('$value = %s;', $a)
127
                ->dispatch($node['children'][1])
128
                ->outdent()
129
            ->write('}');
130
    }
131
 
132
    private function visit_not(array $node)
133
    {
134
        return $this
135
            ->write('// Visiting not node')
136
            ->dispatch($node['children'][0])
137
            ->write('// Applying boolean not to result of not node')
138
            ->write('$value = !Utils::isTruthy($value);');
139
    }
140
 
141
    private function visit_subexpression(array $node)
142
    {
143
        return $this
144
            ->dispatch($node['children'][0])
145
            ->write('if ($value !== null) {')
146
                ->indent()
147
                ->dispatch($node['children'][1])
148
                ->outdent()
149
            ->write('}');
150
    }
151
 
152
    private function visit_field(array $node)
153
    {
154
        $arr = '$value[' . var_export($node['value'], true) . ']';
155
        $obj = '$value->{' . var_export($node['value'], true) . '}';
156
        $this->write('if (is_array($value) || $value instanceof \\ArrayAccess) {')
157
                ->indent()
158
                ->write('$value = isset(%s) ? %s : null;', $arr, $arr)
159
                ->outdent()
160
            ->write('} elseif ($value instanceof \\stdClass) {')
161
                ->indent()
162
                ->write('$value = isset(%s) ? %s : null;', $obj, $obj)
163
                ->outdent()
164
            ->write("} else {")
165
                ->indent()
166
                ->write('$value = null;')
167
                ->outdent()
168
            ->write("}");
169
 
170
        return $this;
171
    }
172
 
173
    private function visit_index(array $node)
174
    {
175
        if ($node['value'] >= 0) {
176
            $check = '$value[' . $node['value'] . ']';
177
            return $this->write(
178
                '$value = (is_array($value) || $value instanceof \\ArrayAccess)'
179
                    . ' && isset(%s) ? %s : null;',
180
                $check, $check
181
            );
182
        }
183
 
184
        $a = $this->makeVar('count');
185
        return $this
186
            ->write('if (is_array($value) || ($value instanceof \\ArrayAccess && $value instanceof \\Countable)) {')
187
                ->indent()
188
                ->write('%s = count($value) + %s;', $a, $node['value'])
189
                ->write('$value = isset($value[%s]) ? $value[%s] : null;', $a, $a)
190
                ->outdent()
191
            ->write('} else {')
192
                ->indent()
193
                ->write('$value = null;')
194
                ->outdent()
195
            ->write('}');
196
    }
197
 
198
    private function visit_literal(array $node)
199
    {
200
        return $this->write('$value = %s;', var_export($node['value'], true));
201
    }
202
 
203
    private function visit_pipe(array $node)
204
    {
205
        return $this
206
            ->dispatch($node['children'][0])
207
            ->dispatch($node['children'][1]);
208
    }
209
 
210
    private function visit_multi_select_list(array $node)
211
    {
212
        return $this->visit_multi_select_hash($node);
213
    }
214
 
215
    private function visit_multi_select_hash(array $node)
216
    {
217
        $listVal = $this->makeVar('list');
218
        $value = $this->makeVar('prev');
219
        $this->write('if ($value !== null) {')
220
            ->indent()
221
            ->write('%s = [];', $listVal)
222
            ->write('%s = $value;', $value);
223
 
224
        $first = true;
225
        foreach ($node['children'] as $child) {
226
            if (!$first) {
227
                $this->write('$value = %s;', $value);
228
            }
229
            $first = false;
230
            if ($node['type'] == 'multi_select_hash') {
231
                $this->dispatch($child['children'][0]);
232
                $key = var_export($child['value'], true);
233
                $this->write('%s[%s] = $value;', $listVal, $key);
234
            } else {
235
                $this->dispatch($child);
236
                $this->write('%s[] = $value;', $listVal);
237
            }
238
        }
239
 
240
        return $this
241
            ->write('$value = %s;', $listVal)
242
            ->outdent()
243
            ->write('}');
244
    }
245
 
246
    private function visit_function(array $node)
247
    {
248
        $value = $this->makeVar('val');
249
        $args = $this->makeVar('args');
250
        $this->write('%s = $value;', $value)
251
            ->write('%s = [];', $args);
252
 
253
        foreach ($node['children'] as $arg) {
254
            $this->dispatch($arg);
255
            $this->write('%s[] = $value;', $args)
256
                ->write('$value = %s;', $value);
257
        }
258
 
259
        return $this->write(
260
            '$value = Fd::getInstance()->__invoke("%s", %s);',
261
            $node['value'], $args
262
        );
263
    }
264
 
265
    private function visit_slice(array $node)
266
    {
267
        return $this
268
            ->write('$value = !is_string($value) && !Utils::isArray($value)')
269
            ->write('    ? null : Utils::slice($value, %s, %s, %s);',
270
                var_export($node['value'][0], true),
271
                var_export($node['value'][1], true),
272
                var_export($node['value'][2], true)
273
            );
274
    }
275
 
276
    private function visit_current(array $node)
277
    {
278
        return $this->write('// Visiting current node (no-op)');
279
    }
280
 
281
    private function visit_expref(array $node)
282
    {
283
        $child = var_export($node['children'][0], true);
284
        return $this->write('$value = function ($value) use ($interpreter) {')
285
            ->indent()
286
            ->write('return $interpreter->visit(%s, $value);', $child)
287
            ->outdent()
288
        ->write('};');
289
    }
290
 
291
    private function visit_flatten(array $node)
292
    {
293
        $this->dispatch($node['children'][0]);
294
        $merged = $this->makeVar('merged');
295
        $val = $this->makeVar('val');
296
 
297
        $this
298
            ->write('// Visiting merge node')
299
            ->write('if (!Utils::isArray($value)) {')
300
                ->indent()
301
                ->write('$value = null;')
302
                ->outdent()
303
            ->write('} else {')
304
                ->indent()
305
                ->write('%s = [];', $merged)
306
                ->write('foreach ($value as %s) {', $val)
307
                    ->indent()
308
                    ->write('if (is_array(%s) && isset(%s[0])) {', $val, $val)
309
                        ->indent()
310
                        ->write('%s = array_merge(%s, %s);', $merged, $merged, $val)
311
                        ->outdent()
312
                    ->write('} elseif (%s !== []) {', $val)
313
                        ->indent()
314
                        ->write('%s[] = %s;', $merged, $val)
315
                        ->outdent()
316
                    ->write('}')
317
                    ->outdent()
318
                ->write('}')
319
                ->write('$value = %s;', $merged)
320
                ->outdent()
321
            ->write('}');
322
 
323
        return $this;
324
    }
325
 
326
    private function visit_projection(array $node)
327
    {
328
        $val = $this->makeVar('val');
329
        $collected = $this->makeVar('collected');
330
        $this->write('// Visiting projection node')
331
            ->dispatch($node['children'][0])
332
            ->write('');
333
 
334
        if (!isset($node['from'])) {
335
            $this->write('if (!is_array($value) || !($value instanceof \stdClass)) { $value = null; }');
336
        } elseif ($node['from'] == 'object') {
337
            $this->write('if (!Utils::isObject($value)) { $value = null; }');
338
        } elseif ($node['from'] == 'array') {
339
            $this->write('if (!Utils::isArray($value)) { $value = null; }');
340
        }
341
 
342
        $this->write('if ($value !== null) {')
343
            ->indent()
344
            ->write('%s = [];', $collected)
345
            ->write('foreach ((array) $value as %s) {', $val)
346
                ->indent()
347
                ->write('$value = %s;', $val)
348
                ->dispatch($node['children'][1])
349
                ->write('if ($value !== null) {')
350
                    ->indent()
351
                    ->write('%s[] = $value;', $collected)
352
                    ->outdent()
353
                ->write('}')
354
                ->outdent()
355
            ->write('}')
356
            ->write('$value = %s;', $collected)
357
            ->outdent()
358
        ->write('}');
359
 
360
        return $this;
361
    }
362
 
363
    private function visit_condition(array $node)
364
    {
365
        $value = $this->makeVar('beforeCondition');
366
        return $this
367
            ->write('%s = $value;', $value)
368
            ->write('// Visiting condition node')
369
            ->dispatch($node['children'][0])
370
            ->write('// Checking result of condition node')
371
            ->write('if (Utils::isTruthy($value)) {')
372
                ->indent()
373
                ->write('$value = %s;', $value)
374
                ->dispatch($node['children'][1])
375
                ->outdent()
376
            ->write('} else {')
377
                ->indent()
378
                ->write('$value = null;')
379
                ->outdent()
380
            ->write('}');
381
    }
382
 
383
    private function visit_comparator(array $node)
384
    {
385
        $value = $this->makeVar('val');
386
        $a = $this->makeVar('left');
387
        $b = $this->makeVar('right');
388
 
389
        $this
390
            ->write('// Visiting comparator node')
391
            ->write('%s = $value;', $value)
392
            ->dispatch($node['children'][0])
393
            ->write('%s = $value;', $a)
394
            ->write('$value = %s;', $value)
395
            ->dispatch($node['children'][1])
396
            ->write('%s = $value;', $b);
397
 
398
        if ($node['value'] == '==') {
399
            $this->write('$value = Utils::isEqual(%s, %s);', $a, $b);
400
        } elseif ($node['value'] == '!=') {
401
            $this->write('$value = !Utils::isEqual(%s, %s);', $a, $b);
402
        } else {
403
            $this->write(
404
                '$value = (is_int(%s) || is_float(%s)) && (is_int(%s) || is_float(%s)) && %s %s %s;',
405
                $a, $a, $b, $b, $a, $node['value'], $b
406
            );
407
        }
408
 
409
        return $this;
410
    }
411
 
412
    /** @internal */
413
    public function __call($method, $args)
414
    {
415
        throw new \RuntimeException(
416
            sprintf('Invalid node encountered: %s', json_encode($args[0]))
417
        );
418
    }
419
}