You are here

class SassScriptParser in Sassy 7.3

Same name and namespace in other branches
  1. 7 phamlp/sass/script/SassScriptParser.php \SassScriptParser

SassScriptParser class. Parses SassScript. SassScript is lexed into {@link http://en.wikipedia.org/wiki/Reverse_Polish_notation Reverse Polish notation} by the SassScriptLexer and the calculated result returned. @package PHamlP @subpackage Sass.script

Hierarchy

Expanded class hierarchy of SassScriptParser

File

phpsass/script/SassScriptParser.php, line 22

View source
class SassScriptParser {
  const MATCH_INTERPOLATION = '/(?<!\\\\)#\\{(.*?)\\}/';
  const DEFAULT_ENV = 0;
  const CSS_RULE = 1;
  const CSS_PROPERTY = 2;

  /**
   * @var SassContext Used for error reporting
   */
  public static $context;

  /**
   * @var SassScriptLexer the lexer object
   */
  public $lexer;

  /**
   * Hold a copy of a parser available to the general public.
   */
  public static $instance;

  /**
   * SassScriptParser constructor.
   * @return SassScriptParser
   */
  public function __construct() {
    $this->lexer = new SassScriptLexer($this);
    self::$instance = $this;
  }

  /**
   * Replace interpolated SassScript contained in '#{}' with the parsed value.
   * @param string the text to interpolate
   * @param SassContext the context in which the string is interpolated
   * @return string the interpolated text
   */
  public function interpolate($string, $context) {
    for ($i = 0, $n = preg_match_all(self::MATCH_INTERPOLATION, $string, $matches); $i < $n; $i++) {
      $var = $this
        ->evaluate($matches[1][$i], $context)
        ->toString();
      if (preg_match('/^unquote\\((["\'])(.*)\\1\\)$/', $var, $match)) {
        $val = $match[2];
      }
      else {
        if ($var == '""') {
          $val = "";
        }
        else {
          if (preg_match('/^(["\'])(.*)\\1$/', $var, $match)) {
            $val = $match[2];
          }
          else {
            $val = $var;
          }
        }
      }
      $matches[1][$i] = $val;
    }
    return str_replace($matches[0], $matches[1], $string);
  }

  /**
   * Evaluate a SassScript.
   * @param string expression to parse
   * @param SassContext the context in which the expression is evaluated
   * @param  integer the environment in which the expression is evaluated
   * @return SassLiteral parsed value
   */
  public function evaluate($expression, $context, $environment = self::DEFAULT_ENV) {
    self::$context = $context;
    $operands = array();
    $tokens = $this
      ->parse($expression, $context, $environment);
    while (count($tokens)) {
      $token = array_shift($tokens);
      if ($token instanceof SassScriptFunction) {
        array_push($operands, $token
          ->perform());
      }
      elseif ($token instanceof SassLiteral) {
        if ($token instanceof SassString) {
          $token = new SassString($this
            ->interpolate($token
            ->toString(), self::$context));
        }
        array_push($operands, $token);
      }
      else {
        $args = array();
        for ($i = 0, $c = $token->operandCount; $i < $c; $i++) {
          $args[] = array_pop($operands);
        }
        array_push($operands, $token
          ->perform($args));
      }
    }
    return array_shift($operands);
  }

  /**
   * Parse SassScript to a set of tokens in RPN
   * using the Shunting Yard Algorithm.
   * @param string expression to parse
   * @param SassContext the context in which the expression is parsed
   * @param  integer the environment in which the expression is parsed
   * @return array tokens in RPN
   */
  public function parse($expression, $context, $environment = self::DEFAULT_ENV) {
    $outputQueue = array();
    $operatorStack = array();
    $parenthesis = 0;
    $tokens = $this->lexer
      ->lex($expression, $context);
    foreach ($tokens as $i => $token) {

      // If two literals/expessions are seperated by whitespace use the concat operator
      if (empty($token)) {
        if (isset($tokens[$i + 1])) {
          if ($i > 0 && (!$tokens[$i - 1] instanceof SassScriptOperation || $tokens[$i - 1]->operator === SassScriptOperation::$operators[')'][0]) && (!$tokens[$i + 1] instanceof SassScriptOperation || $tokens[$i + 1]->operator === SassScriptOperation::$operators['('][0])) {
            $token = new SassScriptOperation(SassScriptOperation::$defaultOperator, $context);
          }
          else {
            continue;
          }
        }
      }
      elseif ($token instanceof SassScriptVariable) {
        $token = $token
          ->evaluate($context);
        $environment = self::DEFAULT_ENV;
      }

      // If the token is a number or function add it to the output queue.
      if ($token instanceof SassLiteral || $token instanceof SassScriptFunction) {
        if ($environment === self::CSS_PROPERTY && $token instanceof SassNumber && !$parenthesis) {
          $token->inExpression = false;
        }
        array_push($outputQueue, $token);
      }
      elseif ($token instanceof SassScriptOperation) {

        // If the token is a left parenthesis push it onto the stack.
        if ($token->operator == SassScriptOperation::$operators['('][0]) {
          array_push($operatorStack, $token);
          $parenthesis++;
        }
        elseif ($token->operator == SassScriptOperation::$operators[')'][0]) {
          $parenthesis--;
          while ($c = count($operatorStack)) {

            // If the token at the top of the stack is a left parenthesis
            if ($operatorStack[$c - 1]->operator == SassScriptOperation::$operators['('][0]) {

              // Pop the left parenthesis from the stack, but not onto the output queue.
              array_pop($operatorStack);
              break;
            }

            // else pop the operator off the stack onto the output queue.
            array_push($outputQueue, array_pop($operatorStack));
          }

          // If the stack runs out without finding a left parenthesis
          // there are mismatched parentheses.
          if ($c == 0) {
            throw new SassScriptParserException('Unmatched parentheses', $context->node);
          }
        }
        else {

          // while there is an operator, o2, at the top of the stack
          while ($c = count($operatorStack)) {
            $operation = $operatorStack[$c - 1];

            // if o2 is left parenthesis, or
            // the o1 has left associativty and greater precedence than o2, or
            // the o1 has right associativity and lower or equal precedence than o2
            if ($operation->operator == SassScriptOperation::$operators['('][0] || $token->associativity == 'l' && $token->precedence > $operation->precedence || $token->associativity == 'r' && $token->precedence <= $operation->precedence) {
              break;

              // stop checking operators
            }

            //pop o2 off the stack and onto the output queue
            array_push($outputQueue, array_pop($operatorStack));
          }

          // push o1 onto the stack
          array_push($operatorStack, $token);
        }
      }
    }

    // When there are no more tokens
    while ($c = count($operatorStack)) {

      // While there are operators on the stack:
      if ($operatorStack[$c - 1]->operator !== SassScriptOperation::$operators['('][0]) {
        array_push($outputQueue, array_pop($operatorStack));
      }
      else {
        throw new SassScriptParserException('Unmatched parentheses', $context->node);
      }
    }
    return $outputQueue;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SassScriptParser::$context public static property
SassScriptParser::$instance public static property Hold a copy of a parser available to the general public.
SassScriptParser::$lexer public property
SassScriptParser::CSS_PROPERTY constant
SassScriptParser::CSS_RULE constant
SassScriptParser::DEFAULT_ENV constant
SassScriptParser::evaluate public function Evaluate a SassScript.
SassScriptParser::interpolate public function Replace interpolated SassScript contained in '#{}' with the parsed value.
SassScriptParser::MATCH_INTERPOLATION constant
SassScriptParser::parse public function Parse SassScript to a set of tokens in RPN using the Shunting Yard Algorithm.
SassScriptParser::__construct public function SassScriptParser constructor.