public function testParserWithStringConstants() { $context = new Context(); $const = 'string constant'; $context->def('const', $const, 'string'); $actual = Parser::parse('const', $context); $this->assertEquals($const, $actual); }
/** * @param $equation * @param array $constants * @param $expected * * @dataProvider equationAndConstantsProvider */ public function testParserWithConstants($equation, array $constants, $expected) { $context = new Context(); foreach ($constants as $key => $val) { $context->def($key, $val); } $actual = Parser::parse($equation, $context); $this->assertEquals($expected, $actual); }
public function testFunctionDefinitionWithOptionalParams() { $context = new Context(); $context->def('func', function ($param1, $param2 = 100) { return $param1 + $param2; }); $actual = $context->fn('func', array(3)); $this->assertEquals(103.0, $actual); $actual = $context->fn('func', array(3, 200)); $this->assertEquals(203.0, $actual); }
public function testWrapPHPFunction() { $context = new Context(); $context->def('abs'); $equation = 'abs(100)'; $actual = Parser::parse($equation, $context); $expected = 100; $this->assertEquals($expected, $actual); $equation = 'abs(-100)'; $actual = Parser::parse($equation, $context); $expected = 100; $this->assertEquals($expected, $actual); }
<?php // Composer autoloading if (file_exists('vendor/autoload.php')) { $loader = (include 'vendor/autoload.php'); } use RR\Shunt\Parser; use RR\Shunt\Context; // einfach $trm = '3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3'; print Parser::parse($trm); // 3.0001220703125 print "\n"; // mit eigenen konstanten und funktionen $ctx = new Context(); $ctx->def('abs'); // wrapper $ctx->def('foo', 5); $ctx->def('bar', function ($a, $b) { return $a * $b; }); $trm = '3 + bar(4, 2) / (abs(-1) - foo) ^ 2 ^ 3'; print Parser::parse($trm, $ctx); // 3.0001220703125
/** * @expectedException \RR\Shunt\Exception\RuntimeError */ public function testCallNotsetConstantCausesException() { $context = new Context(); $context->cs('notdefinedfunction'); }
/** * @expectedException RR\Shunt\Exception\ParseError */ public function testParserExceptionSurplusClosingBracket() { $context = new Context(); $context->def('pi'); $equation = 'pi())'; $actual = Parser::parse($equation, $context); }
public function reduce(Context $ctx) { $this->stack = array(); $len = 0; // While there are input tokens left // Read the next token from input. while ($t = array_shift($this->queue)) { switch ($t->type) { case Token::T_NUMBER: case Token::T_IDENT: // wert einer konstanten ermitteln if ($t->type === Token::T_IDENT) { $t = new Token(Token::T_NUMBER, $ctx->cs($t->value)); } // If the token is a value or identifier // Push it onto the stack. $this->stack[] = $t; ++$len; break; case Token::T_PLUS: case Token::T_MINUS: case Token::T_UNARY_PLUS: case Token::T_UNARY_MINUS: case Token::T_TIMES: case Token::T_DIV: case Token::T_MOD: case Token::T_POW: case Token::T_NOT: // It is known a priori that the operator takes n arguments. $na = $this->argc($t); // If there are fewer than n values on the stack if ($len < $na) { throw new RuntimeError('laufzeit fehler: zu wenig paramter für operator "' . $t->value . '" (' . $na . ' -> ' . $len . ')'); } $rhs = array_pop($this->stack); $lhs = null; if ($na > 1) { $lhs = array_pop($this->stack); } // if ($lhs) print "{$lhs->value} {$t->value} {$rhs->value}\n"; // else print "{$t->value} {$rhs->value}\n"; $len -= $na - 1; // Push the returned results, if any, back onto the stack. $this->stack[] = new Token(Token::T_NUMBER, $this->op($t->type, $lhs, $rhs)); break; case Token::T_FUNCTION: // function $argc = $t->argc; $argv = array(); $len -= $argc - 1; for (; $argc > 0; --$argc) { array_unshift($argv, array_pop($this->stack)->value); } // Push the returned results, if any, back onto the stack. $this->stack[] = new Token(Token::T_NUMBER, $ctx->fn($t->value, $argv)); break; default: throw new RuntimeError('laufzeit fehler: unerwarteter token `' . $t->value . '`'); } } // If there is only one value in the stack // That value is the result of the calculation. if (count($this->stack) === 1) { return array_pop($this->stack)->value; } // If there are more values in the stack // (Error) The user input has too many values. throw new RuntimeError('laufzeit fehler: zu viele werte im stack'); }
protected function op($op, $lhs, $rhs, Context $ctx) { // If there is a custom operator handler function defined in the context, call it instead if ($ctx->hasCustomOperatorHandler($op)) { $lhsValue = is_object($lhs) ? $lhs->value : null; $rhsValue = is_object($rhs) ? $rhs->value : null; return $ctx->execCustomOperatorHandler($op, $lhsValue, $rhsValue); } if ($lhs !== null) { $lhs = $lhs->value; $rhs = $rhs->value; switch ($op) { case Token::T_PLUS: return $lhs + $rhs; case Token::T_MINUS: return $lhs - $rhs; case Token::T_TIMES: return $lhs * $rhs; case Token::T_DIV: if ($rhs === 0.0) { throw new RuntimeError('run-time error: division by zero'); } return $lhs / $rhs; case Token::T_MOD: if ($rhs === 0.0) { throw new RuntimeError('run-time error: rest-division by zero'); } return (double) $lhs % $rhs; case Token::T_POW: return (double) pow($lhs, $rhs); } // throw? return 0; } switch ($op) { case Token::T_NOT: return is_null($rhs->value) ? null : (double) (!$rhs->value); case Token::T_UNARY_MINUS: return is_null($rhs->value) ? null : -$rhs->value; case Token::T_UNARY_PLUS: return is_null($rhs->value) ? null : +$rhs->value; } }
// Composer autoloading if (file_exists('vendor/autoload.php')) { $loader = (include 'vendor/autoload.php'); } use RR\Shunt\Parser; use RR\Shunt\Context; // einfach $trm = '3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3'; print Parser::parse($trm) . "\n"; // 3.0001220703125 // mit eigenen konstanten und funktionen $ctx = new Context(); $ctx->def('abs'); // wrapper $ctx->def('foo', 5); $ctx->def('bar', function ($a, $b) { return $a * $b; }); $trm = '3 + bar(4, 2) / (abs(-1) - foo) ^ 2 ^ 3'; print Parser::parse($trm, $ctx) . "\n"; // 3.0001220703125 // mit string konstanten $ctx = new Context(); $ctx->def('groupA', 'A', 'string'); // string constant $ctx->def('isgroupA', function ($g) { return $g == 'A' ? 1 : 0; }); $trm = 'isgroupA(groupA)'; print Parser::parse($trm, $ctx) . "\n"; // 1
protected function op($op, $lhs, $rhs, Context $ctx) { // If there is a custom operator handler function defined in the context, call it instead if ($ctx->hasCustomOperatorHandler($op)) { $lhsValue = is_object($lhs) ? $lhs->value : null; $rhsValue = is_object($rhs) ? $rhs->value : null; return $ctx->execCustomOperatorHandler($op, $lhsValue, $rhsValue); } if ($lhs !== null) { $lhs = $lhs->value; $rhs = $rhs->value; switch ($op) { case Token::T_GREATER_EQUAL: case Token::T_LESS_EQUAL: case Token::T_GREATER: case Token::T_LESS: case Token::T_PLUS: case Token::T_MINUS: case Token::T_TIMES: case Token::T_DIV: case Token::T_MOD: case Token::T_POW: if (is_bool($lhs) && is_bool($rhs)) { throw new RuntimeError('run-time error: trying to do a number only operation over two booleans'); } else { if (is_bool($lhs) || is_bool($rhs)) { throw new RuntimeError('run-time error: trying to calculate a value out of a decimal and a boolean'); } } break; case Token::T_EQUAL: case Token::T_NOT_EQUAL: if (is_bool($lhs) ^ is_bool($rhs)) { throw new RuntimeError('run-time error: trying to calculate a value out of a decimal and a boolean'); } break; case Token::T_AND: case Token::T_OR: case Token::T_XOR: if (!is_bool($lhs) || !is_bool($rhs)) { throw new RuntimeError('run-time error: trying to do a boolean only operation over two numbers'); } break; } switch ($op) { case Token::T_AND: return $lhs && $rhs; case Token::T_OR: return $lhs || $rhs; case Token::T_XOR: return $lhs ^ $rhs; case Token::T_GREATER_EQUAL: return $lhs >= $rhs; case Token::T_LESS_EQUAL: return $lhs <= $rhs; case Token::T_GREATER: return $lhs > $rhs; case Token::T_LESS: return $lhs < $rhs; case Token::T_EQUAL: return $lhs == $rhs; case Token::T_NOT_EQUAL: return $lhs != $rhs; case Token::T_PLUS: return $lhs + $rhs; case Token::T_MINUS: return $lhs - $rhs; case Token::T_TIMES: return $lhs * $rhs; case Token::T_DIV: if ($rhs === 0.0) { throw new RuntimeError('run-time error: division by zero'); } return $lhs / $rhs; case Token::T_MOD: if ($rhs === 0.0) { throw new RuntimeError('run-time error: rest-division by zero'); } return (double) $lhs % $rhs; case Token::T_POW: return (double) pow($lhs, $rhs); } // throw? return 0; } switch ($op) { case Token::T_NOT: return is_null($rhs->value) ? null : (double) (!$rhs->value); case Token::T_UNARY_MINUS: return is_null($rhs->value) ? null : -$rhs->value; case Token::T_UNARY_PLUS: return is_null($rhs->value) ? null : +$rhs->value; } }