Skip to content

Instantly share code, notes, and snippets.

@Koc
Created September 30, 2011 07:51
Show Gist options
  • Save Koc/1253014 to your computer and use it in GitHub Desktop.
Save Koc/1253014 to your computer and use it in GitHub Desktop.
<?php
$userVariable = array(
'max_riders_on_trip' => 50,
'has_bycicle' => 1,
'has_rollers' => 0,
'has_snowboard' => 1,
'invite_users' => 27,
);
$badgeRules = array(
1 => 'max_riders_on_trip >= 75',
2 => 'max_riders_on_trip > 49',
3 => 'max_riders_on_trip >= 50 & has_rollers = 1',
4 => 'max_riders_on_trip >= 50 & has_bycicle = 1',
5 => 'has_bycicle = 1 | has_rollers = 1 | has_snowboard = 1',
);
class BadgeRulesCalculator
{
protected $logicalTerm = array('|', '&',);
protected $comparasionTerm = array('<', '>', '<=', '>=', '<>', '=');
protected $text;
protected $variables;
protected $tokens;
const TYPE_VARIABLE = 'variable';
const TYPE_COMPARASION = 'comparation';
const TYPE_LOGICAL = 'logical';
const TYPE_BOOLEAN = 'boolean';
const TYPE_NUBMER = 'number';
public function __construct($text, $variables)
{
$this->text = $text;
$this->variables = $variables;
}
public function tokenize()
{
$lexemes = explode(' ', $this->text);
$possibleTypes = array(self::TYPE_VARIABLE, self::TYPE_NUBMER);
foreach ($lexemes as $lexeme) {
$type = $this->lexemeToType($lexeme);
if (!in_array($type, $possibleTypes)) {
throw new Exception(sprintf('Invalid token type. Expected "%s", got "%s"', implode('|', $possibleTypes), $type));
}
$this->tokens[] = array('type' => $type, 'value' => $lexeme);
switch ($type) {
case self::TYPE_VARIABLE:
$possibleTypes = array(self::TYPE_COMPARASION, self::TYPE_LOGICAL);
break;
case self::TYPE_COMPARASION:
$possibleTypes = array(self::TYPE_VARIABLE, self::TYPE_NUBMER, self::TYPE_LOGICAL);
break;
case self::TYPE_NUBMER:
$possibleTypes = array(self::TYPE_COMPARASION, self::TYPE_LOGICAL);
break;
case self::TYPE_LOGICAL:
$possibleTypes = array(self::TYPE_VARIABLE, self::TYPE_NUBMER);
break;
}
}
return $this->tokens;
}
public function getUsedVariables()
{
$usedVariables = array();
foreach ($this->tokens as $token) {
if (self::TYPE_VARIABLE == $token['type']) {
$usedVariables[] = $token['value'];
}
}
return array_unique($usedVariables);
}
public function calculate()
{
if (null === $this->tokens) {
$this->tokenize();
}
$logicalTokens = array();
for ($i = 0, $n = count($this->tokens); $i < $n; $i += 3) {
if ($this->tokens[$i]['type'] == self::TYPE_LOGICAL) {
$logicalTokens[] = $this->tokens[$i];
$i++;
}
if (!isset($this->tokens[$i + 1]) || !isset($this->tokens[$i + 2])) {
throw new Exception('Bad expr.');
}
$parsedValue = $this->testComparasion(
$this->tokenToValue($this->tokens[$i]),
$this->tokenToValue($this->tokens[$i + 2]),
$this->tokenToValue($this->tokens[$i + 1]
));
$logicalTokens[] = array('type' => self::TYPE_BOOLEAN, 'value' => $parsedValue);
}
if (!$logicalTokens) {
return false;
}
$i = 0;
$lastToken = null;
do {
if (!isset($logicalTokens[$i + 1]) || !isset($logicalTokens[$i + 2])) {
throw new Exception('Bad expr.');
}
$parsedValue = $this->testBoolean(
$this->tokenToValue($logicalTokens[$i]),
$this->tokenToValue($logicalTokens[$i + 2]),
$this->tokenToValue($logicalTokens[$i + 1]
));
unset($logicalTokens[$i], $logicalTokens[$i + 1]);
$lastToken = $logicalTokens[$i + 2] = array('type' => self::TYPE_BOOLEAN, 'value' => $parsedValue);
$i += 2;
} while (count($logicalTokens) > 1);
return $lastToken['value'];
}
protected function testComparasion($left, $right, $type)
{
switch ($type) {
case '=': return $left == $right;
case '>': return $left > $right;
case '<': return $left < $right;
case '<>': return $left != $right;
case '>=': return $left >= $right;
case '<=': return $left <= $right;
}
throw new Exception('Unknown comparation type');
}
protected function testBoolean($left, $right, $type)
{
switch ($type) {
case '&': return $left && $right;
case '|': return $left || $right;
}
throw new Exception('Unknown boolean type');
}
protected function lexemeToType($lexeme)
{
if (is_numeric($lexeme)) {
return self::TYPE_NUBMER;
}
if (in_array($lexeme, $this->comparasionTerm)) {
return self::TYPE_COMPARASION;
}
if (in_array($lexeme, $this->logicalTerm)) {
return self::TYPE_LOGICAL;
}
if (preg_match('/[a-z_]/', $lexeme)) {
return self::TYPE_VARIABLE;
}
throw new Exception('Invalid lexeme');
}
protected function tokenToValue($token)
{
switch ($token['type']) {
case self::TYPE_NUBMER:
case self::TYPE_LOGICAL:
case self::TYPE_BOOLEAN:
case self::TYPE_COMPARASION:
return $token['value'];
case self::TYPE_VARIABLE:
return isset($this->variables[$token['value']]) ? $this->variables[$token['value']] : null;
}
throw new Exception('Bad token type');
}
}
$calculator = new BadgeRulesCalculator($badgeRules[3], $userVariable);
//var_dump($parser->tokenize());
//var_dump($parser->getUsedVariables());
var_dump($calculator->calculate());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment