isValid = true; $this->error = ''; $this->constants = []; $this->pos = 0; $this->formula = $formula; $state = self::STATE_AFTER_OPEN_BRACE; $afterSpace = false; $level = 0; while (isset($this->formula[$this->pos])) { if ($this->formula[$this->pos] === ' ') { $afterSpace = true; $this->pos++; continue; } switch ($state) { case self::STATE_AFTER_OPEN_BRACE: switch ($this->formula[$this->pos]) { case '(': $state = self::STATE_AFTER_OPEN_BRACE; $level++; break; default: if ($this->parseConstant()) { $state = self::STATE_AFTER_CONSTANT; } elseif ($this->parseNotOperator()) { $state = self::STATE_AFTER_NOT_OPERATOR; } else { break 3; } } break; case self::STATE_AFTER_OPERATOR: switch ($this->formula[$this->pos]) { case '(': $state = self::STATE_AFTER_OPEN_BRACE; $level++; break; default: if (!$afterSpace) { break 3; } if ($this->parseConstant()) { $state = self::STATE_AFTER_CONSTANT; } elseif ($this->parseNotOperator()) { $state = self::STATE_AFTER_NOT_OPERATOR; } else { break 3; } } break; case self::STATE_AFTER_NOT_OPERATOR: switch ($this->formula[$this->pos]) { case '(': $state = self::STATE_AFTER_OPEN_BRACE; $level++; break; default: if (!$afterSpace) { break 3; } if ($this->parseConstant()) { $state = self::STATE_AFTER_CONSTANT; } else { break 3; } } break; case self::STATE_AFTER_CLOSE_BRACE: switch ($this->formula[$this->pos]) { case ')': if ($level == 0) { break 3; } $level--; break; default: if ($this->parseOperator()) { $state = self::STATE_AFTER_OPERATOR; } else { break 3; } } break; case self::STATE_AFTER_CONSTANT: switch ($this->formula[$this->pos]) { case ')': $state = self::STATE_AFTER_CLOSE_BRACE; if ($level == 0) { break 3; } $level--; break; default: if (!$afterSpace) { break 3; } if ($this->parseOperator()) { $state = self::STATE_AFTER_OPERATOR; } else { break 3; } } break; } $afterSpace = false; $this->pos++; } if ($this->pos == 0) { $this->error = _('expression is empty'); $this->isValid = false; } if ($level != 0 || isset($this->formula[$this->pos]) || $state == self::STATE_AFTER_OPERATOR) { $this->error = _s('incorrect syntax near "%1$s"', substr($this->formula, $this->pos == 0 ? 0 : $this->pos - 1) ); $this->isValid = false; } return $this->isValid; } /** * Parses a constant and advances the position to its last character. * * @return bool */ protected function parseConstant() { $start = $this->pos; while (isset($this->formula[$this->pos]) && $this->isConstantChar($this->formula[$this->pos])) { $this->pos++; } // empty constant if ($start == $this->pos) { return false; } $constant = substr($this->formula, $start, $this->pos - $start); $this->constants[] = [ 'value' => $constant, 'pos' => $start ]; $this->pos--; return true; } /** * Parses a keyword and advances the position to its last character. * * @return bool */ protected function parseNotOperator() { if (substr($this->formula, $this->pos, 3) !== 'not') { return false; } $this->pos += 2; return true; } /** * Parses an operator and advances the position to its last character. * * @return bool */ protected function parseOperator() { $start = $this->pos; while (isset($this->formula[$this->pos]) && $this->isOperatorChar($this->formula[$this->pos])) { $this->pos++; } // empty operator if ($start == $this->pos) { return false; } $operator = substr($this->formula, $start, $this->pos - $start); // check if this is a valid operator if (!in_array($operator, $this->allowedOperators)) { $this->pos = $start; return false; } $this->pos--; return true; } /** * Returns true if the given character is a valid constant character. * * @param string $c * * @return bool */ protected function isConstantChar($c) { return ($c >= 'A' && $c <= 'Z'); } /** * Returns true if the given character is a valid operator character. * * @param string $c * * @return bool */ protected function isOperatorChar($c) { return ($c >= 'a' && $c <= 'z'); } }