1<?php
2
3/**
4 * Hoa
5 *
6 *
7 * @license
8 *
9 * New BSD License
10 *
11 * Copyright © 2007-2017, Hoa community. All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *     * Redistributions of source code must retain the above copyright
16 *       notice, this list of conditions and the following disclaimer.
17 *     * Redistributions in binary form must reproduce the above copyright
18 *       notice, this list of conditions and the following disclaimer in the
19 *       documentation and/or other materials provided with the distribution.
20 *     * Neither the name of the Hoa nor the names of its contributors may be
21 *       used to endorse or promote products derived from this software without
22 *       specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37namespace Hoa\Math\Visitor;
38
39use Hoa\Math;
40use Hoa\Visitor;
41
42/**
43 * Class \Hoa\Math\Visitor\Arithmetic.
44 *
45 * Evaluate arithmetical expressions.
46 *
47 * @copyright  Copyright © 2007-2017 Hoa community
48 *             Ivan Enderlin, Cédric Dugat.
49 * @license    New BSD License
50 */
51class Arithmetic implements Visitor\Visit
52{
53    /**
54     * Visitor context containing the list of supported functions, constants and variables
55     *
56     * @var \Hoa\Math\Context
57     */
58    protected $_context = null;
59
60    /**
61     * Initializes context.
62     *
63     */
64    public function __construct()
65    {
66        $this->initializeContext();
67
68        return;
69    }
70
71    /**
72     * Set visitor's context
73     *
74     * @param   \Hoa\Math\Context $context
75     * @return  \Hoa\Math\Context
76     */
77    public function setContext(Math\Context $context)
78    {
79        $old = $this->_context;
80
81        $this->_context = $context;
82
83        return $old;
84    }
85
86    /**
87     * Get visitor's context
88     *
89     * @return  \Hoa\Math\Context
90     */
91    public function getContext()
92    {
93        return $this->_context;
94    }
95
96    /**
97     * Visit an element.
98     *
99     * @param   \Hoa\Visitor\Element  $element    Element to visit.
100     * @param   mixed                 &$handle    Handle (reference).
101     * @param   mixed                 $eldnah     Handle (not reference).
102     * @return  float
103     */
104    public function visit(
105        Visitor\Element $element,
106        &$handle = null,
107        $eldnah  = null
108    ) {
109        $type     = $element->getId();
110        $children = $element->getChildren();
111
112        if (null === $handle) {
113            $handle = function ($x) {
114                return $x;
115            };
116        }
117
118        $acc = &$handle;
119
120        switch ($type) {
121            case '#function':
122                $name      = array_shift($children)->accept($this, $_, $eldnah);
123                $function  = $this->getFunction($name);
124                $arguments = [];
125
126                foreach ($children as $child) {
127                    $child->accept($this, $_, $eldnah);
128                    $arguments[] = $_();
129                    unset($_);
130                }
131
132                $acc = function () use ($function, $arguments, $acc) {
133                    return $acc($function->distributeArguments($arguments));
134                };
135
136                break;
137
138            case '#negative':
139                $children[0]->accept($this, $a, $eldnah);
140
141                $acc = function () use ($a, $acc) {
142                    return $acc(-$a());
143                };
144
145                break;
146
147            case '#addition':
148                $children[0]->accept($this, $a, $eldnah);
149
150                $acc = function ($b) use ($a, $acc) {
151                    return $acc($a() + $b);
152                };
153
154                $children[1]->accept($this, $acc, $eldnah);
155
156                break;
157
158            case '#substraction':
159                $children[0]->accept($this, $a, $eldnah);
160
161                $acc = function ($b) use ($a, $acc) {
162                    return $acc($a()) - $b;
163                };
164
165                $children[1]->accept($this, $acc, $eldnah);
166
167                break;
168
169            case '#multiplication':
170                $children[0]->accept($this, $a, $eldnah);
171
172                $acc = function ($b) use ($a, $acc) {
173                    return $acc($a() * $b);
174                };
175
176                $children[1]->accept($this, $acc, $eldnah);
177
178                break;
179
180            case '#division':
181                $children[0]->accept($this, $a, $eldnah);
182                $parent = $element->getParent();
183
184                if (null  === $parent ||
185                    $type === $parent->getId()) {
186                    $acc = function ($b) use ($a, $acc) {
187                        if (0.0 === $b) {
188                            throw new \RuntimeException(
189                                'Division by zero is not possible.'
190                            );
191                        }
192
193                        return $acc($a()) / $b;
194                    };
195                } else {
196                    if ('#fakegroup' !== $parent->getId()) {
197                        $classname = get_class($element);
198                        $group     = new $classname(
199                            '#fakegroup',
200                            null,
201                            [$element],
202                            $parent
203                        );
204                        $element->setParent($group);
205
206                        $this->visit($group, $acc, $eldnah);
207
208                        break;
209                    } else {
210                        $acc = function ($b) use ($a, $acc) {
211                            if (0.0 === $b) {
212                                throw new \RuntimeException(
213                                    'Division by zero is not possible.'
214                                );
215                            }
216
217                            return $acc($a() / $b);
218                        };
219                    }
220                }
221
222                $children[1]->accept($this, $acc, $eldnah);
223
224                break;
225
226            case '#fakegroup':
227            case '#group':
228                $children[0]->accept($this, $a, $eldnah);
229
230                $acc = function () use ($a, $acc) {
231                    return $acc($a());
232                };
233
234                break;
235
236            case '#variable':
237                $out = $this->getVariable($children[0]->getValueValue());
238
239                $acc = function () use ($out, $acc) {
240                    return $acc($out);
241                };
242
243                break;
244
245            case 'token':
246                $value = $element->getValueValue();
247                $out   = null;
248
249                if ('constant' === $element->getValueToken()) {
250                    if (defined($value)) {
251                        $out = constant($value);
252                    } else {
253                        $out = $this->getConstant($value);
254                    }
255                } elseif ('id' === $element->getValueToken()) {
256                    return $value;
257                } else {
258                    $out = (float) $value;
259                }
260
261                $acc = function () use ($out, $acc) {
262                    return $acc($out);
263                };
264
265                break;
266        }
267
268        if (null === $element->getParent()) {
269            return $acc();
270        }
271    }
272
273    /**
274     * Get functions.
275     *
276     * @return  \ArrayObject
277     */
278    public function getFunctions()
279    {
280        return $this->_context->getFunctions();
281    }
282
283    /**
284     * Get a function.
285     *
286     * @param   string  $name    Function name.
287     * @return  \Hoa\Consistency\Xcallable
288     * @throws  \Hoa\Math\Exception\UnknownFunction
289     */
290    public function getFunction($name)
291    {
292        return $this->_context->getFunction($name);
293    }
294
295    /**
296     * Get constants.
297     *
298     * @return  \ArrayObject
299     */
300    public function getConstants()
301    {
302        return $this->_context->getConstants();
303    }
304
305    /**
306     * Get a constant.
307     *
308     * @param   string  $name    Constant name.
309     * @return  mixed
310     * @throws  \Hoa\Math\Exception\UnknownFunction
311     */
312    public function getConstant($name)
313    {
314        return $this->_context->getConstant($name);
315    }
316
317    /**
318     * Get variables.
319     *
320     * @return \ArrayObject
321     */
322    public function getVariables()
323    {
324        return $this->_context->getVariables();
325    }
326
327    /**
328     * Get a variable.
329     *
330     * @param   string  $name    Variable name.
331     * @return  callable
332     * @throws  \Hoa\Math\Exception\UnknownVariable
333     */
334    public function getVariable($name)
335    {
336        return $this->_context->getVariable($name);
337    }
338
339    protected function initializeContext()
340    {
341        if (null === $this->_context) {
342            $this->_context = new Math\Context();
343        }
344
345        return;
346    }
347
348    /**
349     * Add a function.
350     *
351     * @param   string  $name        Function name.
352     * @param   mixed   $callable    Callable.
353     * @return  void
354     */
355    public function addFunction($name, $callable = null)
356    {
357        return $this->_context->addFunction($name, $callable);
358    }
359
360    /**
361     * Add a constant.
362     *
363     * @param   string  $name     Constant name.
364     * @param   mixed   $value    Value.
365     * @return  void
366     */
367    public function addConstant($name, $value)
368    {
369        return $this->_context->addConstant($name, $value);
370    }
371
372    /**
373     * Add a variable.
374     *
375     * @param   string    $name        Variable name.
376     * @param   callable  $callable    Callable.
377     * @return  void
378     */
379    public function addVariable($name, callable $callable)
380    {
381        return $this->_context->addVariable($name, $callable);
382    }
383}
384