Skip to content

Instantly share code, notes, and snippets.

@igorw
Last active December 16, 2021 20:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save igorw/6902824 to your computer and use it in GitHub Desktop.
Save igorw/6902824 to your computer and use it in GitHub Desktop.
Shunting-yard infix to postfix conversion.
<?php
if ($argc === 1) {
echo "Usage: php calc.php EXPR\n";
exit(1);
}
$input = $argv[1];
$operators = [
'+' => ['precedence' => 0, 'associativity' => 'left'],
'-' => ['precedence' => 0, 'associativity' => 'left'],
'*' => ['precedence' => 1, 'associativity' => 'left'],
'/' => ['precedence' => 1, 'associativity' => 'left'],
'%' => ['precedence' => 1, 'associativity' => 'left'],
'^' => ['precedence' => 2, 'associativity' => 'right'],
];
$tokens = tokenize($input);
$rpn = shunting_yard($tokens, $operators);
$result = execute($rpn);
echo "$result\n";
function tokenize($input)
{
return array_map('trim', explode(' ', preg_replace('#[ ]+#', ' ', $input)));
}
function shunting_yard(array $tokens, array $operators)
{
$stack = new \SplStack();
$output = new \SplQueue();
foreach ($tokens as $token) {
if (is_numeric($token)) {
$output->enqueue($token);
} elseif (isset($operators[$token])) {
$o1 = $token;
while (has_operator($stack, $operators) && ($o2 = $stack->top()) && has_lower_precedence($o1, $o2, $operators)) {
$output->enqueue($stack->pop());
}
$stack->push($o1);
} elseif ('(' === $token) {
$stack->push($token);
} elseif (')' === $token) {
while (count($stack) > 0 && '(' !== $stack->top()) {
$output->enqueue($stack->pop());
}
if (count($stack) === 0) {
throw new \InvalidArgumentException(sprintf('Mismatched parenthesis in input: %s', json_encode($tokens)));
}
// pop off '('
$stack->pop();
} else {
throw new \InvalidArgumentException(sprintf('Invalid token: %s', $token));
}
}
while (has_operator($stack, $operators)) {
$output->enqueue($stack->pop());
}
if (count($stack) > 0) {
throw new \InvalidArgumentException(sprintf('Mismatched parenthesis or misplaced number in input: %s', json_encode($tokens)));
}
return iterator_to_array($output);
}
function has_operator(\SplStack $stack, array $operators)
{
return count($stack) > 0 && ($top = $stack->top()) && isset($operators[$top]);
}
function has_lower_precedence($o1, $o2, array $operators)
{
$op1 = $operators[$o1];
$op2 = $operators[$o2];
return ('left' === $op1['associativity'] && $op1['precedence'] === $op2['precedence']) || $op1['precedence'] < $op2['precedence'];
}
function execute(array $ops)
{
$stack = new \SplStack();
foreach ($ops as $op) {
if (is_numeric($op)) {
$stack->push((float) $op);
continue;
}
switch ($op) {
case '+':
$stack->push($stack->pop() + $stack->pop());
break;
case '-':
$n = $stack->pop();
$stack->push($stack->pop() - $n);
break;
case '*':
$stack->push($stack->pop() * $stack->pop());
break;
case '/':
$n = $stack->pop();
$stack->push($stack->pop() / $n);
break;
case '%':
$n = $stack->pop();
$stack->push($stack->pop() % $n);
break;
case '^':
$n = $stack->pop();
$stack->push(pow($stack->pop(), $n));
break;
default:
throw new \InvalidArgumentException(sprintf('Invalid operation: %s', $op));
break;
}
}
return $stack->top();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment