Issues (12)

src/ExpressionGroup.php (1 issue)

1
<?php
2
3
namespace petitparser;
4
5
/**
6
 * Models a group of operators of the same precedence.
7
 */
8
class ExpressionGroup
9
{
10
    /**
11
     * @var Parser[]
12
     */
13
    protected $_primitives = array();
14
15
    /**
16
     * @var Parser[]
17
     */
18
    protected $_prefix = array();
19
20
    /**
21
     * @var Parser[]
22
     */
23
    protected $_postfix = array();
24
25
    /**
26
     * @var Parser[]
27
     */
28
    protected $_right = array();
29
30
    /**
31
     * @var Parser[]
32
     */
33
    protected $_left = array();
34
35
    /**
36
     * Defines a new primitive or literal [parser].
37
     *
38
     * @param Parser $parser
39
     *
40
     * @return void
41
     */
42 1
    public function primitive(Parser $parser)
43
    {
44 1
        $this->_primitives[] = $parser;
45 1
    }
46
47
    /**
48
     * @param Parser $inner
49
     *
50
     * @return Parser
51
     *
52
     * @ignore
53
     */
54 1
    public function _build_primitive(Parser $inner)
55
    {
56 1
        return $this->_build_choice($this->_primitives, $inner);
57
    }
58
59
    /**
60
     * Adds a prefix operator [parser]. Evaluates the optional [action] with the
61
     * parsed `operator` and `value`.
62
     *
63
     * @param Parser   $parser
64
     * @param callable $action optional closure of the form: mixed function($operator, $value)
65
     *
66
     * @return void
67
     */
68 1
    public function prefix(Parser $parser, $action = null)
69
    {
70 1
        if ($action === null) {
71
            $action = function ($operator, $value) {
72
                return array($operator, $value);
73
            };
74
        }
75
76 1
        $this->_prefix[] = $parser->map(
77
            function ($operator) use ($action) {
78 1
                return new ExpressionResult($operator, $action);
79
            }
80 1
        );
81 1
    }
82
83
    /**
84
     * @param Parser $inner
85
     *
86
     * @return Parser
87
     *
88
     * @ignore
89
     */
90 1
    public function _build_prefix(Parser $inner)
91
    {
92 1
        if (count($this->_prefix) === 0) {
93 1
            return $inner;
94
        } else {
95 1
            $parser = new SequenceParser(array(
96 1
                $this->_build_choice($this->_prefix)->star(),
97 1
                $inner,
98 1
            ));
99
100 1
            return $parser->map(
101
                function ($tuple) {
102
                    /**
103
                     * @var ExpressionResult[] $results
104
                     */
105 1
                    $results = array_reverse($tuple[0]);
106
107 1
                    $value = $tuple[count($tuple) - 1];
108
109 1
                    foreach ($results as $result) {
110 1
                        $value = call_user_func($result->action, $result->operator, $value);
111 1
                    }
112
113 1
                    return $value;
114
                }
115 1
            );
116
        }
117
    }
118
119
    /**
120
     * Adds a postfix operator [parser]. Evaluates the optional [action] with the
121
     * parsed `value` and `operator`.
122
     *
123
     * @param Parser   $parser
124
     * @param callable $action closure of the form: mixed function($value, $operator)
125
     *
126
     * @return void
127
     */
128 1
    public function postfix(Parser $parser, $action = null)
129
    {
130 1
        if ($action == null) {
131
            $action = function ($value, $operator) {
132
                return array($value, $operator);
133
            };
134
        }
135
136 1
        $this->_postfix[] = $parser->map(
137
            function ($operator) use ($action) {
138 1
                return new ExpressionResult($operator, $action);
139
            }
140 1
        );
141 1
    }
142
143
    /**
144
     * @param Parser $inner
145
     *
146
     * @return Parser
147
     */
148 1
    public function _build_postfix(Parser $inner)
149
    {
150 1
        if (count($this->_postfix) === 0) {
151 1
            return $inner;
152
        } else {
153 1
            $parser = new SequenceParser(array(
154 1
                $inner,
155 1
                $this->_build_choice($this->_postfix)->star(),
156 1
            ));
157
158 1
            return $parser->map(
159
                function ($tuple) {
160
                    /**
161
                     * @var ExpressionResult[] $results
162
                     */
163 1
                    $results = array_reverse($tuple[count($tuple) - 1]);
164
165 1
                    $value = $tuple[0];
166
167 1
                    foreach ($results as $result) {
168 1
                        $value = call_user_func($result->action, $value, $result->operator);
169 1
                    }
170
171 1
                    return $value;
172
                }
173 1
            );
174
        }
175
    }
176
177
    /**
178
     * Adds a right-associative operator [parser]. Evaluates the optional [action] with
179
     * the parsed `left` term, `operator`, and `right` term.
180
     *
181
     * @param Parser   $parser
182
     * @param callable $action closure of the form: mixed function($left, $operator, $right)
183
     *
184
     * @return void
185
     */
186 1
    public function right(Parser $parser, $action = null)
187
    {
188 1
        if ($action === null) {
189
            $action = function ($left, $operator, $right) {
190
                return array($left, $operator, $right);
191
            };
192
        }
193
194 1
        $this->_right[] = $parser->map(
195
            function ($operator) use ($action) {
196 1
                return new ExpressionResult($operator, $action);
197
            }
198 1
        );
199 1
    }
200
201
    /**
202
     * @param Parser $inner
203
     *
204
     * @return Parser
205
     */
206 1
    public function _build_right(Parser $inner)
207
    {
208 1
        if (count($this->_right) === 0) {
209 1
            return $inner;
210
        } else {
211
            return $inner
212 1
                ->separatedBy($this->_build_choice($this->_right))
213 1
                ->map(
214
                    function ($sequence) {
215
                        /**
216
                         * @var ExpressionResult $result
217
                         * TODO type hints don't seem to work for the $sequence argument
218
                         */
219
220 1
                        $result = $sequence[count($sequence) - 1];
221
222 1
                        for ($i = count($sequence) - 2; $i > 0; $i -= 2) {
223 1
                            $result = call_user_func($sequence[$i]->action, $sequence[$i - 1], $sequence[$i]->operator, $result);
224 1
                        }
225
226 1
                        return $result;
227
                    }
228 1
                );
229
        }
230
    }
231
232
    /**
233
     * Adds a left-associative operator [parser]. Evaluates the optional [action] with
234
     * the parsed `left` term, `operator`, and `right` term.
235
     *
236
     * @param Parser   $parser
237
     * @param callable $action closure of the form: mixed function($left, $operator, $right)
238
     *
239
     * @return void
240
     */
241 1
    public function left(Parser $parser, $action = null)
242
    {
243 1
        if ($action === null) {
244
            $action = function ($left, $operator, $right) {
245
                return array($left, $operator, $right);
246
            };
247
        }
248
249 1
        $this->_left[] = $parser->map(
250
            function ($operator) use ($action) {
251 1
                return new ExpressionResult($operator, $action);
252
            }
253 1
        );
254 1
    }
255
256
    /**
257
     * @param Parser $inner
258
     *
259
     * @return Parser
260
     */
261 1
    public function  _build_left(Parser $inner)
262
    {
263 1
        if (count($this->_left) === 0) {
264 1
            return $inner;
265
        } else {
266
            return $inner
267 1
                ->separatedBy($this->_build_choice($this->_left))
268 1
                ->map(
269 1
                    function ($sequence) {
270
                        // TODO fix type-hints
271 1
                        $result = $sequence[0];
272
273 1
                        for ($i = 1; $i < count($sequence); $i += 2) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
274 1
                            $result = call_user_func(
275 1
                                $sequence[$i]->action,
276 1
                                $result,
277 1
                                $sequence[$i]->operator,
278 1
                                $sequence[$i + 1]
279 1
                            );
280 1
                        }
281
282 1
                        return $result;
283
                    }
284 1
                );
285
        }
286
    }
287
288
    /**
289
     * helper to build an optimal choice parser
290
     *
291
     * @param Parser[] $parsers
292
     * @param Parser   $otherwise
293
     *
294
     * @return Parser
295
     */
296 1
    public function _build_choice($parsers, Parser $otherwise = null)
297
    {
298 1
        if (count($parsers) === 0) {
299 1
            return $otherwise;
300 1
        } elseif (count($parsers) === 1) {
301 1
            return $parsers[0];
302
        } else {
303 1
            return new ChoiceParser($parsers);
304
        }
305
    }
306
307
    /**
308
     * helper to build the group of parsers
309
     *
310
     * @param Parser $inner
311
     *
312
     * @return Parser
313
     *
314
     * @ignore
315
     */
316 1
    public function _build(Parser $inner)
317
    {
318 1
        return $this->_build_left(
319 1
            $this->_build_right(
320 1
                $this->_build_postfix(
321 1
                    $this->_build_prefix(
322 1
                        $this->_build_primitive($inner)
323 1
                    )
324 1
                )
325 1
            )
326 1
        );
327
    }
328
}
329