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