Source for file parser.php

Documentation is available at parser.php

  1. <?php
  2. /**
  3.  * @package     FrameworkOnFramework
  4.  * @subpackage  less
  5.  * @copyright   Copyright (C) 2010 - 2012 Akeeba Ltd. All rights reserved.
  6.  * @license     GNU General Public License version 2 or later; see LICENSE.txt
  7.  */
  8. // Protect from unauthorized access
  9. defined('_JEXEC'or die;
  10.  
  11. /**
  12.  * This class is taken verbatim from:
  13.  *
  14.  * lessphp v0.3.8
  15.  * http://leafo.net/lessphp
  16.  *
  17.  * LESS css compiler, adapted from http://lesscss.org
  18.  *
  19.  * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
  20.  * Licensed under MIT or GPLv3, see LICENSE
  21.  *
  22.  * Responsible for taking a string of LESS code and converting it into a syntax tree
  23.  *
  24.  * @since  2.0
  25.  */
  26. {
  27.     // Used to uniquely identify blocks
  28.     protected static $nextBlockId 0;
  29.  
  30.     protected static $precedence array(
  31.         '=<'                     => 0,
  32.         '>='                     => 0,
  33.         '='                         => 0,
  34.         '<'                         => 0,
  35.         '>'                         => 0,
  36.         '+'                         => 1,
  37.         '-'                         => 1,
  38.         '*'                         => 2,
  39.         '/'                         => 2,
  40.         '%'                         => 2,
  41.     );
  42.  
  43.     protected static $whitePattern;
  44.  
  45.     protected static $commentMulti;
  46.  
  47.     protected static $commentSingle "//";
  48.  
  49.     protected static $commentMultiLeft "/*";
  50.  
  51.     protected static $commentMultiRight "*/";
  52.  
  53.     // Regex string to match any of the operators
  54.     protected static $operatorString;
  55.  
  56.     // These properties will supress division unless it's inside parenthases
  57.     protected static $supressDivisionProps array('/border-radius$/i''/^font$/i');
  58.  
  59.     protected $blockDirectives = array("font-face""keyframes""page""-moz-document");
  60.  
  61.     protected $lineDirectives = array("charset");
  62.  
  63.     /**
  64.      * if we are in parens we can be more liberal with whitespace around
  65.      * operators because it must evaluate to a single value and thus is less
  66.      * ambiguous.
  67.      *
  68.      * Consider:
  69.      *     property1: 10 -5; // is two numbers, 10 and -5
  70.      *     property2: (10 -5); // should evaluate to 5
  71.      */
  72.     protected $inParens = false;
  73.  
  74.     // Caches preg escaped literals
  75.     protected static $literalCache array();
  76.  
  77.     /**
  78.      * Constructor
  79.      *
  80.      * @param   [type]  $lessc       [description]
  81.      * @param   string  $sourceName  [description]
  82.      */
  83.     public function __construct($lessc$sourceName null)
  84.     {
  85.         $this->eatWhiteDefault true;
  86.  
  87.         // Reference to less needed for vPrefix, mPrefix, and parentSelector
  88.         $this->lessc $lessc;
  89.  
  90.         // Name used for error messages
  91.         $this->sourceName $sourceName;
  92.  
  93.         $this->writeComments false;
  94.  
  95.         if (!self::$operatorString)
  96.         {
  97.             self::$operatorString '(' implode('|'array_map(array('FOFLess''preg_quote')array_keys(self::$precedence))) ')';
  98.  
  99.             $commentSingle FOFLess::preg_quote(self::$commentSingle);
  100.             $commentMultiLeft FOFLess::preg_quote(self::$commentMultiLeft);
  101.             $commentMultiRight FOFLess::preg_quote(self::$commentMultiRight);
  102.  
  103.             self::$commentMulti $commentMultiLeft '.*?' $commentMultiRight;
  104.             self::$whitePattern '/' $commentSingle '[^\n]*\s*|(' self::$commentMulti ')\s*|\s+/Ais';
  105.         }
  106.     }
  107.  
  108.     /**
  109.      * Parse text
  110.      *
  111.      * @param   string  $buffer  [description]
  112.      *
  113.      * @return  [type]           [description]
  114.      */
  115.     public function parse($buffer)
  116.     {
  117.         $this->count 0;
  118.         $this->line 1;
  119.  
  120.         // Block stack
  121.         $this->env null;
  122.         $this->buffer $this->writeComments $buffer $this->removeComments($buffer);
  123.         $this->pushSpecialBlock("root");
  124.         $this->eatWhiteDefault true;
  125.         $this->seenComments array();
  126.  
  127.         /*
  128.          * trim whitespace on head
  129.          * if (preg_match('/^\s+/', $this->buffer, $m)) {
  130.          *     $this->line += substr_count($m[0], "\n");
  131.          *     $this->buffer = ltrim($this->buffer);
  132.          * }
  133.          */
  134.         $this->whitespace();
  135.  
  136.         // Parse the entire file
  137.         $lastCount $this->count;
  138.         while (false !== $this->parseChunk());
  139.  
  140.         if ($this->count != strlen($this->buffer))
  141.         {
  142.             $this->throwError();
  143.         }
  144.  
  145.         // TODO report where the block was opened
  146.         if (!is_null($this->env->parent))
  147.         {
  148.             throw new exception('parse error: unclosed block');
  149.         }
  150.  
  151.         return $this->env;
  152.     }
  153.  
  154.     /**
  155.      * Parse a single chunk off the head of the buffer and append it to the
  156.      * current parse environment.
  157.      * Returns false when the buffer is empty, or when there is an error.
  158.      *
  159.      * This function is called repeatedly until the entire document is
  160.      * parsed.
  161.      *
  162.      * This parser is most similar to a recursive descent parser. Single
  163.      * functions represent discrete grammatical rules for the language, and
  164.      * they are able to capture the text that represents those rules.
  165.      *
  166.      * Consider the function lessc::keyword(). (all parse functions are
  167.      * structured the same)
  168.      *
  169.      * The function takes a single reference argument. When calling the
  170.      * function it will attempt to match a keyword on the head of the buffer.
  171.      * If it is successful, it will place the keyword in the referenced
  172.      * argument, advance the position in the buffer, and return true. If it
  173.      * fails then it won't advance the buffer and it will return false.
  174.      *
  175.      * All of these parse functions are powered by lessc::match(), which behaves
  176.      * the same way, but takes a literal regular expression. Sometimes it is
  177.      * more convenient to use match instead of creating a new function.
  178.      *
  179.      * Because of the format of the functions, to parse an entire string of
  180.      * grammatical rules, you can chain them together using &&.
  181.      *
  182.      * But, if some of the rules in the chain succeed before one fails, then
  183.      * the buffer position will be left at an invalid state. In order to
  184.      * avoid this, lessc::seek() is used to remember and set buffer positions.
  185.      *
  186.      * Before parsing a chain, use $s = $this->seek() to remember the current
  187.      * position into $s. Then if a chain fails, use $this->seek($s) to
  188.      * go back where we started.
  189.      *
  190.      * @return  boolean 
  191.      */
  192.     protected function parseChunk()
  193.     {
  194.         if (empty($this->buffer))
  195.         {
  196.             return false;
  197.         }
  198.  
  199.         $s $this->seek();
  200.  
  201.         // Setting a property
  202.         if ($this->keyword($key&& $this->assign()
  203.             && $this->propertyValue($value$key&& $this->end())
  204.         {
  205.             $this->append(array('assign'$key$value)$s);
  206.  
  207.             return true;
  208.         }
  209.         else
  210.         {
  211.             $this->seek($s);
  212.         }
  213.  
  214.         // Look for special css blocks
  215.         if ($this->literal('@'false))
  216.         {
  217.             $this->count--;
  218.  
  219.             // Media
  220.             if ($this->literal('@media'))
  221.             {
  222.                 if (($this->mediaQueryList($mediaQueries|| true)
  223.                     && $this->literal('{'))
  224.                 {
  225.                     $media $this->pushSpecialBlock("media");
  226.                     $media->queries is_null($mediaQueriesarray($mediaQueries;
  227.  
  228.                     return true;
  229.                 }
  230.                 else
  231.                 {
  232.                     $this->seek($s);
  233.  
  234.                     return false;
  235.                 }
  236.             }
  237.  
  238.             if ($this->literal("@"false&& $this->keyword($dirName))
  239.             {
  240.                 if ($this->isDirective($dirName$this->blockDirectives))
  241.                 {
  242.                     if (($this->openString("{"$dirValuenullarray(";")) || true)
  243.                         && $this->literal("{"))
  244.                     {
  245.                         $dir $this->pushSpecialBlock("directive");
  246.                         $dir->name $dirName;
  247.  
  248.                         if (isset($dirValue))
  249.                         {
  250.                             $dir->value $dirValue;
  251.                         }
  252.  
  253.                         return true;
  254.                     }
  255.                 }
  256.                 elseif ($this->isDirective($dirName$this->lineDirectives))
  257.                 {
  258.                     if ($this->propertyValue($dirValue&& $this->end())
  259.                     {
  260.                         $this->append(array("directive"$dirName$dirValue));
  261.  
  262.                         return true;
  263.                     }
  264.                 }
  265.             }
  266.  
  267.             $this->seek($s);
  268.         }
  269.  
  270.         // Setting a variable
  271.         if ($this->variable($var&& $this->assign()
  272.             && $this->propertyValue($value&& $this->end())
  273.         {
  274.             $this->append(array('assign'$var$value)$s);
  275.  
  276.             return true;
  277.         }
  278.         else
  279.         {
  280.             $this->seek($s);
  281.         }
  282.  
  283.         if ($this->import($importValue))
  284.         {
  285.             $this->append($importValue$s);
  286.  
  287.             return true;
  288.         }
  289.  
  290.         // Opening parametric mixin
  291.         if ($this->tag($tagtrue&& $this->argumentDef($args$isVararg)
  292.             && ($this->guards($guards|| true)
  293.             && $this->literal('{'))
  294.         {
  295.             $block $this->pushBlock($this->fixTags(array($tag)));
  296.             $block->args $args;
  297.             $block->isVararg $isVararg;
  298.  
  299.             if (!empty($guards))
  300.             {
  301.                 $block->guards $guards;
  302.             }
  303.  
  304.             return true;
  305.         }
  306.         else
  307.         {
  308.             $this->seek($s);
  309.         }
  310.  
  311.         // Opening a simple block
  312.         if ($this->tags($tags&& $this->literal('{'))
  313.         {
  314.             $tags $this->fixTags($tags);
  315.             $this->pushBlock($tags);
  316.  
  317.             return true;
  318.         }
  319.         else
  320.         {
  321.             $this->seek($s);
  322.         }
  323.  
  324.         // Closing a block
  325.         if ($this->literal('}'false))
  326.         {
  327.             try
  328.             {
  329.                 $block $this->pop();
  330.             }
  331.             catch (exception $e)
  332.             {
  333.                 $this->seek($s);
  334.                 $this->throwError($e->getMessage());
  335.             }
  336.  
  337.             $hidden false;
  338.  
  339.             if (is_null($block->type))
  340.             {
  341.                 $hidden true;
  342.  
  343.                 if (!isset($block->args))
  344.                 {
  345.                     foreach ($block->tags as $tag)
  346.                     {
  347.                         if (!is_string($tag|| $tag{0!= $this->lessc->mPrefix)
  348.                         {
  349.                             $hidden false;
  350.                             break;
  351.                         }
  352.                     }
  353.                 }
  354.  
  355.                 foreach ($block->tags as $tag)
  356.                 {
  357.                     if (is_string($tag))
  358.                     {
  359.                         $this->env->children[$tag][$block;
  360.                     }
  361.                 }
  362.             }
  363.  
  364.             if (!$hidden)
  365.             {
  366.                 $this->append(array('block'$block)$s);
  367.             }
  368.  
  369.             // This is done here so comments aren't bundled into he block that was just closed
  370.             $this->whitespace();
  371.  
  372.             return true;
  373.         }
  374.  
  375.         // Mixin
  376.         if ($this->mixinTags($tags)
  377.             && ($this->argumentValues($argv|| true)
  378.             && ($this->keyword($suffix|| true)
  379.             && $this->end())
  380.         {
  381.             $tags $this->fixTags($tags);
  382.             $this->append(array('mixin'$tags$argv$suffix)$s);
  383.  
  384.             return true;
  385.         }
  386.         else
  387.         {
  388.             $this->seek($s);
  389.         }
  390.  
  391.         // Spare ;
  392.         if ($this->literal(';'))
  393.         {
  394.             return true;
  395.         }
  396.  
  397.         // Got nothing, throw error
  398.         return false;
  399.     }
  400.  
  401.     /**
  402.      * [isDirective description]
  403.      *
  404.      * @param   string  $dirname     [description]
  405.      * @param   [type]  $directives  [description]
  406.      *
  407.      * @return  boolean 
  408.      */
  409.     protected function isDirective($dirname$directives)
  410.     {
  411.         // TODO: cache pattern in parser
  412.         $pattern implode("|"array_map(array("FOFLess""preg_quote")$directives));
  413.         $pattern '/^(-[a-z-]+-)?(' $pattern ')$/i';
  414.  
  415.         return preg_match($pattern$dirname);
  416.     }
  417.  
  418.     /**
  419.      * [fixTags description]
  420.      *
  421.      * @param   [type]  $tags  [description]
  422.      *
  423.      * @return  [type]         [description]
  424.      */
  425.     protected function fixTags($tags)
  426.     {
  427.         // Move @ tags out of variable namespace
  428.         foreach ($tags as &$tag)
  429.         {
  430.             if ($tag{0== $this->lessc->vPrefix)
  431.             {
  432.                 $tag[0$this->lessc->mPrefix;
  433.             }
  434.         }
  435.  
  436.         return $tags;
  437.     }
  438.  
  439.     /**
  440.      * a list of expressions
  441.      *
  442.      * @param   [type]  &$exps  [description]
  443.      *
  444.      * @return  boolean 
  445.      */
  446.     protected function expressionList(&$exps)
  447.     {
  448.         $values array();
  449.  
  450.         while ($this->expression($exp))
  451.         {
  452.             $values[$exp;
  453.         }
  454.  
  455.         if (count($values== 0)
  456.         {
  457.             return false;
  458.         }
  459.  
  460.         $exps FOFLess::compressList($values' ');
  461.  
  462.         return true;
  463.     }
  464.  
  465.     /**
  466.      * Attempt to consume an expression.
  467.      *
  468.      * @param   string  &$out  [description]
  469.      *
  470.      * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
  471.      *
  472.      * @return  boolean 
  473.      */
  474.     protected function expression(&$out)
  475.     {
  476.         if ($this->value($lhs))
  477.         {
  478.             $out $this->expHelper($lhs0);
  479.  
  480.             // Look for / shorthand
  481.             if (!empty($this->env->supressedDivision))
  482.             {
  483.                 unset($this->env->supressedDivision);
  484.                 $s $this->seek();
  485.  
  486.                 if ($this->literal("/"&& $this->value($rhs))
  487.                 {
  488.                     $out array("list""",
  489.                         array($outarray("keyword""/")$rhs));
  490.                 }
  491.                 else
  492.                 {
  493.                     $this->seek($s);
  494.                 }
  495.             }
  496.  
  497.             return true;
  498.         }
  499.  
  500.         return false;
  501.     }
  502.  
  503.     /**
  504.      * Recursively parse infix equation with $lhs at precedence $minP
  505.      *
  506.      * @param   type  $lhs   [description]
  507.      * @param   type  $minP  [description]
  508.      *
  509.      * @return   string 
  510.      */
  511.     protected function expHelper($lhs$minP)
  512.     {
  513.         $this->inExp true;
  514.         $ss $this->seek();
  515.  
  516.         while (true)
  517.         {
  518.             $whiteBefore = isset($this->buffer[$this->count 1]&& ctype_space($this->buffer[$this->count 1]);
  519.  
  520.             // If there is whitespace before the operator, then we require
  521.             // whitespace after the operator for it to be an expression
  522.             $needWhite $whiteBefore && !$this->inParens;
  523.  
  524.             if ($this->match(self::$operatorString ($needWhite '\s' '')$m&& self::$precedence[$m[1]] >= $minP)
  525.             {
  526.                 if (!$this->inParens && isset($this->env->currentProperty&& $m[1== "/" && empty($this->env->supressedDivision))
  527.                 {
  528.                     foreach (self::$supressDivisionProps as $pattern)
  529.                     {
  530.                         if (preg_match($pattern$this->env->currentProperty))
  531.                         {
  532.                             $this->env->supressedDivision true;
  533.                             break 2;
  534.                         }
  535.                     }
  536.                 }
  537.  
  538.                 $whiteAfter = isset($this->buffer[$this->count 1]&& ctype_space($this->buffer[$this->count 1]);
  539.  
  540.                 if (!$this->value($rhs))
  541.                 {
  542.                     break;
  543.                 }
  544.  
  545.                 // Peek for next operator to see what to do with rhs
  546.                 if ($this->peek(self::$operatorString$next&& self::$precedence[$next[1]] self::$precedence[$m[1]])
  547.                 {
  548.                     $rhs $this->expHelper($rhsself::$precedence[$next[1]]);
  549.                 }
  550.  
  551.                 $lhs array('expression'$m[1]$lhs$rhs$whiteBefore$whiteAfter);
  552.                 $ss $this->seek();
  553.  
  554.                 continue;
  555.             }
  556.  
  557.             break;
  558.         }
  559.  
  560.         $this->seek($ss);
  561.  
  562.         return $lhs;
  563.     }
  564.  
  565.     /**
  566.      * Consume a list of values for a property
  567.      *
  568.      * @param   [type]  &$value   [description]
  569.      * @param   [type]  $keyName  [description]
  570.      *
  571.      * @return  boolean 
  572.      */
  573.     public function propertyValue(&$value$keyName null)
  574.     {
  575.         $values array();
  576.  
  577.         if ($keyName !== null)
  578.         {
  579.             $this->env->currentProperty $keyName;
  580.         }
  581.  
  582.         $s null;
  583.  
  584.         while ($this->expressionList($v))
  585.         {
  586.             $values[$v;
  587.             $s $this->seek();
  588.  
  589.             if (!$this->literal(','))
  590.             {
  591.                 break;
  592.             }
  593.         }
  594.  
  595.         if ($s)
  596.         {
  597.             $this->seek($s);
  598.         }
  599.  
  600.         if ($keyName !== null)
  601.         {
  602.             unset($this->env->currentProperty);
  603.         }
  604.  
  605.         if (count($values== 0)
  606.         {
  607.             return false;
  608.         }
  609.  
  610.         $value FOFLess::compressList($values', ');
  611.  
  612.         return true;
  613.     }
  614.  
  615.     /**
  616.      * [parenValue description]
  617.      *
  618.      * @param   [type]  &$out  [description]
  619.      *
  620.      * @return  boolean 
  621.      */
  622.     protected function parenValue(&$out)
  623.     {
  624.         $s $this->seek();
  625.  
  626.         // Speed shortcut
  627.         if (isset($this->buffer[$this->count]&& $this->buffer[$this->count!= "(")
  628.         {
  629.             return false;
  630.         }
  631.  
  632.         $inParens $this->inParens;
  633.  
  634.         if ($this->literal("("&& ($this->inParens = true&& $this->expression($exp&& $this->literal(")"))
  635.         {
  636.             $out $exp;
  637.             $this->inParens = $inParens;
  638.  
  639.             return true;
  640.         }
  641.         else
  642.         {
  643.             $this->inParens = $inParens;
  644.             $this->seek($s);
  645.         }
  646.  
  647.         return false;
  648.     }
  649.  
  650.     /**
  651.      * a single value
  652.      *
  653.      * @param   [type]  &$value  [description]
  654.      *
  655.      * @return  boolean 
  656.      */
  657.     protected function value(&$value)
  658.     {
  659.         $s $this->seek();
  660.  
  661.         // Speed shortcut
  662.         if (isset($this->buffer[$this->count]&& $this->buffer[$this->count== "-")
  663.         {
  664.             // Negation
  665.             if ($this->literal("-"false&&(($this->variable($inner&& $inner array("variable"$inner))
  666.                 || $this->unit($inner|| $this->parenValue($inner)))
  667.             {
  668.                 $value array("unary""-"$inner);
  669.  
  670.                 return true;
  671.             }
  672.             else
  673.             {
  674.                 $this->seek($s);
  675.             }
  676.         }
  677.  
  678.         if ($this->parenValue($value))
  679.         {
  680.             return true;
  681.         }
  682.  
  683.         if ($this->unit($value))
  684.         {
  685.             return true;
  686.         }
  687.  
  688.         if ($this->color($value))
  689.         {
  690.             return true;
  691.         }
  692.  
  693.         if ($this->func($value))
  694.         {
  695.             return true;
  696.         }
  697.  
  698.         if ($this->string($value))
  699.         {
  700.             return true;
  701.         }
  702.  
  703.         if ($this->keyword($word))
  704.         {
  705.             $value array('keyword'$word);
  706.  
  707.             return true;
  708.         }
  709.  
  710.         // Try a variable
  711.         if ($this->variable($var))
  712.         {
  713.             $value array('variable'$var);
  714.  
  715.             return true;
  716.         }
  717.  
  718.         // Unquote string (should this work on any type?
  719.         if ($this->literal("~"&& $this->string($str))
  720.         {
  721.             $value array("escape"$str);
  722.  
  723.             return true;
  724.         }
  725.         else
  726.         {
  727.             $this->seek($s);
  728.         }
  729.  
  730.         // Css hack: \0
  731.         if ($this->literal('\\'&& $this->match('([0-9]+)'$m))
  732.         {
  733.             $value array('keyword''\\' $m[1]);
  734.  
  735.             return true;
  736.         }
  737.         else
  738.         {
  739.             $this->seek($s);
  740.         }
  741.  
  742.         return false;
  743.     }
  744.  
  745.     /**
  746.      * an import statement
  747.      *
  748.      * @param   [type]  &$out  [description]
  749.      *
  750.      * @return  boolean 
  751.      */
  752.     protected function import(&$out)
  753.     {
  754.         $s $this->seek();
  755.  
  756.         if (!$this->literal('@import'))
  757.         {
  758.             return false;
  759.         }
  760.  
  761.         /*
  762.          * @import "something.css" media;
  763.          * @import url("something.css") media;
  764.          * @import url(something.css) media;
  765.          */
  766.  
  767.         if ($this->propertyValue($value))
  768.         {
  769.             $out array("import"$value);
  770.  
  771.             return true;
  772.         }
  773.     }
  774.  
  775.     /**
  776.      * [mediaQueryList description]
  777.      *
  778.      * @param   [type]  &$out  [description]
  779.      *
  780.      * @return  boolean 
  781.      */
  782.     protected function mediaQueryList(&$out)
  783.     {
  784.         if ($this->genericList($list"mediaQuery"","false))
  785.         {
  786.             $out $list[2];
  787.  
  788.             return true;
  789.         }
  790.  
  791.         return false;
  792.     }
  793.  
  794.     /**
  795.      * [mediaQuery description]
  796.      *
  797.      * @param   [type]  &$out  [description]
  798.      *
  799.      * @return  [type]        [description]
  800.      */
  801.     protected function mediaQuery(&$out)
  802.     {
  803.         $s $this->seek();
  804.  
  805.         $expressions null;
  806.         $parts array();
  807.  
  808.         if (($this->literal("only"&& ($only true|| $this->literal("not"&& ($not true|| true&& $this->keyword($mediaType))
  809.         {
  810.             $prop array("mediaType");
  811.  
  812.             if (isset($only))
  813.             {
  814.                 $prop["only";
  815.             }
  816.  
  817.             if (isset($not))
  818.             {
  819.                 $prop["not";
  820.             }
  821.  
  822.             $prop[$mediaType;
  823.             $parts[$prop;
  824.         }
  825.         else
  826.         {
  827.             $this->seek($s);
  828.         }
  829.  
  830.         if (!empty($mediaType&& !$this->literal("and"))
  831.         {
  832.             // ~
  833.         }
  834.         else
  835.         {
  836.             $this->genericList($expressions"mediaExpression""and"false);
  837.  
  838.             if (is_array($expressions))
  839.             {
  840.                 $parts array_merge($parts$expressions[2]);
  841.             }
  842.         }
  843.  
  844.         if (count($parts== 0)
  845.         {
  846.             $this->seek($s);
  847.  
  848.             return false;
  849.         }
  850.  
  851.         $out $parts;
  852.  
  853.         return true;
  854.     }
  855.  
  856.     /**
  857.      * [mediaExpression description]
  858.      *
  859.      * @param   [type]  &$out  [description]
  860.      *
  861.      * @return  boolean 
  862.      */
  863.     protected function mediaExpression(&$out)
  864.     {
  865.         $s $this->seek();
  866.         $value null;
  867.  
  868.         if ($this->literal("("&& $this->keyword($feature&& ($this->literal(":")
  869.             && $this->expression($value|| true&& $this->literal(")"))
  870.         {
  871.             $out array("mediaExp"$feature);
  872.  
  873.             if ($value)
  874.             {
  875.                 $out[$value;
  876.             }
  877.  
  878.             return true;
  879.         }
  880.         elseif ($this->variable($variable))
  881.         {
  882.             $out array('variable'$variable);
  883.  
  884.             return true;
  885.         }
  886.         $this->seek($s);
  887.  
  888.         return false;
  889.     }
  890.  
  891.     /**
  892.      * An unbounded string stopped by $end
  893.      *
  894.      * @param   [type]  $end          [description]
  895.      * @param   [type]  &$out         [description]
  896.      * @param   [type]  $nestingOpen  [description]
  897.      * @param   [type]  $rejectStrs   [description]
  898.      *
  899.      * @return  boolean 
  900.      */
  901.     protected function openString($end&$out$nestingOpen null$rejectStrs null)
  902.     {
  903.         $oldWhite $this->eatWhiteDefault;
  904.         $this->eatWhiteDefault false;
  905.  
  906.         $stop array("'"'"'"@{"$end);
  907.         $stop array_map(array("FOFLess""preg_quote")$stop);
  908.  
  909.         // $stop[] = self::$commentMulti;
  910.  
  911.         if (!is_null($rejectStrs))
  912.         {
  913.             $stop array_merge($stop$rejectStrs);
  914.         }
  915.  
  916.         $patt '(.*?)(' implode("|"$stop')';
  917.  
  918.         $nestingLevel 0;
  919.  
  920.         $content array();
  921.  
  922.         while ($this->match($patt$mfalse))
  923.         {
  924.             if (!empty($m[1]))
  925.             {
  926.                 $content[$m[1];
  927.  
  928.                 if ($nestingOpen)
  929.                 {
  930.                     $nestingLevel += substr_count($m[1]$nestingOpen);
  931.                 }
  932.             }
  933.  
  934.             $tok $m[2];
  935.  
  936.             $this->count -= strlen($tok);
  937.  
  938.             if ($tok == $end)
  939.             {
  940.                 if ($nestingLevel == 0)
  941.                 {
  942.                     break;
  943.                 }
  944.                 else
  945.                 {
  946.                     $nestingLevel--;
  947.                 }
  948.             }
  949.  
  950.             if (($tok == "'" || $tok == '"'&& $this->string($str))
  951.             {
  952.                 $content[$str;
  953.                 continue;
  954.             }
  955.  
  956.             if ($tok == "@{" && $this->interpolation($inter))
  957.             {
  958.                 $content[$inter;
  959.                 continue;
  960.             }
  961.  
  962.             if (in_array($tok$rejectStrs))
  963.             {
  964.                 $count null;
  965.                 break;
  966.             }
  967.  
  968.             $content[$tok;
  969.             $this->count += strlen($tok);
  970.         }
  971.  
  972.         $this->eatWhiteDefault $oldWhite;
  973.  
  974.         if (count($content== 0)
  975.             return false;
  976.  
  977.         // Trim the end
  978.         if (is_string(end($content)))
  979.         {
  980.             $content[count($content1rtrim(end($content));
  981.         }
  982.  
  983.         $out array("string"""$content);
  984.  
  985.         return true;
  986.     }
  987.  
  988.     /**
  989.      * [string description]
  990.      *
  991.      * @param   [type]  &$out  [description]
  992.      *
  993.      * @return  boolean 
  994.      */
  995.     protected function string(&$out)
  996.     {
  997.         $s $this->seek();
  998.  
  999.         if ($this->literal('"'false))
  1000.         {
  1001.             $delim '"';
  1002.         }
  1003.         elseif ($this->literal("'"false))
  1004.         {
  1005.             $delim "'";
  1006.         }
  1007.         else
  1008.         {
  1009.             return false;
  1010.         }
  1011.  
  1012.         $content array();
  1013.  
  1014.         // Look for either ending delim , escape, or string interpolation
  1015.         $patt '([^\n]*?)(@\{|\\\\|' FOFLess::preg_quote($delim')';
  1016.  
  1017.         $oldWhite $this->eatWhiteDefault;
  1018.         $this->eatWhiteDefault false;
  1019.  
  1020.         while ($this->match($patt$mfalse))
  1021.         {
  1022.             $content[$m[1];
  1023.  
  1024.             if ($m[2== "@{")
  1025.             {
  1026.                 $this->count -= strlen($m[2]);
  1027.  
  1028.                 if ($this->interpolation($interfalse))
  1029.                 {
  1030.                     $content[$inter;
  1031.                 }
  1032.                 else
  1033.                 {
  1034.                     $this->count += strlen($m[2]);
  1035.  
  1036.                     // Ignore it
  1037.                     $content["@{";
  1038.                 }
  1039.             }
  1040.             elseif ($m[2== '\\')
  1041.             {
  1042.                 $content[$m[2];
  1043.  
  1044.                 if ($this->literal($delimfalse))
  1045.                 {
  1046.                     $content[$delim;
  1047.                 }
  1048.             }
  1049.             else
  1050.             {
  1051.                 $this->count -= strlen($delim);
  1052.  
  1053.                 // Delim
  1054.                 break;
  1055.             }
  1056.         }
  1057.  
  1058.         $this->eatWhiteDefault $oldWhite;
  1059.  
  1060.         if ($this->literal($delim))
  1061.         {
  1062.             $out array("string"$delim$content);
  1063.  
  1064.             return true;
  1065.         }
  1066.  
  1067.         $this->seek($s);
  1068.  
  1069.         return false;
  1070.     }
  1071.  
  1072.     /**
  1073.      * [interpolation description]
  1074.      *
  1075.      * @param   [type]  &$out  [description]
  1076.      *
  1077.      * @return  boolean 
  1078.      */
  1079.     protected function interpolation(&$out)
  1080.     {
  1081.         $oldWhite $this->eatWhiteDefault;
  1082.         $this->eatWhiteDefault true;
  1083.  
  1084.         $s $this->seek();
  1085.  
  1086.         if ($this->literal("@{"&& $this->openString("}"$interpnullarray("'"'"'";")) && $this->literal("}"false))
  1087.         {
  1088.             $out array("interpolate"$interp);
  1089.             $this->eatWhiteDefault $oldWhite;
  1090.  
  1091.             if ($this->eatWhiteDefault)
  1092.             {
  1093.                 $this->whitespace();
  1094.             }
  1095.  
  1096.             return true;
  1097.         }
  1098.  
  1099.         $this->eatWhiteDefault $oldWhite;
  1100.         $this->seek($s);
  1101.  
  1102.         return false;
  1103.     }
  1104.  
  1105.     /**
  1106.      * [unit description]
  1107.      *
  1108.      * @param   [type]  &$unit  [description]
  1109.      *
  1110.      * @return  boolean 
  1111.      */
  1112.     protected function unit(&$unit)
  1113.     {
  1114.         // Speed shortcut
  1115.         if (isset($this->buffer[$this->count]))
  1116.         {
  1117.             $char $this->buffer[$this->count];
  1118.  
  1119.             if (!ctype_digit($char&& $char != ".")
  1120.             {
  1121.                 return false;
  1122.             }
  1123.         }
  1124.  
  1125.         if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?'$m))
  1126.         {
  1127.             $unit array("number"$m[1]empty($m[2]"" $m[2]);
  1128.  
  1129.             return true;
  1130.         }
  1131.  
  1132.         return false;
  1133.     }
  1134.  
  1135.     /**
  1136.      * a # color
  1137.      *
  1138.      * @param   [type]  &$out  [description]
  1139.      *
  1140.      * @return  boolean 
  1141.      */
  1142.     protected function color(&$out)
  1143.     {
  1144.         if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))'$m))
  1145.         {
  1146.             if (strlen($m[1]7)
  1147.             {
  1148.                 $out array("string"""array($m[1]));
  1149.             }
  1150.             else
  1151.             {
  1152.                 $out array("raw_color"$m[1]);
  1153.             }
  1154.  
  1155.             return true;
  1156.         }
  1157.  
  1158.         return false;
  1159.     }
  1160.  
  1161.     /**
  1162.      * Consume a list of property values delimited by ; and wrapped in ()
  1163.      *
  1164.      * @param   [type]  &$args  [description]
  1165.      * @param   [type]  $delim  [description]
  1166.      *
  1167.      * @return  boolean 
  1168.      */
  1169.     protected function argumentValues(&$args$delim ',')
  1170.     {
  1171.         $s $this->seek();
  1172.  
  1173.         if (!$this->literal('('))
  1174.         {
  1175.             return false;
  1176.         }
  1177.  
  1178.         $values array();
  1179.  
  1180.         while (true)
  1181.         {
  1182.             if ($this->expressionList($value))
  1183.             {
  1184.                 $values[$value;
  1185.             }
  1186.  
  1187.             if (!$this->literal($delim))
  1188.             {
  1189.                 break;
  1190.             }
  1191.             else
  1192.             {
  1193.                 if ($value == null)
  1194.                 {
  1195.                     $values[null;
  1196.                 }
  1197.  
  1198.                 $value null;
  1199.             }
  1200.         }
  1201.  
  1202.         if (!$this->literal(')'))
  1203.         {
  1204.             $this->seek($s);
  1205.  
  1206.             return false;
  1207.         }
  1208.  
  1209.         $args $values;
  1210.  
  1211.         return true;
  1212.     }
  1213.  
  1214.     /**
  1215.      * Consume an argument definition list surrounded by ()
  1216.      * each argument is a variable name with optional value
  1217.      * or at the end a ... or a variable named followed by ...
  1218.      *
  1219.      * @param   [type]  &$args      [description]
  1220.      * @param   [type]  &$isVararg  [description]
  1221.      * @param   [type]  $delim      [description]
  1222.      *
  1223.      * @return  boolean 
  1224.      */
  1225.     protected function argumentDef(&$args&$isVararg$delim ',')
  1226.     {
  1227.         $s $this->seek();
  1228.         if (!$this->literal('('))
  1229.             return false;
  1230.  
  1231.         $values array();
  1232.  
  1233.         $isVararg false;
  1234.  
  1235.         while (true)
  1236.         {
  1237.             if ($this->literal("..."))
  1238.             {
  1239.                 $isVararg true;
  1240.                 break;
  1241.             }
  1242.  
  1243.             if ($this->variable($vname))
  1244.             {
  1245.                 $arg array("arg"$vname);
  1246.                 $ss $this->seek();
  1247.  
  1248.                 if ($this->assign(&& $this->expressionList($value))
  1249.                 {
  1250.                     $arg[$value;
  1251.                 }
  1252.                 else
  1253.                 {
  1254.                     $this->seek($ss);
  1255.  
  1256.                     if ($this->literal("..."))
  1257.                     {
  1258.                         $arg[0"rest";
  1259.                         $isVararg true;
  1260.                     }
  1261.                 }
  1262.  
  1263.                 $values[$arg;
  1264.  
  1265.                 if ($isVararg)
  1266.                 {
  1267.                     break;
  1268.                 }
  1269.  
  1270.                 continue;
  1271.             }
  1272.  
  1273.             if ($this->value($literal))
  1274.             {
  1275.                 $values[array("lit"$literal);
  1276.             }
  1277.  
  1278.             if (!$this->literal($delim))
  1279.             {
  1280.                 break;
  1281.             }
  1282.         }
  1283.  
  1284.         if (!$this->literal(')'))
  1285.         {
  1286.             $this->seek($s);
  1287.  
  1288.             return false;
  1289.         }
  1290.  
  1291.         $args $values;
  1292.  
  1293.         return true;
  1294.     }
  1295.  
  1296.     /**
  1297.      * Consume a list of tags
  1298.      * This accepts a hanging delimiter
  1299.      *
  1300.      * @param   [type]  &$tags   [description]
  1301.      * @param   [type]  $simple  [description]
  1302.      * @param   [type]  $delim   [description]
  1303.      *
  1304.      * @return  boolean 
  1305.      */
  1306.     protected function tags(&$tags$simple false$delim ',')
  1307.     {
  1308.         $tags array();
  1309.  
  1310.         while ($this->tag($tt$simple))
  1311.         {
  1312.             $tags[$tt;
  1313.  
  1314.             if (!$this->literal($delim))
  1315.             {
  1316.                 break;
  1317.             }
  1318.         }
  1319.  
  1320.         if (count($tags== 0)
  1321.         {
  1322.             return false;
  1323.         }
  1324.  
  1325.         return true;
  1326.     }
  1327.  
  1328.     /**
  1329.      * List of tags of specifying mixin path
  1330.      * Optionally separated by > (lazy, accepts extra >)
  1331.      *
  1332.      * @param   [type]  &$tags  [description]
  1333.      *
  1334.      * @return  boolean 
  1335.      */
  1336.     protected function mixinTags(&$tags)
  1337.     {
  1338.         $s $this->seek();
  1339.         $tags array();
  1340.  
  1341.         while ($this->tag($tttrue))
  1342.         {
  1343.             $tags[$tt;
  1344.             $this->literal(">");
  1345.         }
  1346.  
  1347.         if (count($tags== 0)
  1348.         {
  1349.             return false;
  1350.         }
  1351.  
  1352.         return true;
  1353.     }
  1354.  
  1355.     /**
  1356.      * A bracketed value (contained within in a tag definition)
  1357.      *
  1358.      * @param   [type]  &$value  [description]
  1359.      *
  1360.      * @return  boolean 
  1361.      */
  1362.     protected function tagBracket(&$value)
  1363.     {
  1364.         // Speed shortcut
  1365.         if (isset($this->buffer[$this->count]&& $this->buffer[$this->count!= "[")
  1366.         {
  1367.             return false;
  1368.         }
  1369.  
  1370.         $s $this->seek();
  1371.  
  1372.         if ($this->literal('['&& $this->to(']'$ctrue&& $this->literal(']'false))
  1373.         {
  1374.             $value '[' $c ']';
  1375.  
  1376.             // Whitespace?
  1377.             if ($this->whitespace())
  1378.             {
  1379.                 $value .= " ";
  1380.             }
  1381.  
  1382.             // Escape parent selector, (yuck)
  1383.             $value str_replace($this->lessc->parentSelector"$&$"$value);
  1384.  
  1385.             return true;
  1386.         }
  1387.  
  1388.         $this->seek($s);
  1389.  
  1390.         return false;
  1391.     }
  1392.  
  1393.     /**
  1394.      * [tagExpression description]
  1395.      *
  1396.      * @param   [type]  &$value  [description]
  1397.      *
  1398.      * @return  boolean 
  1399.      */
  1400.     protected function tagExpression(&$value)
  1401.     {
  1402.         $s $this->seek();
  1403.  
  1404.         if ($this->literal("("&& $this->expression($exp&& $this->literal(")"))
  1405.         {
  1406.             $value array('exp'$exp);
  1407.  
  1408.             return true;
  1409.         }
  1410.  
  1411.         $this->seek($s);
  1412.  
  1413.         return false;
  1414.     }
  1415.  
  1416.     /**
  1417.      * A single tag
  1418.      *
  1419.      * @param   [type]   &$tag    [description]
  1420.      * @param   boolean  $simple  [description]
  1421.      *
  1422.      * @return  boolean 
  1423.      */
  1424.     protected function tag(&$tag$simple false)
  1425.     {
  1426.         if ($simple)
  1427.         {
  1428.             $chars '^@,:;{}\][>\(\) "\'';
  1429.         }
  1430.         else
  1431.         {
  1432.             $chars '^@,;{}["\'';
  1433.         }
  1434.  
  1435.         $s $this->seek();
  1436.  
  1437.         if (!$simple && $this->tagExpression($tag))
  1438.         {
  1439.             return true;
  1440.         }
  1441.  
  1442.         $hasExpression false;
  1443.         $parts         array();
  1444.  
  1445.         while ($this->tagBracket($first))
  1446.         {
  1447.             $parts[$first;
  1448.         }
  1449.  
  1450.         $oldWhite $this->eatWhiteDefault;
  1451.  
  1452.         $this->eatWhiteDefault false;
  1453.  
  1454.         while (true)
  1455.         {
  1456.             if ($this->match('([' $chars '0-9][' $chars ']*)'$m))
  1457.             {
  1458.                 $parts[$m[1];
  1459.  
  1460.                 if ($simple)
  1461.                 {
  1462.                     break;
  1463.                 }
  1464.  
  1465.                 while ($this->tagBracket($brack))
  1466.                 {
  1467.                     $parts[$brack;
  1468.                 }
  1469.  
  1470.                 continue;
  1471.             }
  1472.  
  1473.             if (isset($this->buffer[$this->count]&& $this->buffer[$this->count== "@")
  1474.             {
  1475.                 if ($this->interpolation($interp))
  1476.                 {
  1477.                     $hasExpression true;
  1478.  
  1479.                     // Don't unescape
  1480.                     $interp[2true;
  1481.                     $parts[$interp;
  1482.  
  1483.                     continue;
  1484.                 }
  1485.  
  1486.                 if ($this->literal("@"))
  1487.                 {
  1488.                     $parts["@";
  1489.  
  1490.                     continue;
  1491.                 }
  1492.             }
  1493.  
  1494.             // For keyframes
  1495.             if ($this->unit($unit))
  1496.             {
  1497.                 $parts[$unit[1];
  1498.                 $parts[$unit[2];
  1499.                 continue;
  1500.             }
  1501.  
  1502.             break;
  1503.         }
  1504.  
  1505.         $this->eatWhiteDefault $oldWhite;
  1506.  
  1507.         if (!$parts)
  1508.         {
  1509.             $this->seek($s);
  1510.  
  1511.             return false;
  1512.         }
  1513.  
  1514.         if ($hasExpression)
  1515.         {
  1516.             $tag array("exp"array("string"""$parts));
  1517.         }
  1518.         else
  1519.         {
  1520.             $tag trim(implode($parts));
  1521.         }
  1522.  
  1523.         $this->whitespace();
  1524.  
  1525.         return true;
  1526.     }
  1527.  
  1528.     /**
  1529.      * A css function
  1530.      *
  1531.      * @param   [type]  &$func  [description]
  1532.      *
  1533.      * @return  boolean 
  1534.      */
  1535.     protected function func(&$func)
  1536.     {
  1537.         $s $this->seek();
  1538.  
  1539.         if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])'$m&& $this->literal('('))
  1540.         {
  1541.             $fname $m[1];
  1542.  
  1543.             $sPreArgs $this->seek();
  1544.  
  1545.             $args array();
  1546.  
  1547.             while (true)
  1548.             {
  1549.                 $ss $this->seek();
  1550.  
  1551.                 // This ugly nonsense is for ie filter properties
  1552.                 if ($this->keyword($name&& $this->literal('='&& $this->expressionList($value))
  1553.                 {
  1554.                     $args[array("string"""array($name"="$value));
  1555.                 }
  1556.                 else
  1557.                 {
  1558.                     $this->seek($ss);
  1559.  
  1560.                     if ($this->expressionList($value))
  1561.                     {
  1562.                         $args[$value;
  1563.                     }
  1564.                 }
  1565.  
  1566.                 if (!$this->literal(','))
  1567.                 {
  1568.                     break;
  1569.                 }
  1570.             }
  1571.  
  1572.             $args array('list'','$args);
  1573.  
  1574.             if ($this->literal(')'))
  1575.             {
  1576.                 $func array('function'$fname$args);
  1577.  
  1578.                 return true;
  1579.             }
  1580.             elseif ($fname == 'url')
  1581.             {
  1582.                 // Couldn't parse and in url? treat as string
  1583.                 $this->seek($sPreArgs);
  1584.  
  1585.                 if ($this->openString(")"$string&& $this->literal(")"))
  1586.                 {
  1587.                     $func array('function'$fname$string);
  1588.  
  1589.                     return true;
  1590.                 }
  1591.             }
  1592.         }
  1593.  
  1594.         $this->seek($s);
  1595.  
  1596.         return false;
  1597.     }
  1598.  
  1599.     /**
  1600.      * Consume a less variable
  1601.      *
  1602.      * @param   [type]  &$name  [description]
  1603.      *
  1604.      * @return  boolean 
  1605.      */
  1606.     protected function variable(&$name)
  1607.     {
  1608.         $s $this->seek();
  1609.  
  1610.         if ($this->literal($this->lessc->vPrefixfalse&&    ($this->variable($sub|| $this->keyword($name)))
  1611.         {
  1612.             if (!empty($sub))
  1613.             {
  1614.                 $name array('variable'$sub);
  1615.             }
  1616.             else
  1617.             {
  1618.                 $name $this->lessc->vPrefix $name;
  1619.             }
  1620.  
  1621.             return true;
  1622.         }
  1623.  
  1624.         $name null;
  1625.         $this->seek($s);
  1626.  
  1627.         return false;
  1628.     }
  1629.  
  1630.     /**
  1631.      * Consume an assignment operator
  1632.      * Can optionally take a name that will be set to the current property name
  1633.      *
  1634.      * @param   string  $name  [description]
  1635.      *
  1636.      * @return  boolean 
  1637.      */
  1638.     protected function assign($name null)
  1639.     {
  1640.         if ($name)
  1641.         {
  1642.             $this->currentProperty $name;
  1643.         }
  1644.  
  1645.         return $this->literal(':'|| $this->literal('=');
  1646.     }
  1647.  
  1648.     /**
  1649.      * Consume a keyword
  1650.      *
  1651.      * @param   [type]  &$word  [description]
  1652.      *
  1653.      * @return  boolean 
  1654.      */
  1655.     protected function keyword(&$word)
  1656.     {
  1657.         if ($this->match('([\w_\-\*!"][\w\-_"]*)'$m))
  1658.         {
  1659.             $word $m[1];
  1660.  
  1661.             return true;
  1662.         }
  1663.  
  1664.         return false;
  1665.     }
  1666.  
  1667.     /**
  1668.      * Consume an end of statement delimiter
  1669.      *
  1670.      * @return  boolean 
  1671.      */
  1672.     protected function end()
  1673.     {
  1674.         if ($this->literal(';'))
  1675.         {
  1676.             return true;
  1677.         }
  1678.         elseif ($this->count == strlen($this->buffer|| $this->buffer{$this->count== '}')
  1679.         {
  1680.             // If there is end of file or a closing block next then we don't need a ;
  1681.             return true;
  1682.         }
  1683.  
  1684.         return false;
  1685.     }
  1686.  
  1687.     /**
  1688.      * [guards description]
  1689.      *
  1690.      * @param   [type]  &$guards  [description]
  1691.      *
  1692.      * @return  boolean 
  1693.      */
  1694.     protected function guards(&$guards)
  1695.     {
  1696.         $s $this->seek();
  1697.  
  1698.         if (!$this->literal("when"))
  1699.         {
  1700.             $this->seek($s);
  1701.  
  1702.             return false;
  1703.         }
  1704.  
  1705.         $guards array();
  1706.  
  1707.         while ($this->guardGroup($g))
  1708.         {
  1709.             $guards[$g;
  1710.  
  1711.             if (!$this->literal(","))
  1712.             {
  1713.                 break;
  1714.             }
  1715.         }
  1716.  
  1717.         if (count($guards== 0)
  1718.         {
  1719.             $guards null;
  1720.             $this->seek($s);
  1721.  
  1722.             return false;
  1723.         }
  1724.  
  1725.         return true;
  1726.     }
  1727.  
  1728.     /**
  1729.      * A bunch of guards that are and'd together
  1730.      *
  1731.      * @param   [type]  &$guardGroup  [description]
  1732.      *
  1733.      * @todo rename to guardGroup
  1734.      *
  1735.      * @return  boolean 
  1736.      */
  1737.     protected function guardGroup(&$guardGroup)
  1738.     {
  1739.         $s $this->seek();
  1740.         $guardGroup array();
  1741.  
  1742.         while ($this->guard($guard))
  1743.         {
  1744.             $guardGroup[$guard;
  1745.  
  1746.             if (!$this->literal("and"))
  1747.             {
  1748.                 break;
  1749.             }
  1750.         }
  1751.  
  1752.         if (count($guardGroup== 0)
  1753.         {
  1754.             $guardGroup null;
  1755.             $this->seek($s);
  1756.  
  1757.             return false;
  1758.         }
  1759.  
  1760.         return true;
  1761.     }
  1762.  
  1763.     /**
  1764.      * [guard description]
  1765.      *
  1766.      * @param   [type]  &$guard  [description]
  1767.      *
  1768.      * @return  boolean 
  1769.      */
  1770.     protected function guard(&$guard)
  1771.     {
  1772.         $s $this->seek();
  1773.         $negate $this->literal("not");
  1774.  
  1775.         if ($this->literal("("&& $this->expression($exp&& $this->literal(")"))
  1776.         {
  1777.             $guard $exp;
  1778.  
  1779.             if ($negate)
  1780.             {
  1781.                 $guard array("negate"$guard);
  1782.             }
  1783.  
  1784.             return true;
  1785.         }
  1786.  
  1787.         $this->seek($s);
  1788.  
  1789.         return false;
  1790.     }
  1791.  
  1792.     /* raw parsing functions */
  1793.  
  1794.     /**
  1795.      * [literal description]
  1796.      *
  1797.      * @param   [type]  $what           [description]
  1798.      * @param   [type]  $eatWhitespace  [description]
  1799.      *
  1800.      * @return  boolean 
  1801.      */
  1802.     protected function literal($what$eatWhitespace null)
  1803.     {
  1804.         if ($eatWhitespace === null)
  1805.         {
  1806.             $eatWhitespace $this->eatWhiteDefault;
  1807.         }
  1808.  
  1809.         // Shortcut on single letter
  1810.         if (!isset($what[1]&& isset($this->buffer[$this->count]))
  1811.         {
  1812.             if ($this->buffer[$this->count== $what)
  1813.             {
  1814.                 if (!$eatWhitespace)
  1815.                 {
  1816.                     $this->count++;
  1817.  
  1818.                     return true;
  1819.                 }
  1820.             }
  1821.             else
  1822.             {
  1823.                 return false;
  1824.             }
  1825.         }
  1826.  
  1827.         if (!isset(self::$literalCache[$what]))
  1828.         {
  1829.             self::$literalCache[$whatFOFLess::preg_quote($what);
  1830.         }
  1831.  
  1832.         return $this->match(self::$literalCache[$what]$m$eatWhitespace);
  1833.     }
  1834.  
  1835.     /**
  1836.      * [genericList description]
  1837.      *
  1838.      * @param   [type]   &$out       [description]
  1839.      * @param   [type]   $parseItem  [description]
  1840.      * @param   string   $delim      [description]
  1841.      * @param   boolean  $flatten    [description]
  1842.      *
  1843.      * @return  boolean 
  1844.      */
  1845.     protected function genericList(&$out$parseItem$delim ""$flatten true)
  1846.     {
  1847.         $s $this->seek();
  1848.         $items array();
  1849.  
  1850.         while ($this->$parseItem($value))
  1851.         {
  1852.             $items[$value;
  1853.  
  1854.             if ($delim)
  1855.             {
  1856.                 if (!$this->literal($delim))
  1857.                 {
  1858.                     break;
  1859.                 }
  1860.             }
  1861.         }
  1862.  
  1863.         if (count($items== 0)
  1864.         {
  1865.             $this->seek($s);
  1866.  
  1867.             return false;
  1868.         }
  1869.  
  1870.         if ($flatten && count($items== 1)
  1871.         {
  1872.             $out $items[0];
  1873.         }
  1874.         else
  1875.         {
  1876.             $out array("list"$delim$items);
  1877.         }
  1878.  
  1879.         return true;
  1880.     }
  1881.  
  1882.     /**
  1883.      * Advance counter to next occurrence of $what
  1884.      * $until - don't include $what in advance
  1885.      * $allowNewline, if string, will be used as valid char set
  1886.      *
  1887.      * @param   [type]   $what          [description]
  1888.      * @param   [type]   &$out          [description]
  1889.      * @param   boolean  $until         [description]
  1890.      * @param   boolean  $allowNewline  [description]
  1891.      *
  1892.      * @return  boolean 
  1893.      */
  1894.     protected function to($what&$out$until false$allowNewline false)
  1895.     {
  1896.         if (is_string($allowNewline))
  1897.         {
  1898.             $validChars $allowNewline;
  1899.         }
  1900.         else
  1901.         {
  1902.             $validChars $allowNewline "." "[^\n]";
  1903.         }
  1904.  
  1905.         if (!$this->match('(' $validChars '*?)' FOFLess::preg_quote($what)$m!$until))
  1906.         {
  1907.             return false;
  1908.         }
  1909.  
  1910.         if ($until)
  1911.         {
  1912.             // Give back $what
  1913.             $this->count -= strlen($what);
  1914.         }
  1915.  
  1916.         $out $m[1];
  1917.  
  1918.         return true;
  1919.     }
  1920.  
  1921.     /**
  1922.      * Try to match something on head of buffer
  1923.      *
  1924.      * @param   [type]  $regex          [description]
  1925.      * @param   [type]  &$out           [description]
  1926.      * @param   [type]  $eatWhitespace  [description]
  1927.      *
  1928.      * @return  boolean 
  1929.      */
  1930.     protected function match($regex&$out$eatWhitespace null)
  1931.     {
  1932.         if ($eatWhitespace === null)
  1933.         {
  1934.             $eatWhitespace $this->eatWhiteDefault;
  1935.         }
  1936.  
  1937.         $r '/' $regex ($eatWhitespace && !$this->writeComments '\s*' '''/Ais';
  1938.  
  1939.         if (preg_match($r$this->buffer$outnull$this->count))
  1940.         {
  1941.             $this->count += strlen($out[0]);
  1942.  
  1943.             if ($eatWhitespace && $this->writeComments)
  1944.             {
  1945.                 $this->whitespace();
  1946.             }
  1947.  
  1948.             return true;
  1949.         }
  1950.  
  1951.         return false;
  1952.     }
  1953.  
  1954.     /**
  1955.      * Watch some whitespace
  1956.      *
  1957.      * @return  boolean 
  1958.      */
  1959.     protected function whitespace()
  1960.     {
  1961.         if ($this->writeComments)
  1962.         {
  1963.             $gotWhite false;
  1964.  
  1965.             while (preg_match(self::$whitePattern$this->buffer$mnull$this->count))
  1966.             {
  1967.                 if (isset($m[1]&& empty($this->commentsSeen[$this->count]))
  1968.                 {
  1969.                     $this->append(array("comment"$m[1]));
  1970.                     $this->commentsSeen[$this->counttrue;
  1971.                 }
  1972.  
  1973.                 $this->count += strlen($m[0]);
  1974.                 $gotWhite true;
  1975.             }
  1976.  
  1977.             return $gotWhite;
  1978.         }
  1979.         else
  1980.         {
  1981.             $this->match(""$m);
  1982.  
  1983.             return strlen($m[0]0;
  1984.         }
  1985.     }
  1986.  
  1987.     /**
  1988.      * Match something without consuming it
  1989.      *
  1990.      * @param   [type]  $regex  [description]
  1991.      * @param   [type]  &$out   [description]
  1992.      * @param   [type]  $from   [description]
  1993.      *
  1994.      * @return  boolean 
  1995.      */
  1996.     protected function peek($regex&$out null$from null)
  1997.     {
  1998.         if (is_null($from))
  1999.         {
  2000.             $from $this->count;
  2001.         }
  2002.  
  2003.         $r '/' $regex '/Ais';
  2004.         $result preg_match($r$this->buffer$outnull$from);
  2005.  
  2006.         return $result;
  2007.     }
  2008.  
  2009.     /**
  2010.      * Seek to a spot in the buffer or return where we are on no argument
  2011.      *
  2012.      * @param   [type]  $where  [description]
  2013.      *
  2014.      * @return  boolean 
  2015.      */
  2016.     protected function seek($where null)
  2017.     {
  2018.         if ($where === null)
  2019.         {
  2020.             return $this->count;
  2021.         }
  2022.         else
  2023.         {
  2024.             $this->count $where;
  2025.         }
  2026.  
  2027.         return true;
  2028.     }
  2029.  
  2030.     /* misc functions */
  2031.  
  2032.     /**
  2033.      * [throwError description]
  2034.      *
  2035.      * @param   string  $msg    [description]
  2036.      * @param   [type]  $count  [description]
  2037.      *
  2038.      * @return  void 
  2039.      */
  2040.     public function throwError($msg "parse error"$count null)
  2041.     {
  2042.         $count is_null($count$this->count $count;
  2043.  
  2044.         $line $this->line substr_count(substr($this->buffer0$count)"\n");
  2045.  
  2046.         if (!empty($this->sourceName))
  2047.         {
  2048.             $loc "$this->sourceName on line $line";
  2049.         }
  2050.         else
  2051.         {
  2052.             $loc "line: $line";
  2053.         }
  2054.  
  2055.         // TODO this depends on $this->count
  2056.         if ($this->peek("(.*?)(\n|$)"$m$count))
  2057.         {
  2058.             throw new exception("$msg: failed at `$m[1]$loc");
  2059.         }
  2060.         else
  2061.         {
  2062.             throw new exception("$msg$loc");
  2063.         }
  2064.     }
  2065.  
  2066.     /**
  2067.      * [pushBlock description]
  2068.      *
  2069.      * @param   [type]  $selectors  [description]
  2070.      * @param   [type]  $type       [description]
  2071.      *
  2072.      * @return  stdClass 
  2073.      */
  2074.     protected function pushBlock($selectors null$type null)
  2075.     {
  2076.         $b new stdclass;
  2077.         $b->parent $this->env;
  2078.  
  2079.         $b->type $type;
  2080.         $b->id self::$nextBlockId++;
  2081.  
  2082.         // TODO: kill me from here
  2083.         $b->isVararg false;
  2084.         $b->tags $selectors;
  2085.  
  2086.         $b->props array();
  2087.         $b->children array();
  2088.  
  2089.         $this->env $b;
  2090.  
  2091.         return $b;
  2092.     }
  2093.  
  2094.     /**
  2095.      * Push a block that doesn't multiply tags
  2096.      *
  2097.      * @param   [type]  $type  [description]
  2098.      *
  2099.      * @return  stdClass 
  2100.      */
  2101.     protected function pushSpecialBlock($type)
  2102.     {
  2103.         return $this->pushBlock(null$type);
  2104.     }
  2105.  
  2106.     /**
  2107.      * Append a property to the current block
  2108.      *
  2109.      * @param   [type]  $prop  [description]
  2110.      * @param   [type]  $pos   [description]
  2111.      *
  2112.      * @return  void 
  2113.      */
  2114.     protected function append($prop$pos null)
  2115.     {
  2116.         if ($pos !== null)
  2117.         {
  2118.             $prop[-1$pos;
  2119.         }
  2120.  
  2121.         $this->env->props[$prop;
  2122.     }
  2123.  
  2124.     /**
  2125.      * Pop something off the stack
  2126.      *
  2127.      * @return  [type]  [description]
  2128.      */
  2129.     protected function pop()
  2130.     {
  2131.         $old $this->env;
  2132.         $this->env $this->env->parent;
  2133.  
  2134.         return $old;
  2135.     }
  2136.  
  2137.     /**
  2138.      * Remove comments from $text
  2139.      *
  2140.      * @param   [type]  $text  [description]
  2141.      *
  2142.      * @todo: make it work for all functions, not just url
  2143.      *
  2144.      * @return  [type]         [description]
  2145.      */
  2146.     protected function removeComments($text)
  2147.     {
  2148.         $look array(
  2149.             'url(''//''/*''"'"'"
  2150.         );
  2151.  
  2152.         $out '';
  2153.         $min null;
  2154.  
  2155.         while (true)
  2156.         {
  2157.             // Find the next item
  2158.             foreach ($look as $token)
  2159.             {
  2160.                 $pos strpos($text$token);
  2161.  
  2162.                 if ($pos !== false)
  2163.                 {
  2164.                     if (!isset($min|| $pos $min[1])
  2165.                     {
  2166.                         $min array($token$pos);
  2167.                     }
  2168.                 }
  2169.             }
  2170.  
  2171.             if (is_null($min))
  2172.                 break;
  2173.  
  2174.             $count $min[1];
  2175.             $skip 0;
  2176.             $newlines 0;
  2177.  
  2178.             switch ($min[0])
  2179.             {
  2180.                 case 'url(':
  2181.  
  2182.                     if (preg_match('/url\(.*?\)/'$text$m0$count))
  2183.                     {
  2184.                         $count += strlen($m[0]strlen($min[0]);
  2185.                     }
  2186.  
  2187.                     break;
  2188.                 case '"':
  2189.                 case "'":
  2190.  
  2191.                     if (preg_match('/' $min[0'.*?' $min[0'/'$text$m0$count))
  2192.                     {
  2193.                         $count += strlen($m[0]1;
  2194.                     }
  2195.  
  2196.                     break;
  2197.                 case '//':
  2198.                     $skip strpos($text"\n"$count);
  2199.  
  2200.                     if ($skip === false)
  2201.                     {
  2202.                         $skip strlen($text$count;
  2203.                     }
  2204.                     else
  2205.                     {
  2206.                         $skip -= $count;
  2207.                     }
  2208.  
  2209.                     break;
  2210.                 case '/*':
  2211.  
  2212.                     if (preg_match('/\/\*.*?\*\//s'$text$m0$count))
  2213.                     {
  2214.                         $skip strlen($m[0]);
  2215.                         $newlines substr_count($m[0]"\n");
  2216.                     }
  2217.  
  2218.                     break;
  2219.             }
  2220.  
  2221.             if ($skip == 0)
  2222.             {
  2223.                 $count += strlen($min[0]);
  2224.             }
  2225.  
  2226.             $out .= substr($text0$countstr_repeat("\n"$newlines);
  2227.             $text substr($text$count $skip);
  2228.  
  2229.             $min null;
  2230.         }
  2231.  
  2232.         return $out $text;
  2233.     }
  2234. }

Documentation generated on Tue, 19 Nov 2013 15:10:16 +0100 by phpDocumentor 1.4.3