LeafoScssPhpCompiler

SCSS compiler.

Defined (1)

The class is defined in the following location(s).

/inc/ReduxFramework/ReduxCore/inc/scssphp/src/Compiler.php  
  1. class Compiler 
  2. static protected $operatorNames = array( 
  3. '+' => 'add',  
  4. '-' => 'sub',  
  5. '*' => 'mul',  
  6. '/' => 'div',  
  7. '%' => 'mod',  
  8.  
  9. '==' => 'eq',  
  10. '!=' => 'neq',  
  11. '<' => 'lt',  
  12. '>' => 'gt',  
  13.  
  14. '<=' => 'lte',  
  15. '>=' => 'gte',  
  16. ); 
  17.  
  18. static protected $namespaces = array( 
  19. 'special' => '%',  
  20. 'mixin' => '@',  
  21. 'function' => '^',  
  22. ); 
  23.  
  24. static protected $unitTable = array( 
  25. 'in' => array( 
  26. 'in' => 1,  
  27. 'pt' => 72,  
  28. 'pc' => 6,  
  29. 'cm' => 2.54,  
  30. 'mm' => 25.4,  
  31. 'px' => 96,  
  32. ); 
  33.  
  34. static public $true = array('keyword', 'true'); 
  35. static public $false = array('keyword', 'false'); 
  36. static public $null = array('null'); 
  37.  
  38. static public $defaultValue = array('keyword', ''); 
  39. static public $selfSelector = array('self'); 
  40.  
  41. protected $importPaths = array(''); 
  42. protected $importCache = array(); 
  43.  
  44. protected $userFunctions = array(); 
  45. protected $registeredVars = array(); 
  46.  
  47. protected $numberPrecision = 5; 
  48.  
  49. protected $formatter = 'Leafo\ScssPhp\Formatter\Nested'; 
  50.  
  51. /** 
  52. * Compile scss 
  53. * @param string $code 
  54. * @param string $name 
  55. * @return string 
  56. */ 
  57. public function compile($code, $name = null) 
  58. $this->indentLevel = -1; 
  59. $this->commentsSeen = array(); 
  60. $this->extends = array(); 
  61. $this->extendsMap = array(); 
  62. $this->parsedFiles = array(); 
  63. $this->env = null; 
  64. $this->scope = null; 
  65.  
  66. $locale = setlocale(LC_NUMERIC, 0); 
  67. setlocale(LC_NUMERIC, 'C'); 
  68.  
  69. $this->parser = new Parser($name); 
  70.  
  71. $tree = $this->parser->parse($code); 
  72.  
  73. $this->formatter = new $this->formatter(); 
  74.  
  75. $this->pushEnv($tree); 
  76. $this->injectVariables($this->registeredVars); 
  77. $this->compileRoot($tree); 
  78. $this->popEnv(); 
  79.  
  80. $out = $this->formatter->format($this->scope); 
  81.  
  82. setlocale(LC_NUMERIC, $locale); 
  83.  
  84. return $out; 
  85.  
  86. protected function isSelfExtend($target, $origin) 
  87. foreach ($origin as $sel) { 
  88. if (in_array($target, $sel)) { 
  89. return true; 
  90.  
  91. return false; 
  92.  
  93. protected function pushExtends($target, $origin) 
  94. if ($this->isSelfExtend($target, $origin)) { 
  95. return; 
  96.  
  97. $i = count($this->extends); 
  98. $this->extends[] = array($target, $origin); 
  99.  
  100. foreach ($target as $part) { 
  101. if (isset($this->extendsMap[$part])) { 
  102. $this->extendsMap[$part][] = $i; 
  103. } else { 
  104. $this->extendsMap[$part] = array($i); 
  105.  
  106. protected function makeOutputBlock($type, $selectors = null) 
  107. $out = new \stdClass; 
  108. $out->type = $type; 
  109. $out->lines = array(); 
  110. $out->children = array(); 
  111. $out->parent = $this->scope; 
  112. $out->selectors = $selectors; 
  113. $out->depth = $this->env->depth; 
  114.  
  115. return $out; 
  116.  
  117. protected function matchExtendsSingle($single, &$outOrigin) 
  118. $counts = array(); 
  119. foreach ($single as $part) { 
  120. if (!is_string($part)) { 
  121. return false; // hmm 
  122.  
  123. if (isset($this->extendsMap[$part])) { 
  124. foreach ($this->extendsMap[$part] as $idx) { 
  125. $counts[$idx] = 
  126. isset($counts[$idx]) ? $counts[$idx] + 1 : 1; 
  127.  
  128. $outOrigin = array(); 
  129. $found = false; 
  130.  
  131. foreach ($counts as $idx => $count) { 
  132. list($target, $origin) = $this->extends[$idx]; 
  133.  
  134. // check count 
  135. if ($count != count($target)) { 
  136. continue; 
  137.  
  138. // check if target is subset of single 
  139. if (array_diff(array_intersect($single, $target), $target)) { 
  140. continue; 
  141.  
  142. $rem = array_diff($single, $target); 
  143.  
  144. foreach ($origin as $j => $new) { 
  145. // prevent infinite loop when target extends itself 
  146. foreach ($new as $new_selector) { 
  147. if (!array_diff($single, $new_selector)) { 
  148. continue 2; 
  149.  
  150. $origin[$j][count($origin[$j]) - 1] = $this->combineSelectorSingle(end($new), $rem); 
  151.  
  152. $outOrigin = array_merge($outOrigin, $origin); 
  153.  
  154. $found = true; 
  155.  
  156. return $found; 
  157.  
  158. protected function combineSelectorSingle($base, $other) 
  159. $tag = null; 
  160. $out = array(); 
  161.  
  162. foreach (array($base, $other) as $single) { 
  163. foreach ($single as $part) { 
  164. if (preg_match('/^[^\[.#:]/', $part)) { 
  165. $tag = $part; 
  166. } else { 
  167. $out[] = $part; 
  168.  
  169. if ($tag) { 
  170. array_unshift($out, $tag); 
  171.  
  172. return $out; 
  173.  
  174. protected function matchExtends($selector, &$out, $from = 0, $initial = true) 
  175. foreach ($selector as $i => $part) { 
  176. if ($i < $from) { 
  177. continue; 
  178.  
  179. if ($this->matchExtendsSingle($part, $origin)) { 
  180. $before = array_slice($selector, 0, $i); 
  181. $after = array_slice($selector, $i + 1); 
  182.  
  183. foreach ($origin as $new) { 
  184. $k = 0; 
  185.  
  186. // remove shared parts 
  187. if ($initial) { 
  188. foreach ($before as $k => $val) { 
  189. if (!isset($new[$k]) || $val != $new[$k]) { 
  190. break; 
  191.  
  192. $result = array_merge( 
  193. $before,  
  194. $k > 0 ? array_slice($new, $k) : $new,  
  195. $after 
  196. ); 
  197.  
  198. if ($result == $selector) { 
  199. continue; 
  200.  
  201. $out[] = $result; 
  202.  
  203. // recursively check for more matches 
  204. $this->matchExtends($result, $out, $i, false); 
  205.  
  206. // selector sequence merging 
  207. if (!empty($before) && count($new) > 1) { 
  208. $result2 = array_merge( 
  209. array_slice($new, 0, -1),  
  210. $k > 0 ? array_slice($before, $k) : $before,  
  211. array_slice($new, -1),  
  212. $after 
  213. ); 
  214.  
  215. $out[] = $result2; 
  216.  
  217. protected function flattenSelectors($block, $parentKey = null) 
  218. if ($block->selectors) { 
  219. $selectors = array(); 
  220.  
  221. foreach ($block->selectors as $s) { 
  222. $selectors[] = $s; 
  223.  
  224. if (!is_array($s)) { 
  225. continue; 
  226.  
  227. // check extends 
  228. if (!empty($this->extendsMap)) { 
  229. $this->matchExtends($s, $selectors); 
  230.  
  231. $block->selectors = array(); 
  232. $placeholderSelector = false; 
  233.  
  234. foreach ($selectors as $selector) { 
  235. if ($this->hasSelectorPlaceholder($selector)) { 
  236. $placeholderSelector = true; 
  237. continue; 
  238.  
  239. $block->selectors[] = $this->compileSelector($selector); 
  240.  
  241. if ($placeholderSelector && 0 == count($block->selectors) && null !== $parentKey) { 
  242. unset($block->parent->children[$parentKey]); 
  243.  
  244. return; 
  245.  
  246. foreach ($block->children as $key => $child) { 
  247. $this->flattenSelectors($child, $key); 
  248.  
  249. protected function compileRoot($rootBlock) 
  250. $this->scope = $this->makeOutputBlock('root'); 
  251.  
  252. $this->compileChildren($rootBlock->children, $this->scope); 
  253. $this->flattenSelectors($this->scope); 
  254.  
  255. protected function compileMedia($media) 
  256. $this->pushEnv($media); 
  257.  
  258. $mediaQuery = $this->compileMediaQuery($this->multiplyMedia($this->env)); 
  259.  
  260. if (!empty($mediaQuery)) { 
  261. $this->scope = $this->makeOutputBlock('media', array($mediaQuery)); 
  262.  
  263. $parentScope = $this->mediaParent($this->scope); 
  264. $parentScope->children[] = $this->scope; 
  265.  
  266. // top level properties in a media cause it to be wrapped 
  267. $needsWrap = false; 
  268.  
  269. foreach ($media->children as $child) { 
  270. $type = $child[0]; 
  271. if ($type !== 'block' && $type !== 'media' && $type !== 'directive') { 
  272. $needsWrap = true; 
  273. break; 
  274.  
  275. if ($needsWrap) { 
  276. $wrapped = (object)array( 
  277. 'selectors' => array(),  
  278. 'children' => $media->children 
  279. ); 
  280. $media->children = array(array('block', $wrapped)); 
  281.  
  282. $this->compileChildren($media->children, $this->scope); 
  283.  
  284. $this->scope = $this->scope->parent; 
  285.  
  286. $this->popEnv(); 
  287.  
  288. protected function mediaParent($scope) 
  289. while (!empty($scope->parent)) { 
  290. if (!empty($scope->type) && $scope->type != 'media') { 
  291. break; 
  292. $scope = $scope->parent; 
  293.  
  294. return $scope; 
  295.  
  296. // TODO: refactor compileNestedBlock and compileMedia into same thing? 
  297. protected function compileNestedBlock($block, $selectors) 
  298. $this->pushEnv($block); 
  299.  
  300. $this->scope = $this->makeOutputBlock($block->type, $selectors); 
  301. $this->scope->parent->children[] = $this->scope; 
  302. $this->compileChildren($block->children, $this->scope); 
  303.  
  304. $this->scope = $this->scope->parent; 
  305. $this->popEnv(); 
  306.  
  307. /** 
  308. * Recursively compiles a block. 
  309. * A block is analogous to a CSS block in most cases. A single SCSS document 
  310. * is encapsulated in a block when parsed, but it does not have parent tags 
  311. * so all of its children appear on the root level when compiled. 
  312. * Blocks are made up of selectors and children. 
  313. * The children of a block are just all the blocks that are defined within. 
  314. * Compiling the block involves pushing a fresh environment on the stack,  
  315. * and iterating through the props, compiling each one. 
  316. * @see Compiler::compileChild() 
  317. * @param \StdClass $block 
  318. */ 
  319. protected function compileBlock($block) 
  320. $env = $this->pushEnv($block); 
  321.  
  322. $env->selectors = 
  323. array_map(array($this, 'evalSelector'), $block->selectors); 
  324.  
  325. $out = $this->makeOutputBlock(null, $this->multiplySelectors($env)); 
  326. $this->scope->children[] = $out; 
  327. $this->compileChildren($block->children, $out); 
  328.  
  329. $this->popEnv(); 
  330.  
  331. // root level comment 
  332. protected function compileComment($block) 
  333. $out = $this->makeOutputBlock('comment'); 
  334. $out->lines[] = $block[1]; 
  335. $this->scope->children[] = $out; 
  336.  
  337. // joins together .classes and #ids 
  338. protected function flattenSelectorSingle($single) 
  339. $joined = array(); 
  340. foreach ($single as $part) { 
  341. if (empty($joined) || 
  342. !is_string($part) || 
  343. preg_match('/[\[.:#%]/', $part) 
  344. ) { 
  345. $joined[] = $part; 
  346. continue; 
  347.  
  348. if (is_array(end($joined))) { 
  349. $joined[] = $part; 
  350. } else { 
  351. $joined[count($joined) - 1] .= $part; 
  352.  
  353. return $joined; 
  354.  
  355. // replaces all the interpolates 
  356. protected function evalSelector($selector) 
  357. return array_map(array($this, 'evalSelectorPart'), $selector); 
  358.  
  359. protected function evalSelectorPart($piece) 
  360. foreach ($piece as &$p) { 
  361. if (!is_array($p)) { 
  362. continue; 
  363.  
  364. switch ($p[0]) { 
  365. case 'interpolate': 
  366. $p = $this->compileValue($p); 
  367. break; 
  368. case 'string': 
  369. $p = $this->compileValue($p); 
  370. break; 
  371.  
  372. return $this->flattenSelectorSingle($piece); 
  373.  
  374. // compiles to string 
  375. // self(&) should have been replaced by now 
  376. protected function compileSelector($selector) 
  377. if (!is_array($selector)) { 
  378. return $selector; // media and the like 
  379.  
  380. return implode( 
  381. ' ',  
  382. array_map( 
  383. array($this, 'compileSelectorPart'),  
  384. $selector 
  385. ); 
  386.  
  387. protected function compileSelectorPart($piece) 
  388. foreach ($piece as &$p) { 
  389. if (!is_array($p)) { 
  390. continue; 
  391.  
  392. switch ($p[0]) { 
  393. case 'self': 
  394. $p = '&'; 
  395. break; 
  396. default: 
  397. $p = $this->compileValue($p); 
  398. break; 
  399.  
  400. return implode($piece); 
  401.  
  402. protected function hasSelectorPlaceholder($selector) 
  403. if (!is_array($selector)) { 
  404. return false; 
  405.  
  406. foreach ($selector as $parts) { 
  407. foreach ($parts as $part) { 
  408. if ('%' == $part[0]) { 
  409. return true; 
  410.  
  411. return false; 
  412.  
  413. protected function compileChildren($stms, $out) 
  414. foreach ($stms as $stm) { 
  415. $ret = $this->compileChild($stm, $out); 
  416.  
  417. if (isset($ret)) { 
  418. return $ret; 
  419.  
  420. protected function compileMediaQuery($queryList) 
  421. $out = '@media'; 
  422. $first = true; 
  423.  
  424. foreach ($queryList as $query) { 
  425. $type = null; 
  426. $parts = array(); 
  427.  
  428. foreach ($query as $q) { 
  429. switch ($q[0]) { 
  430. case 'mediaType': 
  431. if ($type) { 
  432. $type = $this->mergeMediaTypes( 
  433. $type,  
  434. array_map(array($this, 'compileValue'), array_slice($q, 1)) 
  435. ); 
  436.  
  437. if (empty($type)) { // merge failed 
  438. return null; 
  439. } else { 
  440. $type = array_map(array($this, 'compileValue'), array_slice($q, 1)); 
  441. break; 
  442. case 'mediaExp': 
  443. if (isset($q[2])) { 
  444. $parts[] = '(' 
  445. . $this->compileValue($q[1]) 
  446. . $this->formatter->assignSeparator 
  447. . $this->compileValue($q[2]) 
  448. . ')'; 
  449. } else { 
  450. $parts[] = '(' 
  451. . $this->compileValue($q[1]) 
  452. . ')'; 
  453. break; 
  454.  
  455. if ($type) { 
  456. array_unshift($parts, implode(' ', array_filter($type))); 
  457.  
  458. if (!empty($parts)) { 
  459. if ($first) { 
  460. $first = false; 
  461. $out .= ' '; 
  462. } else { 
  463. $out .= $this->formatter->tagSeparator; 
  464.  
  465. $out .= implode(' and ', $parts); 
  466.  
  467. return $out; 
  468.  
  469. protected function mergeMediaTypes($type1, $type2) 
  470. if (empty($type1)) { 
  471. return $type2; 
  472.  
  473. if (empty($type2)) { 
  474. return $type1; 
  475.  
  476. $m1 = ''; 
  477. $t1 = ''; 
  478.  
  479. if (count($type1) > 1) { 
  480. $m1= strtolower($type1[0]); 
  481. $t1= strtolower($type1[1]); 
  482. } else { 
  483. $t1 = strtolower($type1[0]); 
  484.  
  485. $m2 = ''; 
  486. $t2 = ''; 
  487.  
  488. if (count($type2) > 1) { 
  489. $m2 = strtolower($type2[0]); 
  490. $t2 = strtolower($type2[1]); 
  491. } else { 
  492. $t2 = strtolower($type2[0]); 
  493.  
  494. if (($m1 == 'not') ^ ($m2 == 'not')) { 
  495. if ($t1 == $t2) { 
  496. return null; 
  497.  
  498. return array( 
  499. $m1 == 'not' ? $m2 : $m1,  
  500. $m1 == 'not' ? $t2 : $t1 
  501. ); 
  502.  
  503. if ($m1 == 'not' && $m2 == 'not') { 
  504. # CSS has no way of representing "neither screen nor print" 
  505. if ($t1 != $t2) { 
  506. return null; 
  507.  
  508. return array('not', $t1); 
  509.  
  510. if ($t1 != $t2) { 
  511. return null; 
  512.  
  513. // t1 == t2, neither m1 nor m2 are "not" 
  514. return array(empty($m1)? $m2 : $m1, $t1); 
  515.  
  516. // returns true if the value was something that could be imported 
  517. protected function compileImport($rawPath, $out) 
  518. if ($rawPath[0] == 'string') { 
  519. $path = $this->compileStringContent($rawPath); 
  520.  
  521. if ($path = $this->findImport($path)) { 
  522. $this->importFile($path, $out); 
  523.  
  524. return true; 
  525.  
  526. return false; 
  527. if ($rawPath[0] == 'list') { 
  528. // handle a list of strings 
  529. if (count($rawPath[2]) == 0) { 
  530. return false; 
  531.  
  532. foreach ($rawPath[2] as $path) { 
  533. if ($path[0] != 'string') { 
  534. return false; 
  535.  
  536. foreach ($rawPath[2] as $path) { 
  537. $this->compileImport($path, $out); 
  538.  
  539. return true; 
  540.  
  541. return false; 
  542.  
  543. // return a value to halt execution 
  544. protected function compileChild($child, $out) 
  545. $this->sourcePos = isset($child[-1]) ? $child[-1] : -1; 
  546. $this->sourceParser = isset($child[-2]) ? $child[-2] : $this->parser; 
  547.  
  548. switch ($child[0]) { 
  549. case 'import': 
  550. list(, $rawPath) = $child; 
  551.  
  552. $rawPath = $this->reduce($rawPath); 
  553. if (!$this->compileImport($rawPath, $out)) { 
  554. $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';'; 
  555. break; 
  556. case 'directive': 
  557. list(, $directive) = $child; 
  558.  
  559. $s = '@' . $directive->name; 
  560. if (!empty($directive->value)) { 
  561. $s .= ' ' . $this->compileValue($directive->value); 
  562. $this->compileNestedBlock($directive, array($s)); 
  563. break; 
  564. case 'media': 
  565. $this->compileMedia($child[1]); 
  566. break; 
  567. case 'block': 
  568. $this->compileBlock($child[1]); 
  569. break; 
  570. case 'charset': 
  571. $out->lines[] = '@charset '.$this->compileValue($child[1]).';'; 
  572. break; 
  573. case 'assign': 
  574. list(, $name, $value) = $child; 
  575.  
  576. if ($name[0] == 'var') { 
  577. $isDefault = !empty($child[3]); 
  578.  
  579. if ($isDefault) { 
  580. $existingValue = $this->get($name[1], true); 
  581. $shouldSet = $existingValue === true || $existingValue == self::$null; 
  582.  
  583. if (! $isDefault || $shouldSet) { 
  584. $this->set($name[1], $this->reduce($value)); 
  585. break; 
  586.  
  587. // if the value reduces to null from something else then 
  588. // the property should be discarded 
  589. if ($value[0] != 'null') { 
  590. $value = $this->reduce($value); 
  591. if ($value[0] == 'null') { 
  592. break; 
  593.  
  594. $compiledValue = $this->compileValue($value); 
  595. $out->lines[] = $this->formatter->property( 
  596. $this->compileValue($name),  
  597. $compiledValue 
  598. ); 
  599. break; 
  600. case 'comment': 
  601. if ($out->type == 'root') { 
  602. $this->compileComment($child); 
  603. break; 
  604.  
  605. $out->lines[] = $child[1]; 
  606. break; 
  607. case 'mixin': 
  608. case 'function': 
  609. list(, $block) = $child; 
  610.  
  611. $this->set(self::$namespaces[$block->type] . $block->name, $block); 
  612. break; 
  613. case 'extend': 
  614. list(, $selectors) = $child; 
  615.  
  616. foreach ($selectors as $sel) { 
  617. // only use the first one 
  618. $sel = current($this->evalSelector($sel)); 
  619. $this->pushExtends($sel, $out->selectors); 
  620. break; 
  621. case 'if': 
  622. list(, $if) = $child; 
  623.  
  624. if ($this->isTruthy($this->reduce($if->cond, true))) { 
  625. return $this->compileChildren($if->children, $out); 
  626. } else { 
  627. foreach ($if->cases as $case) { 
  628. if ($case->type == 'else' || 
  629. $case->type == 'elseif' && $this->isTruthy($this->reduce($case->cond)) 
  630. ) { 
  631. return $this->compileChildren($case->children, $out); 
  632. break; 
  633. case 'return': 
  634. return $this->reduce($child[1], true); 
  635. case 'each': 
  636. list(, $each) = $child; 
  637.  
  638. $list = $this->coerceList($this->reduce($each->list)); 
  639. foreach ($list[2] as $item) { 
  640. $this->pushEnv(); 
  641. $this->set($each->var, $item); 
  642. // TODO: allow return from here? 
  643. $this->compileChildren($each->children, $out); 
  644. $this->popEnv(); 
  645. break; 
  646. case 'while': 
  647. list(, $while) = $child; 
  648.  
  649. while ($this->isTruthy($this->reduce($while->cond, true))) { 
  650. $ret = $this->compileChildren($while->children, $out); 
  651. if ($ret) { 
  652. return $ret; 
  653. break; 
  654. case 'for': 
  655. list(, $for) = $child; 
  656.  
  657. $start = $this->reduce($for->start, true); 
  658. $start = $start[1]; 
  659. $end = $this->reduce($for->end, true); 
  660. $end = $end[1]; 
  661. $d = $start < $end ? 1 : -1; 
  662.  
  663. while (true) { 
  664. if ((! $for->until && $start - $d == $end) || 
  665. ($for->until && $start == $end) 
  666. ) { 
  667. break; 
  668.  
  669. $this->set($for->var, array('number', $start, '')); 
  670. $start += $d; 
  671.  
  672. $ret = $this->compileChildren($for->children, $out); 
  673.  
  674. if ($ret) { 
  675. return $ret; 
  676.  
  677. break; 
  678. case 'nestedprop': 
  679. list(, $prop) = $child; 
  680.  
  681. $prefixed = array(); 
  682. $prefix = $this->compileValue($prop->prefix) . '-'; 
  683.  
  684. foreach ($prop->children as $child) { 
  685. if ($child[0] == 'assign') { 
  686. array_unshift($child[1][2], $prefix); 
  687.  
  688. if ($child[0] == 'nestedprop') { 
  689. array_unshift($child[1]->prefix[2], $prefix); 
  690.  
  691. $prefixed[] = $child; 
  692.  
  693. $this->compileChildren($prefixed, $out); 
  694. break; 
  695. case 'include': // including a mixin 
  696. list(, $name, $argValues, $content) = $child; 
  697.  
  698. $mixin = $this->get(self::$namespaces['mixin'] . $name, false); 
  699.  
  700. if (! $mixin) { 
  701. $this->throwError("Undefined mixin $name"); 
  702.  
  703. $callingScope = $this->env; 
  704.  
  705. // push scope, apply args 
  706. $this->pushEnv(); 
  707.  
  708. if ($this->env->depth > 0) { 
  709. $this->env->depth--; 
  710.  
  711. if (isset($content)) { 
  712. $content->scope = $callingScope; 
  713. $this->setRaw(self::$namespaces['special'] . 'content', $content); 
  714.  
  715. if (isset($mixin->args)) { 
  716. $this->applyArguments($mixin->args, $argValues); 
  717.  
  718. foreach ($mixin->children as $child) { 
  719. $this->compileChild($child, $out); 
  720.  
  721. $this->popEnv(); 
  722. break; 
  723. case 'mixin_content': 
  724. $content = $this->get(self::$namespaces['special'] . 'content'); 
  725.  
  726. if (!isset($content)) { 
  727. $this->throwError('Expected @content inside of mixin'); 
  728.  
  729. $strongTypes = array('include', 'block', 'for', 'while'); 
  730.  
  731. foreach ($content->children as $child) { 
  732. $this->storeEnv = (in_array($child[0], $strongTypes)) 
  733. ? null 
  734. : $content->scope; 
  735.  
  736. $this->compileChild($child, $out); 
  737.  
  738. unset($this->storeEnv); 
  739. break; 
  740. case 'debug': 
  741. list(, $value, $pos) = $child; 
  742.  
  743. $line = $this->parser->getLineNo($pos); 
  744. $value = $this->compileValue($this->reduce($value, true)); 
  745. fwrite(STDERR, "Line $line DEBUG: $value\n"); 
  746. break; 
  747. default: 
  748. $this->throwError("unknown child type: $child[0]"); 
  749.  
  750. protected function expToString($exp) 
  751. list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp; 
  752.  
  753. $content = array($this->reduce($left)); 
  754.  
  755. if ($whiteLeft) { 
  756. $content[] = ' '; 
  757.  
  758. $content[] = $op; 
  759.  
  760. if ($whiteRight) { 
  761. $content[] = ' '; 
  762.  
  763. $content[] = $this->reduce($right); 
  764.  
  765. return array('string', '', $content); 
  766.  
  767. protected function isTruthy($value) 
  768. return $value != self::$false && $value != self::$null; 
  769.  
  770. // should $value cause its operand to eval 
  771. protected function shouldEval($value) 
  772. switch ($value[0]) { 
  773. case 'exp': 
  774. if ($value[1] == '/') { 
  775. return $this->shouldEval($value[2], $value[3]); 
  776.  
  777. // fall-thru 
  778. case 'var': 
  779. case 'fncall': 
  780. return true; 
  781. return false; 
  782.  
  783. protected function reduce($value, $inExp = false) 
  784. list($type) = $value; 
  785. switch ($type) { 
  786. case 'exp': 
  787. list(, $op, $left, $right, $inParens) = $value; 
  788.  
  789. $opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op; 
  790. $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right); 
  791.  
  792. $left = $this->reduce($left, true); 
  793. $right = $this->reduce($right, true); 
  794.  
  795. // only do division in special cases 
  796. if ($opName == 'div' && !$inParens && !$inExp) { 
  797. if ($left[0] != 'color' && $right[0] != 'color') { 
  798. return $this->expToString($value); 
  799.  
  800. $left = $this->coerceForExpression($left); 
  801. $right = $this->coerceForExpression($right); 
  802.  
  803. $ltype = $left[0]; 
  804. $rtype = $right[0]; 
  805.  
  806. $ucOpName = ucfirst($opName); 
  807. $ucLType = ucfirst($ltype); 
  808. $ucRType = ucfirst($rtype); 
  809.  
  810. // this tries: 
  811. // 1. op[op name][left type][right type] 
  812. // 2. op[left type][right type] (passing the op as first arg 
  813. // 3. op[op name] 
  814. $fn = "op${ucOpName}${ucLType}${ucRType}"; 
  815. if (is_callable(array($this, $fn)) || 
  816. (($fn = "op${ucLType}${ucRType}") && 
  817. is_callable(array($this, $fn)) && 
  818. $passOp = true) || 
  819. (($fn = "op${ucOpName}") && 
  820. is_callable(array($this, $fn)) && 
  821. $genOp = true) 
  822. ) { 
  823. $unitChange = false; 
  824.  
  825. if (!isset($genOp) && 
  826. $left[0] == 'number' && $right[0] == 'number' 
  827. ) { 
  828. if ($opName == 'mod' && $right[2] != '') { 
  829. $this->throwError("Cannot modulo by a number with units: $right[1]$right[2]."); 
  830.  
  831. $unitChange = true; 
  832. $emptyUnit = $left[2] == '' || $right[2] == ''; 
  833. $targetUnit = '' != $left[2] ? $left[2] : $right[2]; 
  834.  
  835. if ($opName != 'mul') { 
  836. $left[2] = '' != $left[2] ? $left[2] : $targetUnit; 
  837. $right[2] = '' != $right[2] ? $right[2] : $targetUnit; 
  838.  
  839. if ($opName != 'mod') { 
  840. $left = $this->normalizeNumber($left); 
  841. $right = $this->normalizeNumber($right); 
  842.  
  843. if ($opName == 'div' && !$emptyUnit && $left[2] == $right[2]) { 
  844. $targetUnit = ''; 
  845.  
  846. if ($opName == 'mul') { 
  847. $left[2] = '' != $left[2] ? $left[2] : $right[2]; 
  848. $right[2] = '' != $right[2] ? $right[2] : $left[2]; 
  849. } elseif ($opName == 'div' && $left[2] == $right[2]) { 
  850. $left[2] = ''; 
  851. $right[2] = ''; 
  852.  
  853. $shouldEval = $inParens || $inExp; 
  854.  
  855. if (isset($passOp)) { 
  856. $out = $this->$fn($op, $left, $right, $shouldEval); 
  857. } else { 
  858. $out = $this->$fn($left, $right, $shouldEval); 
  859.  
  860. if (isset($out)) { 
  861. if ($unitChange && $out[0] == 'number') { 
  862. $out = $this->coerceUnit($out, $targetUnit); 
  863.  
  864. return $out; 
  865.  
  866. return $this->expToString($value); 
  867. case 'unary': 
  868. list(, $op, $exp, $inParens) = $value; 
  869.  
  870. $inExp = $inExp || $this->shouldEval($exp); 
  871. $exp = $this->reduce($exp); 
  872.  
  873. if ($exp[0] == 'number') { 
  874. switch ($op) { 
  875. case '+': 
  876. return $exp; 
  877. case '-': 
  878. $exp[1] *= -1; 
  879.  
  880. return $exp; 
  881.  
  882. if ($op == 'not') { 
  883. if ($inExp || $inParens) { 
  884. if ($exp == self::$false) { 
  885. return self::$true; 
  886.  
  887. return self::$false; 
  888. } else { 
  889. $op = $op . ' '; 
  890.  
  891. return array('string', '', array($op, $exp)); 
  892. case 'var': 
  893. list(, $name) = $value; 
  894.  
  895. return $this->reduce($this->get($name)); 
  896. case 'list': 
  897. foreach ($value[2] as &$item) { 
  898. $item = $this->reduce($item); 
  899.  
  900. return $value; 
  901. case 'string': 
  902. foreach ($value[2] as &$item) { 
  903. if (is_array($item)) { 
  904. $item = $this->reduce($item); 
  905. return $value; 
  906. case 'interpolate': 
  907. $value[1] = $this->reduce($value[1]); 
  908.  
  909. return $value; 
  910. case 'fncall': 
  911. list(, $name, $argValues) = $value; 
  912.  
  913. // user defined function? 
  914. $func = $this->get(self::$namespaces['function'] . $name, false); 
  915.  
  916. if ($func) { 
  917. $this->pushEnv(); 
  918.  
  919. // set the args 
  920. if (isset($func->args)) { 
  921. $this->applyArguments($func->args, $argValues); 
  922.  
  923. // throw away lines and children 
  924. $tmp = (object)array( 
  925. 'lines' => array(),  
  926. 'children' => array() 
  927. ); 
  928.  
  929. $ret = $this->compileChildren($func->children, $tmp); 
  930.  
  931. $this->popEnv(); 
  932.  
  933. return !isset($ret) ? self::$defaultValue : $ret; 
  934.  
  935. // built in function 
  936. if ($this->callBuiltin($name, $argValues, $returnValue)) { 
  937. return $returnValue; 
  938.  
  939. // need to flatten the arguments into a list 
  940. $listArgs = array(); 
  941.  
  942. foreach ((array)$argValues as $arg) { 
  943. if (empty($arg[0])) { 
  944. $listArgs[] = $this->reduce($arg[1]); 
  945.  
  946. return array('function', $name, array('list', ', ', $listArgs)); 
  947. default: 
  948. return $value; 
  949.  
  950. public function normalizeValue($value) 
  951. $value = $this->coerceForExpression($this->reduce($value)); 
  952. list($type) = $value; 
  953.  
  954. switch ($type) { 
  955. case 'list': 
  956. $value = $this->extractInterpolation($value); 
  957.  
  958. if ($value[0] != 'list') { 
  959. return array('keyword', $this->compileValue($value)); 
  960.  
  961. foreach ($value[2] as $key => $item) { 
  962. $value[2][$key] = $this->normalizeValue($item); 
  963.  
  964. return $value; 
  965.  
  966. case 'number': 
  967. return $this->normalizeNumber($value); 
  968.  
  969. default: 
  970. return $value; 
  971.  
  972. // just does physical lengths for now 
  973. protected function normalizeNumber($number) 
  974. list(, $value, $unit) = $number; 
  975.  
  976. if (isset(self::$unitTable['in'][$unit])) { 
  977. $conv = self::$unitTable['in'][$unit]; 
  978.  
  979. return array('number', $value / $conv, 'in'); 
  980.  
  981. return $number; 
  982.  
  983. // $number should be normalized 
  984. protected function coerceUnit($number, $unit) 
  985. list(, $value, $baseUnit) = $number; 
  986.  
  987. if (isset(self::$unitTable[$baseUnit][$unit])) { 
  988. $value = $value * self::$unitTable[$baseUnit][$unit]; 
  989.  
  990. return array('number', $value, $unit); 
  991.  
  992. protected function opAddNumberNumber($left, $right) 
  993. return array('number', $left[1] + $right[1], $left[2]); 
  994.  
  995. protected function opMulNumberNumber($left, $right) 
  996. return array('number', $left[1] * $right[1], $left[2]); 
  997.  
  998. protected function opSubNumberNumber($left, $right) 
  999. return array('number', $left[1] - $right[1], $left[2]); 
  1000.  
  1001. protected function opDivNumberNumber($left, $right) 
  1002. return array('number', $left[1] / $right[1], $left[2]); 
  1003.  
  1004. protected function opModNumberNumber($left, $right) 
  1005. return array('number', $left[1] % $right[1], $left[2]); 
  1006.  
  1007. // adding strings 
  1008. protected function opAdd($left, $right) 
  1009. if ($strLeft = $this->coerceString($left)) { 
  1010. if ($right[0] == 'string') { 
  1011. $right[1] = ''; 
  1012.  
  1013. $strLeft[2][] = $right; 
  1014.  
  1015. return $strLeft; 
  1016.  
  1017. if ($strRight = $this->coerceString($right)) { 
  1018. if ($left[0] == 'string') { 
  1019. $left[1] = ''; 
  1020.  
  1021. array_unshift($strRight[2], $left); 
  1022.  
  1023. return $strRight; 
  1024.  
  1025. protected function opAnd($left, $right, $shouldEval) 
  1026. if (!$shouldEval) { 
  1027. return; 
  1028.  
  1029. if ($left != self::$false) { 
  1030. return $right; 
  1031.  
  1032. return $left; 
  1033.  
  1034. protected function opOr($left, $right, $shouldEval) 
  1035. if (!$shouldEval) { 
  1036. return; 
  1037.  
  1038. if ($left != self::$false) { 
  1039. return $left; 
  1040.  
  1041. return $right; 
  1042.  
  1043. protected function opColorColor($op, $left, $right) 
  1044. $out = array('color'); 
  1045.  
  1046. foreach (range(1, 3) as $i) { 
  1047. $lval = isset($left[$i]) ? $left[$i] : 0; 
  1048. $rval = isset($right[$i]) ? $right[$i] : 0; 
  1049.  
  1050. switch ($op) { 
  1051. case '+': 
  1052. $out[] = $lval + $rval; 
  1053. break; 
  1054. case '-': 
  1055. $out[] = $lval - $rval; 
  1056. break; 
  1057. case '*': 
  1058. $out[] = $lval * $rval; 
  1059. break; 
  1060. case '%': 
  1061. $out[] = $lval % $rval; 
  1062. break; 
  1063. case '/': 
  1064. if ($rval == 0) { 
  1065. $this->throwError("color: Can't divide by zero"); 
  1066. $out[] = (int) ($lval / $rval); 
  1067. break; 
  1068. case '==': 
  1069. return $this->opEq($left, $right); 
  1070. case '!=': 
  1071. return $this->opNeq($left, $right); 
  1072. default: 
  1073. $this->throwError("color: unknown op $op"); 
  1074.  
  1075. if (isset($left[4])) { 
  1076. $out[4] = $left[4]; 
  1077. } elseif (isset($right[4])) { 
  1078. $out[4] = $right[4]; 
  1079.  
  1080. return $this->fixColor($out); 
  1081.  
  1082. protected function opColorNumber($op, $left, $right) 
  1083. $value = $right[1]; 
  1084.  
  1085. return $this->opColorColor( 
  1086. $op,  
  1087. $left,  
  1088. array('color', $value, $value, $value) 
  1089. ); 
  1090.  
  1091. protected function opNumberColor($op, $left, $right) 
  1092. $value = $left[1]; 
  1093.  
  1094. return $this->opColorColor( 
  1095. $op,  
  1096. array('color', $value, $value, $value),  
  1097. $right 
  1098. ); 
  1099.  
  1100. protected function opEq($left, $right) 
  1101. if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) { 
  1102. $lStr[1] = ''; 
  1103. $rStr[1] = ''; 
  1104.  
  1105. return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr)); 
  1106.  
  1107. return $this->toBool($left == $right); 
  1108.  
  1109. protected function opNeq($left, $right) 
  1110. return $this->toBool($left != $right); 
  1111.  
  1112. protected function opGteNumberNumber($left, $right) 
  1113. return $this->toBool($left[1] >= $right[1]); 
  1114.  
  1115. protected function opGtNumberNumber($left, $right) 
  1116. return $this->toBool($left[1] > $right[1]); 
  1117.  
  1118. protected function opLteNumberNumber($left, $right) 
  1119. return $this->toBool($left[1] <= $right[1]); 
  1120.  
  1121. protected function opLtNumberNumber($left, $right) 
  1122. return $this->toBool($left[1] < $right[1]); 
  1123.  
  1124. public function toBool($thing) 
  1125. return $thing ? self::$true : self::$false; 
  1126.  
  1127. /** 
  1128. * Compiles a primitive value into a CSS property value. 
  1129. * Values in scssphp are typed by being wrapped in arrays, their format is 
  1130. * typically: 
  1131. * array(type, contents [, additional_contents]*) 
  1132. * The input is expected to be reduced. This function will not work on 
  1133. * things like expressions and variables. 
  1134. * @param array $value 
  1135. */ 
  1136. protected function compileValue($value) 
  1137. $value = $this->reduce($value); 
  1138.  
  1139. list($type) = $value; 
  1140.  
  1141. switch ($type) { 
  1142. case 'keyword': 
  1143. return $value[1]; 
  1144. case 'color': 
  1145. // [1] - red component (either number for a %) 
  1146. // [2] - green component 
  1147. // [3] - blue component 
  1148. // [4] - optional alpha component 
  1149. list(, $r, $g, $b) = $value; 
  1150.  
  1151. $r = round($r); 
  1152. $g = round($g); 
  1153. $b = round($b); 
  1154.  
  1155. if (count($value) == 5 && $value[4] != 1) { // rgba 
  1156. return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')'; 
  1157.  
  1158. $h = sprintf('#%02x%02x%02x', $r, $g, $b); 
  1159.  
  1160. // Converting hex color to short notation (e.g. #003399 to #039) 
  1161. if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { 
  1162. $h = '#' . $h[1] . $h[3] . $h[5]; 
  1163.  
  1164. return $h; 
  1165. case 'number': 
  1166. return round($value[1], $this->numberPrecision) . $value[2]; 
  1167. case 'string': 
  1168. return $value[1] . $this->compileStringContent($value) . $value[1]; 
  1169. case 'function': 
  1170. $args = !empty($value[2]) ? $this->compileValue($value[2]) : ''; 
  1171. return "$value[1]($args)"; 
  1172. case 'list': 
  1173. $value = $this->extractInterpolation($value); 
  1174.  
  1175. if ($value[0] != 'list') { 
  1176. return $this->compileValue($value); 
  1177.  
  1178. list(, $delim, $items) = $value; 
  1179.  
  1180. $filtered = array(); 
  1181. foreach ($items as $item) { 
  1182. if ($item[0] == 'null') { 
  1183. continue; 
  1184.  
  1185. $filtered[] = $this->compileValue($item); 
  1186.  
  1187. return implode("$delim ", $filtered); 
  1188. case 'interpolated': # node created by extractInterpolation 
  1189. list(, $interpolate, $left, $right) = $value; 
  1190. list(, , $whiteLeft, $whiteRight) = $interpolate; 
  1191.  
  1192. $left = count($left[2]) > 0 ? 
  1193. $this->compileValue($left).$whiteLeft : ''; 
  1194.  
  1195. $right = count($right[2]) > 0 ? 
  1196. $whiteRight.$this->compileValue($right) : ''; 
  1197.  
  1198. return $left.$this->compileValue($interpolate).$right; 
  1199.  
  1200. case 'interpolate': # raw parse node 
  1201. list(, $exp) = $value; 
  1202.  
  1203. // strip quotes if it's a string 
  1204. $reduced = $this->reduce($exp); 
  1205. switch ($reduced[0]) { 
  1206. case 'string': 
  1207. $reduced = array('keyword',  
  1208. $this->compileStringContent($reduced)); 
  1209. break; 
  1210. case 'null': 
  1211. $reduced = array('keyword', ''); 
  1212.  
  1213. return $this->compileValue($reduced); 
  1214. case 'null': 
  1215. return 'null'; 
  1216. default: 
  1217. $this->throwError("unknown value type: $type"); 
  1218.  
  1219. protected function compileStringContent($string) 
  1220. $parts = array(); 
  1221.  
  1222. foreach ($string[2] as $part) { 
  1223. if (is_array($part)) { 
  1224. $parts[] = $this->compileValue($part); 
  1225. } else { 
  1226. $parts[] = $part; 
  1227.  
  1228. return implode($parts); 
  1229.  
  1230. // doesn't need to be recursive, compileValue will handle that 
  1231. protected function extractInterpolation($list) 
  1232. $items = $list[2]; 
  1233.  
  1234. foreach ($items as $i => $item) { 
  1235. if ($item[0] == 'interpolate') { 
  1236. $before = array('list', $list[1], array_slice($items, 0, $i)); 
  1237. $after = array('list', $list[1], array_slice($items, $i + 1)); 
  1238.  
  1239. return array('interpolated', $item, $before, $after); 
  1240.  
  1241. return $list; 
  1242.  
  1243. // find the final set of selectors 
  1244. protected function multiplySelectors($env) 
  1245. $envs = array(); 
  1246.  
  1247. while (null !== $env) { 
  1248. if (!empty($env->selectors)) { 
  1249. $envs[] = $env; 
  1250.  
  1251. $env = $env->parent; 
  1252. }; 
  1253.  
  1254. $selectors = array(); 
  1255. $parentSelectors = array(array()); 
  1256.  
  1257. while ($env = array_pop($envs)) { 
  1258. $selectors = array(); 
  1259.  
  1260. foreach ($env->selectors as $selector) { 
  1261. foreach ($parentSelectors as $parent) { 
  1262. $selectors[] = $this->joinSelectors($parent, $selector); 
  1263.  
  1264. $parentSelectors = $selectors; 
  1265.  
  1266. return $selectors; 
  1267.  
  1268. // looks for & to replace, or append parent before child 
  1269. protected function joinSelectors($parent, $child) 
  1270. $setSelf = false; 
  1271. $out = array(); 
  1272.  
  1273. foreach ($child as $part) { 
  1274. $newPart = array(); 
  1275.  
  1276. foreach ($part as $p) { 
  1277. if ($p == self::$selfSelector) { 
  1278. $setSelf = true; 
  1279.  
  1280. foreach ($parent as $i => $parentPart) { 
  1281. if ($i > 0) { 
  1282. $out[] = $newPart; 
  1283. $newPart = array(); 
  1284.  
  1285. foreach ($parentPart as $pp) { 
  1286. $newPart[] = $pp; 
  1287. } else { 
  1288. $newPart[] = $p; 
  1289.  
  1290. $out[] = $newPart; 
  1291.  
  1292. return $setSelf ? $out : array_merge($parent, $child); 
  1293.  
  1294. protected function multiplyMedia($env, $childQueries = null) 
  1295. if (!isset($env) || 
  1296. !empty($env->block->type) && $env->block->type != 'media' 
  1297. ) { 
  1298. return $childQueries; 
  1299.  
  1300. // plain old block, skip 
  1301. if (empty($env->block->type)) { 
  1302. return $this->multiplyMedia($env->parent, $childQueries); 
  1303.  
  1304. $parentQueries = $env->block->queryList; 
  1305. if ($childQueries == null) { 
  1306. $childQueries = $parentQueries; 
  1307. } else { 
  1308. $originalQueries = $childQueries; 
  1309. $childQueries = array(); 
  1310.  
  1311. foreach ($parentQueries as $parentQuery) { 
  1312. foreach ($originalQueries as $childQuery) { 
  1313. $childQueries []= array_merge($parentQuery, $childQuery); 
  1314.  
  1315. return $this->multiplyMedia($env->parent, $childQueries); 
  1316.  
  1317. // convert something to list 
  1318. protected function coerceList($item, $delim = ', ') 
  1319. if (isset($item) && $item[0] == 'list') { 
  1320. return $item; 
  1321.  
  1322. return array('list', $delim, !isset($item) ? array(): array($item)); 
  1323.  
  1324. protected function applyArguments($argDef, $argValues) 
  1325. $storeEnv = $this->getStoreEnv(); 
  1326.  
  1327. $env = new \stdClass; 
  1328. $env->store = $storeEnv->store; 
  1329.  
  1330. $hasVariable = false; 
  1331. $args = array(); 
  1332.  
  1333. foreach ($argDef as $i => $arg) { 
  1334. list($name, $default, $isVariable) = $argDef[$i]; 
  1335.  
  1336. $args[$name] = array($i, $name, $default, $isVariable); 
  1337. $hasVariable |= $isVariable; 
  1338.  
  1339. $keywordArgs = array(); 
  1340. $deferredKeywordArgs = array(); 
  1341. $remaining = array(); 
  1342.  
  1343. // assign the keyword args 
  1344. foreach ((array) $argValues as $arg) { 
  1345. if (!empty($arg[0])) { 
  1346. if (!isset($args[$arg[0][1]])) { 
  1347. if ($hasVariable) { 
  1348. $deferredKeywordArgs[$arg[0][1]] = $arg[1]; 
  1349. } else { 
  1350. $this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]); 
  1351. } elseif ($args[$arg[0][1]][0] < count($remaining)) { 
  1352. $this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]); 
  1353. } else { 
  1354. $keywordArgs[$arg[0][1]] = $arg[1]; 
  1355. } elseif (count($keywordArgs)) { 
  1356. $this->throwError('Positional arguments must come before keyword arguments.'); 
  1357. } elseif ($arg[2] == true) { 
  1358. $val = $this->reduce($arg[1], true); 
  1359.  
  1360. if ($val[0] == 'list') { 
  1361. foreach ($val[2] as $name => $item) { 
  1362. if (!is_numeric($name)) { 
  1363. $keywordArgs[$name] = $item; 
  1364. } else { 
  1365. $remaining[] = $item; 
  1366. } else { 
  1367. $remaining[] = $val; 
  1368. } else { 
  1369. $remaining[] = $arg[1]; 
  1370.  
  1371. foreach ($args as $arg) { 
  1372. list($i, $name, $default, $isVariable) = $arg; 
  1373.  
  1374. if ($isVariable) { 
  1375. $val = array('list', ', ', array()); 
  1376. for ($count = count($remaining); $i < $count; $i++) { 
  1377. $val[2][] = $remaining[$i]; 
  1378. foreach ($deferredKeywordArgs as $itemName => $item) { 
  1379. $val[2][$itemName] = $item; 
  1380. } elseif (isset($remaining[$i])) { 
  1381. $val = $remaining[$i]; 
  1382. } elseif (isset($keywordArgs[$name])) { 
  1383. $val = $keywordArgs[$name]; 
  1384. } elseif (!empty($default)) { 
  1385. continue; 
  1386. } else { 
  1387. $this->throwError("Missing argument $name"); 
  1388.  
  1389. $this->set($name, $this->reduce($val, true), true, $env); 
  1390.  
  1391. $storeEnv->store = $env->store; 
  1392.  
  1393. foreach ($args as $arg) { 
  1394. list($i, $name, $default, $isVariable) = $arg; 
  1395.  
  1396. if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) { 
  1397. continue; 
  1398.  
  1399. $this->set($name, $this->reduce($default, true), true); 
  1400.  
  1401. protected function pushEnv($block = null) 
  1402. $env = new \stdClass; 
  1403. $env->parent = $this->env; 
  1404. $env->store = array(); 
  1405. $env->block = $block; 
  1406. $env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0; 
  1407.  
  1408. $this->env = $env; 
  1409.  
  1410. return $env; 
  1411.  
  1412. protected function normalizeName($name) 
  1413. return str_replace('-', '_', $name); 
  1414.  
  1415. protected function getStoreEnv() 
  1416. return isset($this->storeEnv) ? $this->storeEnv : $this->env; 
  1417.  
  1418. protected function set($name, $value, $shadow = false, $env = null) 
  1419. $name = $this->normalizeName($name); 
  1420.  
  1421. if ($shadow) { 
  1422. $this->setRaw($name, $value, $env); 
  1423. } else { 
  1424. $this->setExisting($name, $value, $env); 
  1425.  
  1426. protected function setExisting($name, $value, $env = null) 
  1427. if (!isset($env)) { 
  1428. $env = $this->getStoreEnv(); 
  1429.  
  1430. if (isset($env->store[$name]) || !isset($env->parent)) { 
  1431. $env->store[$name] = $value; 
  1432. } else { 
  1433. $this->setExisting($name, $value, $env->parent); 
  1434.  
  1435. protected function setRaw($name, $value, $env = null) 
  1436. if (!isset($env)) { 
  1437. $env = $this->getStoreEnv(); 
  1438.  
  1439. $env->store[$name] = $value; 
  1440.  
  1441. public function get($name, $defaultValue = null, $env = null) 
  1442. $name = $this->normalizeName($name); 
  1443.  
  1444. if (!isset($env)) { 
  1445. $env = $this->getStoreEnv(); 
  1446.  
  1447. if (! isset($defaultValue)) { 
  1448. $defaultValue = self::$defaultValue; 
  1449.  
  1450. if (isset($env->store[$name])) { 
  1451. return $env->store[$name]; 
  1452.  
  1453. if (isset($env->parent)) { 
  1454. return $this->get($name, $defaultValue, $env->parent); 
  1455.  
  1456. return $defaultValue; // found nothing 
  1457.  
  1458. protected function injectVariables(array $args) 
  1459. if (empty($args)) { 
  1460. return; 
  1461.  
  1462. $parser = new Parser(__METHOD__, false); 
  1463.  
  1464. foreach ($args as $name => $strValue) { 
  1465. if ($name[0] === '$') { 
  1466. $name = substr($name, 1); 
  1467.  
  1468. $parser->env = null; 
  1469. $parser->count = 0; 
  1470. $parser->buffer = (string) $strValue; 
  1471. $parser->inParens = false; 
  1472. $parser->eatWhiteDefault = true; 
  1473. $parser->insertComments = true; 
  1474.  
  1475. if (! $parser->valueList($value)) { 
  1476. throw new \Exception("failed to parse passed in variable $name: $strValue"); 
  1477.  
  1478. $this->set($name, $value); 
  1479.  
  1480. /** 
  1481. * Set variables 
  1482. * @param array $variables 
  1483. */ 
  1484. public function setVariables(array $variables) 
  1485. $this->registeredVars = array_merge($this->registeredVars, $variables); 
  1486.  
  1487. /** 
  1488. * Unset variable 
  1489. * @param string $name 
  1490. */ 
  1491. public function unsetVariable($name) 
  1492. unset($this->registeredVars[$name]); 
  1493.  
  1494. protected function popEnv() 
  1495. $env = $this->env; 
  1496. $this->env = $this->env->parent; 
  1497.  
  1498. return $env; 
  1499.  
  1500. public function getParsedFiles() 
  1501. return $this->parsedFiles; 
  1502.  
  1503. public function addImportPath($path) 
  1504. if (!in_array($path, $this->importPaths)) { 
  1505. $this->importPaths[] = $path; 
  1506.  
  1507. public function setImportPaths($path) 
  1508. $this->importPaths = (array)$path; 
  1509.  
  1510. public function setNumberPrecision($numberPrecision) 
  1511. $this->numberPrecision = $numberPrecision; 
  1512.  
  1513. public function setFormatter($formatterName) 
  1514. $this->formatter = $formatterName; 
  1515.  
  1516. public function registerFunction($name, $func) 
  1517. $this->userFunctions[$this->normalizeName($name)] = $func; 
  1518.  
  1519. public function unregisterFunction($name) 
  1520. unset($this->userFunctions[$this->normalizeName($name)]); 
  1521.  
  1522. protected function importFile($path, $out) 
  1523. // see if tree is cached 
  1524. $realPath = realpath($path); 
  1525. if (isset($this->importCache[$realPath])) { 
  1526. $tree = $this->importCache[$realPath]; 
  1527. } else { 
  1528. $code = file_get_contents($path); 
  1529. $parser = new Parser($path, false); 
  1530. $tree = $parser->parse($code); 
  1531. $this->parsedFiles[] = $path; 
  1532.  
  1533. $this->importCache[$realPath] = $tree; 
  1534.  
  1535. $pi = pathinfo($path); 
  1536. array_unshift($this->importPaths, $pi['dirname']); 
  1537. $this->compileChildren($tree->children, $out); 
  1538. array_shift($this->importPaths); 
  1539.  
  1540. // results the file path for an import url if it exists 
  1541. public function findImport($url) 
  1542. $urls = array(); 
  1543.  
  1544. // for "normal" scss imports (ignore vanilla css and external requests) 
  1545. if (!preg_match('/\.css$|^http:\/\//', $url)) { 
  1546. // try both normal and the _partial filename 
  1547. $urls = array($url, preg_replace('/[^\/]+$/', '_\0', $url)); 
  1548.  
  1549. foreach ($this->importPaths as $dir) { 
  1550. if (is_string($dir)) { 
  1551. // check urls for normal import paths 
  1552. foreach ($urls as $full) { 
  1553. $full = $dir . 
  1554. (!empty($dir) && substr($dir, -1) != '/' ? '/' : '') . 
  1555. $full; 
  1556.  
  1557. if ($this->fileExists($file = $full.'.scss') || 
  1558. $this->fileExists($file = $full) 
  1559. ) { 
  1560. return $file; 
  1561. } else { 
  1562. // check custom callback for import path 
  1563. $file = call_user_func($dir, $url, $this); 
  1564.  
  1565. if ($file !== null) { 
  1566. return $file; 
  1567.  
  1568. return null; 
  1569.  
  1570. protected function fileExists($name) 
  1571. return is_file($name); 
  1572.  
  1573. protected function callBuiltin($name, $args, &$returnValue) 
  1574. // try a lib function 
  1575. $name = $this->normalizeName($name); 
  1576. $libName = 'lib' . preg_replace_callback( 
  1577. '/_(.)/',  
  1578. function ($m) { 
  1579. return ucfirst($m[1]); 
  1580. },  
  1581. ucfirst($name) 
  1582. ); 
  1583.  
  1584. $f = array($this, $libName); 
  1585.  
  1586. if (is_callable($f)) { 
  1587. $prototype = isset(self::$$libName) ? self::$$libName : null; 
  1588. $sorted = $this->sortArgs($prototype, $args); 
  1589.  
  1590. foreach ($sorted as &$val) { 
  1591. $val = $this->reduce($val, true); 
  1592.  
  1593. $returnValue = call_user_func($f, $sorted, $this); 
  1594. } elseif (isset($this->userFunctions[$name])) { 
  1595. // see if we can find a user function 
  1596. $fn = $this->userFunctions[$name]; 
  1597.  
  1598. foreach ($args as &$val) { 
  1599. $val = $this->reduce($val[1], true); 
  1600.  
  1601. $returnValue = call_user_func($fn, $args, $this); 
  1602.  
  1603. if (isset($returnValue)) { 
  1604. // coerce a php value into a scss one 
  1605. if (is_numeric($returnValue)) { 
  1606. $returnValue = array('number', $returnValue, ''); 
  1607. } elseif (is_bool($returnValue)) { 
  1608. $returnValue = $returnValue ? self::$true : self::$false; 
  1609. } elseif (!is_array($returnValue)) { 
  1610. $returnValue = array('keyword', $returnValue); 
  1611.  
  1612. return true; 
  1613.  
  1614. return false; 
  1615.  
  1616. // sorts any keyword arguments 
  1617. // TODO: merge with apply arguments? 
  1618. protected function sortArgs($prototype, $args) 
  1619. $keyArgs = array(); 
  1620. $posArgs = array(); 
  1621.  
  1622. foreach ($args as $arg) { 
  1623. list($key, $value) = $arg; 
  1624. $key = $key[1]; 
  1625. if (empty($key)) { 
  1626. $posArgs[] = $value; 
  1627. } else { 
  1628. $keyArgs[$key] = $value; 
  1629.  
  1630. if (!isset($prototype)) { 
  1631. return $posArgs; 
  1632.  
  1633. $finalArgs = array(); 
  1634.  
  1635. foreach ($prototype as $i => $names) { 
  1636. if (isset($posArgs[$i])) { 
  1637. $finalArgs[] = $posArgs[$i]; 
  1638. continue; 
  1639.  
  1640. $set = false; 
  1641. foreach ((array)$names as $name) { 
  1642. if (isset($keyArgs[$name])) { 
  1643. $finalArgs[] = $keyArgs[$name]; 
  1644. $set = true; 
  1645. break; 
  1646.  
  1647. if (!$set) { 
  1648. $finalArgs[] = null; 
  1649.  
  1650. return $finalArgs; 
  1651.  
  1652. protected function coerceForExpression($value) 
  1653. if ($color = $this->coerceColor($value)) { 
  1654. return $color; 
  1655.  
  1656. return $value; 
  1657.  
  1658. protected function coerceColor($value) 
  1659. switch ($value[0]) { 
  1660. case 'color': 
  1661. return $value; 
  1662.  
  1663. case 'keyword': 
  1664. $name = $value[1]; 
  1665.  
  1666. if (isset(Colors::$cssColors[$name])) { 
  1667. $rgba = explode(', ', Colors::$cssColors[$name]); 
  1668.  
  1669. return isset($rgba[3]) 
  1670. ? array('color', (int) $rgba[0], (int) $rgba[1], (int) $rgba[2], (int) $rgba[3]) 
  1671. : array('color', (int) $rgba[0], (int) $rgba[1], (int) $rgba[2]); 
  1672.  
  1673. return null; 
  1674.  
  1675. return null; 
  1676.  
  1677. protected function coerceString($value) 
  1678. switch ($value[0]) { 
  1679. case 'string': 
  1680. return $value; 
  1681. case 'keyword': 
  1682. return array('string', '', array($value[1])); 
  1683. return null; 
  1684.  
  1685. public function assertList($value) 
  1686. if ($value[0] != 'list') { 
  1687. $this->throwError('expecting list'); 
  1688.  
  1689. return $value; 
  1690.  
  1691. public function assertColor($value) 
  1692. if ($color = $this->coerceColor($value)) { 
  1693. return $color; 
  1694.  
  1695. $this->throwError('expecting color'); 
  1696.  
  1697. public function assertNumber($value) 
  1698. if ($value[0] != 'number') { 
  1699. $this->throwError('expecting number'); 
  1700.  
  1701. return $value[1]; 
  1702.  
  1703. protected function coercePercent($value) 
  1704. if ($value[0] == 'number') { 
  1705. if ($value[2] == '%') { 
  1706. return $value[1] / 100; 
  1707.  
  1708. return $value[1]; 
  1709.  
  1710. return 0; 
  1711.  
  1712. // make sure a color's components don't go out of bounds 
  1713. protected function fixColor($c) 
  1714. foreach (range(1, 3) as $i) { 
  1715. if ($c[$i] < 0) { 
  1716. $c[$i] = 0; 
  1717.  
  1718. if ($c[$i] > 255) { 
  1719. $c[$i] = 255; 
  1720.  
  1721. return $c; 
  1722.  
  1723. public function toHSL($red, $green, $blue) 
  1724. $min = min($red, $green, $blue); 
  1725. $max = max($red, $green, $blue); 
  1726.  
  1727. $l = $min + $max; 
  1728.  
  1729. if ($min == $max) { 
  1730. $s = $h = 0; 
  1731. } else { 
  1732. $d = $max - $min; 
  1733.  
  1734. if ($l < 255) { 
  1735. $s = $d / $l; 
  1736. } else { 
  1737. $s = $d / (510 - $l); 
  1738.  
  1739. if ($red == $max) { 
  1740. $h = 60 * ($green - $blue) / $d; 
  1741. } elseif ($green == $max) { 
  1742. $h = 60 * ($blue - $red) / $d + 120; 
  1743. } elseif ($blue == $max) { 
  1744. $h = 60 * ($red - $green) / $d + 240; 
  1745.  
  1746. return array('hsl', fmod($h, 360), $s * 100, $l / 5.1); 
  1747.  
  1748. public function hueToRGB($m1, $m2, $h) 
  1749. if ($h < 0) { 
  1750. $h += 1; 
  1751. } elseif ($h > 1) { 
  1752. $h -= 1; 
  1753.  
  1754. if ($h * 6 < 1) { 
  1755. return $m1 + ($m2 - $m1) * $h * 6; 
  1756.  
  1757. if ($h * 2 < 1) { 
  1758. return $m2; 
  1759.  
  1760. if ($h * 3 < 2) { 
  1761. return $m1 + ($m2 - $m1) * (2/3 - $h) * 6; 
  1762.  
  1763. return $m1; 
  1764.  
  1765. // H from 0 to 360, S and L from 0 to 100 
  1766. public function toRGB($hue, $saturation, $lightness) 
  1767. if ($hue < 0) { 
  1768. $hue += 360; 
  1769.  
  1770. $h = $hue / 360; 
  1771. $s = min(100, max(0, $saturation)) / 100; 
  1772. $l = min(100, max(0, $lightness)) / 100; 
  1773.  
  1774. $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s; 
  1775. $m1 = $l * 2 - $m2; 
  1776.  
  1777. $r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255; 
  1778. $g = $this->hueToRGB($m1, $m2, $h) * 255; 
  1779. $b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255; 
  1780.  
  1781. $out = array('color', $r, $g, $b); 
  1782.  
  1783. return $out; 
  1784.  
  1785. // Built in functions 
  1786.  
  1787. protected static $libIf = array('condition', 'if-true', 'if-false'); 
  1788. protected function libIf($args) 
  1789. list($cond, $t, $f) = $args; 
  1790.  
  1791. if (!$this->isTruthy($cond)) { 
  1792. return $f; 
  1793.  
  1794. return $t; 
  1795.  
  1796. protected static $libIndex = array('list', 'value'); 
  1797. protected function libIndex($args) 
  1798. list($list, $value) = $args; 
  1799.  
  1800. $list = $this->assertList($list); 
  1801.  
  1802. $values = array(); 
  1803.  
  1804. foreach ($list[2] as $item) { 
  1805. $values[] = $this->normalizeValue($item); 
  1806.  
  1807. $key = array_search($this->normalizeValue($value), $values); 
  1808.  
  1809. return false === $key ? false : $key + 1; 
  1810.  
  1811. protected static $libRgb = array('red', 'green', 'blue'); 
  1812. protected function libRgb($args) 
  1813. list($r, $g, $b) = $args; 
  1814.  
  1815. return array('color', $r[1], $g[1], $b[1]); 
  1816.  
  1817. protected static $libRgba = array( 
  1818. array('red', 'color'),  
  1819. 'green', 'blue', 'alpha'); 
  1820. protected function libRgba($args) 
  1821. if ($color = $this->coerceColor($args[0])) { 
  1822. $num = !isset($args[1]) ? $args[3] : $args[1]; 
  1823. $alpha = $this->assertNumber($num); 
  1824. $color[4] = $alpha; 
  1825.  
  1826. return $color; 
  1827.  
  1828. list($r, $g, $b, $a) = $args; 
  1829.  
  1830. return array('color', $r[1], $g[1], $b[1], $a[1]); 
  1831.  
  1832. // helper function for adjust_color, change_color, and scale_color 
  1833. protected function alterColor($args, $fn) 
  1834. $color = $this->assertColor($args[0]); 
  1835.  
  1836. foreach (array(1, 2, 3, 7) as $i) { 
  1837. if (isset($args[$i])) { 
  1838. $val = $this->assertNumber($args[$i]); 
  1839. $ii = $i == 7 ? 4 : $i; // alpha 
  1840. $color[$ii] = 
  1841. $this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i); 
  1842.  
  1843. if (isset($args[4]) || isset($args[5]) || isset($args[6])) { 
  1844. $hsl = $this->toHSL($color[1], $color[2], $color[3]); 
  1845.  
  1846. foreach (array(4, 5, 6) as $i) { 
  1847. if (isset($args[$i])) { 
  1848. $val = $this->assertNumber($args[$i]); 
  1849. $hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i); 
  1850.  
  1851. $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]); 
  1852.  
  1853. if (isset($color[4])) { 
  1854. $rgb[4] = $color[4]; 
  1855.  
  1856. $color = $rgb; 
  1857.  
  1858. return $color; 
  1859.  
  1860. protected static $libAdjustColor = array( 
  1861. 'color', 'red', 'green', 'blue',  
  1862. 'hue', 'saturation', 'lightness', 'alpha' 
  1863. ); 
  1864. protected function adjustColorHelper($base, $alter, $i) 
  1865. return $base += $alter; 
  1866. protected function libAdjustColor($args) 
  1867. return $this->alterColor($args, 'adjustColorHelper'); 
  1868.  
  1869. protected static $libChangeColor = array( 
  1870. 'color', 'red', 'green', 'blue',  
  1871. 'hue', 'saturation', 'lightness', 'alpha' 
  1872. ); 
  1873. protected function changeColorHelper($base, $alter, $i) 
  1874. return $alter; 
  1875. protected function libChangeColor($args) 
  1876. return $this->alterColor($args, 'changeColorHelper'); 
  1877.  
  1878. protected static $libScaleColor = array( 
  1879. 'color', 'red', 'green', 'blue',  
  1880. 'hue', 'saturation', 'lightness', 'alpha' 
  1881. ); 
  1882. protected function scaleColorHelper($base, $scale, $i) 
  1883. // 1, 2, 3 - rgb 
  1884. // 4, 5, 6 - hsl 
  1885. // 7 - a 
  1886. switch ($i) { 
  1887. case 1: 
  1888. case 2: 
  1889. case 3: 
  1890. $max = 255; 
  1891. break; 
  1892. case 4: 
  1893. $max = 360; 
  1894. break; 
  1895. case 7: 
  1896. $max = 1; 
  1897. break; 
  1898. default: 
  1899. $max = 100; 
  1900.  
  1901. $scale = $scale / 100; 
  1902.  
  1903. if ($scale < 0) { 
  1904. return $base * $scale + $base; 
  1905.  
  1906. return ($max - $base) * $scale + $base; 
  1907. protected function libScaleColor($args) 
  1908. return $this->alterColor($args, 'scaleColorHelper'); 
  1909.  
  1910. protected static $libIeHexStr = array('color'); 
  1911. protected function libIeHexStr($args) 
  1912. $color = $this->coerceColor($args[0]); 
  1913. $color[4] = isset($color[4]) ? round(255*$color[4]) : 255; 
  1914.  
  1915. return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]); 
  1916.  
  1917. protected static $libRed = array('color'); 
  1918. protected function libRed($args) 
  1919. $color = $this->coerceColor($args[0]); 
  1920.  
  1921. return $color[1]; 
  1922.  
  1923. protected static $libGreen = array('color'); 
  1924. protected function libGreen($args) 
  1925. $color = $this->coerceColor($args[0]); 
  1926.  
  1927. return $color[2]; 
  1928.  
  1929. protected static $libBlue = array('color'); 
  1930. protected function libBlue($args) 
  1931. $color = $this->coerceColor($args[0]); 
  1932.  
  1933. return $color[3]; 
  1934.  
  1935. protected static $libAlpha = array('color'); 
  1936. protected function libAlpha($args) 
  1937. if ($color = $this->coerceColor($args[0])) { 
  1938. return isset($color[4]) ? $color[4] : 1; 
  1939.  
  1940. // this might be the IE function, so return value unchanged 
  1941. return null; 
  1942.  
  1943. protected static $libOpacity = array('color'); 
  1944. protected function libOpacity($args) 
  1945. $value = $args[0]; 
  1946.  
  1947. if ($value[0] === 'number') { 
  1948. return null; 
  1949.  
  1950. return $this->libAlpha($args); 
  1951.  
  1952. // mix two colors 
  1953. protected static $libMix = array('color-1', 'color-2', 'weight'); 
  1954. protected function libMix($args) 
  1955. list($first, $second, $weight) = $args; 
  1956. $first = $this->assertColor($first); 
  1957. $second = $this->assertColor($second); 
  1958.  
  1959. if (!isset($weight)) { 
  1960. $weight = 0.5; 
  1961. } else { 
  1962. $weight = $this->coercePercent($weight); 
  1963.  
  1964. $firstAlpha = isset($first[4]) ? $first[4] : 1; 
  1965. $secondAlpha = isset($second[4]) ? $second[4] : 1; 
  1966.  
  1967. $w = $weight * 2 - 1; 
  1968. $a = $firstAlpha - $secondAlpha; 
  1969.  
  1970. $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; 
  1971. $w2 = 1.0 - $w1; 
  1972.  
  1973. $new = array('color',  
  1974. $w1 * $first[1] + $w2 * $second[1],  
  1975. $w1 * $first[2] + $w2 * $second[2],  
  1976. $w1 * $first[3] + $w2 * $second[3],  
  1977. ); 
  1978.  
  1979. if ($firstAlpha != 1.0 || $secondAlpha != 1.0) { 
  1980. $new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1); 
  1981.  
  1982. return $this->fixColor($new); 
  1983.  
  1984. protected static $libHsl = array('hue', 'saturation', 'lightness'); 
  1985. protected function libHsl($args) 
  1986. list($h, $s, $l) = $args; 
  1987.  
  1988. return $this->toRGB($h[1], $s[1], $l[1]); 
  1989.  
  1990. protected static $libHsla = array('hue', 'saturation',  
  1991. 'lightness', 'alpha'); 
  1992. protected function libHsla($args) 
  1993. list($h, $s, $l, $a) = $args; 
  1994.  
  1995. $color = $this->toRGB($h[1], $s[1], $l[1]); 
  1996. $color[4] = $a[1]; 
  1997.  
  1998. return $color; 
  1999.  
  2000. protected static $libHue = array('color'); 
  2001. protected function libHue($args) 
  2002. $color = $this->assertColor($args[0]); 
  2003. $hsl = $this->toHSL($color[1], $color[2], $color[3]); 
  2004.  
  2005. return array('number', $hsl[1], 'deg'); 
  2006.  
  2007. protected static $libSaturation = array('color'); 
  2008. protected function libSaturation($args) 
  2009. $color = $this->assertColor($args[0]); 
  2010. $hsl = $this->toHSL($color[1], $color[2], $color[3]); 
  2011.  
  2012. return array('number', $hsl[2], '%'); 
  2013.  
  2014. protected static $libLightness = array('color'); 
  2015. protected function libLightness($args) 
  2016. $color = $this->assertColor($args[0]); 
  2017. $hsl = $this->toHSL($color[1], $color[2], $color[3]); 
  2018.  
  2019. return array('number', $hsl[3], '%'); 
  2020.  
  2021. protected function adjustHsl($color, $idx, $amount) 
  2022. $hsl = $this->toHSL($color[1], $color[2], $color[3]); 
  2023. $hsl[$idx] += $amount; 
  2024. $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]); 
  2025.  
  2026. if (isset($color[4])) { 
  2027. $out[4] = $color[4]; 
  2028.  
  2029. return $out; 
  2030.  
  2031. protected static $libAdjustHue = array('color', 'degrees'); 
  2032. protected function libAdjustHue($args) 
  2033. $color = $this->assertColor($args[0]); 
  2034. $degrees = $this->assertNumber($args[1]); 
  2035.  
  2036. return $this->adjustHsl($color, 1, $degrees); 
  2037.  
  2038. protected static $libLighten = array('color', 'amount'); 
  2039. protected function libLighten($args) 
  2040. $color = $this->assertColor($args[0]); 
  2041. $amount = 100*$this->coercePercent($args[1]); 
  2042.  
  2043. return $this->adjustHsl($color, 3, $amount); 
  2044.  
  2045. protected static $libDarken = array('color', 'amount'); 
  2046. protected function libDarken($args) 
  2047. $color = $this->assertColor($args[0]); 
  2048. $amount = 100*$this->coercePercent($args[1]); 
  2049.  
  2050. return $this->adjustHsl($color, 3, -$amount); 
  2051.  
  2052. protected static $libSaturate = array('color', 'amount'); 
  2053. protected function libSaturate($args) 
  2054. $value = $args[0]; 
  2055.  
  2056. if ($value[0] === 'number') { 
  2057. return null; 
  2058.  
  2059. $color = $this->assertColor($value); 
  2060. $amount = 100*$this->coercePercent($args[1]); 
  2061.  
  2062. return $this->adjustHsl($color, 2, $amount); 
  2063.  
  2064. protected static $libDesaturate = array('color', 'amount'); 
  2065. protected function libDesaturate($args) 
  2066. $color = $this->assertColor($args[0]); 
  2067. $amount = 100*$this->coercePercent($args[1]); 
  2068.  
  2069. return $this->adjustHsl($color, 2, -$amount); 
  2070.  
  2071. protected static $libGrayscale = array('color'); 
  2072. protected function libGrayscale($args) 
  2073. $value = $args[0]; 
  2074.  
  2075. if ($value[0] === 'number') { 
  2076. return null; 
  2077.  
  2078. return $this->adjustHsl($this->assertColor($value), 2, -100); 
  2079.  
  2080. protected static $libComplement = array('color'); 
  2081. protected function libComplement($args) 
  2082. return $this->adjustHsl($this->assertColor($args[0]), 1, 180); 
  2083.  
  2084. protected static $libInvert = array('color'); 
  2085. protected function libInvert($args) 
  2086. $value = $args[0]; 
  2087.  
  2088. if ($value[0] === 'number') { 
  2089. return null; 
  2090.  
  2091. $color = $this->assertColor($value); 
  2092. $color[1] = 255 - $color[1]; 
  2093. $color[2] = 255 - $color[2]; 
  2094. $color[3] = 255 - $color[3]; 
  2095.  
  2096. return $color; 
  2097.  
  2098. // increases opacity by amount 
  2099. protected static $libOpacify = array('color', 'amount'); 
  2100. protected function libOpacify($args) 
  2101. $color = $this->assertColor($args[0]); 
  2102. $amount = $this->coercePercent($args[1]); 
  2103.  
  2104. $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount; 
  2105. $color[4] = min(1, max(0, $color[4])); 
  2106.  
  2107. return $color; 
  2108.  
  2109. protected static $libFadeIn = array('color', 'amount'); 
  2110. protected function libFadeIn($args) 
  2111. return $this->libOpacify($args); 
  2112.  
  2113. // decreases opacity by amount 
  2114. protected static $libTransparentize = array('color', 'amount'); 
  2115. protected function libTransparentize($args) 
  2116. $color = $this->assertColor($args[0]); 
  2117. $amount = $this->coercePercent($args[1]); 
  2118.  
  2119. $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount; 
  2120. $color[4] = min(1, max(0, $color[4])); 
  2121.  
  2122. return $color; 
  2123.  
  2124. protected static $libFadeOut = array('color', 'amount'); 
  2125. protected function libFadeOut($args) 
  2126. return $this->libTransparentize($args); 
  2127.  
  2128. protected static $libUnquote = array('string'); 
  2129. protected function libUnquote($args) 
  2130. $str = $args[0]; 
  2131.  
  2132. if ($str[0] == 'string') { 
  2133. $str[1] = ''; 
  2134.  
  2135. return $str; 
  2136.  
  2137. protected static $libQuote = array('string'); 
  2138. protected function libQuote($args) 
  2139. $value = $args[0]; 
  2140.  
  2141. if ($value[0] == 'string' && !empty($value[1])) { 
  2142. return $value; 
  2143.  
  2144. return array('string', '"', array($value)); 
  2145.  
  2146. protected static $libPercentage = array('value'); 
  2147. protected function libPercentage($args) 
  2148. return array('number',  
  2149. $this->coercePercent($args[0]) * 100,  
  2150. '%'); 
  2151.  
  2152. protected static $libRound = array('value'); 
  2153. protected function libRound($args) 
  2154. $num = $args[0]; 
  2155. $num[1] = round($num[1]); 
  2156.  
  2157. return $num; 
  2158.  
  2159. protected static $libFloor = array('value'); 
  2160. protected function libFloor($args) 
  2161. $num = $args[0]; 
  2162. $num[1] = floor($num[1]); 
  2163.  
  2164. return $num; 
  2165.  
  2166. protected static $libCeil = array('value'); 
  2167. protected function libCeil($args) 
  2168. $num = $args[0]; 
  2169. $num[1] = ceil($num[1]); 
  2170.  
  2171. return $num; 
  2172.  
  2173. protected static $libAbs = array('value'); 
  2174. protected function libAbs($args) 
  2175. $num = $args[0]; 
  2176. $num[1] = abs($num[1]); 
  2177.  
  2178. return $num; 
  2179.  
  2180. protected function libMin($args) 
  2181. $numbers = $this->getNormalizedNumbers($args); 
  2182. $min = null; 
  2183.  
  2184. foreach ($numbers as $key => $number) { 
  2185. if (null === $min || $number[1] <= $min[1]) { 
  2186. $min = array($key, $number[1]); 
  2187.  
  2188. return $args[$min[0]]; 
  2189.  
  2190. protected function libMax($args) 
  2191. $numbers = $this->getNormalizedNumbers($args); 
  2192. $max = null; 
  2193.  
  2194. foreach ($numbers as $key => $number) { 
  2195. if (null === $max || $number[1] >= $max[1]) { 
  2196. $max = array($key, $number[1]); 
  2197.  
  2198. return $args[$max[0]]; 
  2199.  
  2200. protected function getNormalizedNumbers($args) 
  2201. $unit = null; 
  2202. $originalUnit = null; 
  2203. $numbers = array(); 
  2204.  
  2205. foreach ($args as $key => $item) { 
  2206. if ('number' != $item[0]) { 
  2207. $this->throwError('%s is not a number', $item[0]); 
  2208.  
  2209. $number = $this->normalizeNumber($item); 
  2210.  
  2211. if (null === $unit) { 
  2212. $unit = $number[2]; 
  2213. $originalUnit = $item[2]; 
  2214. } elseif ($unit !== $number[2]) { 
  2215. $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]); 
  2216.  
  2217. $numbers[$key] = $number; 
  2218.  
  2219. return $numbers; 
  2220.  
  2221. protected static $libLength = array('list'); 
  2222. protected function libLength($args) 
  2223. $list = $this->coerceList($args[0]); 
  2224.  
  2225. return count($list[2]); 
  2226.  
  2227. protected static $libNth = array('list', 'n'); 
  2228. protected function libNth($args) 
  2229. $list = $this->coerceList($args[0]); 
  2230. $n = $this->assertNumber($args[1]) - 1; 
  2231.  
  2232. return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue; 
  2233.  
  2234. protected function listSeparatorForJoin($list1, $sep) 
  2235. if (!isset($sep)) { 
  2236. return $list1[1]; 
  2237.  
  2238. switch ($this->compileValue($sep)) { 
  2239. case 'comma': 
  2240. return ', '; 
  2241. case 'space': 
  2242. return ''; 
  2243. default: 
  2244. return $list1[1]; 
  2245.  
  2246. protected static $libJoin = array('list1', 'list2', 'separator'); 
  2247. protected function libJoin($args) 
  2248. list($list1, $list2, $sep) = $args; 
  2249.  
  2250. $list1 = $this->coerceList($list1, ' '); 
  2251. $list2 = $this->coerceList($list2, ' '); 
  2252. $sep = $this->listSeparatorForJoin($list1, $sep); 
  2253.  
  2254. return array('list', $sep, array_merge($list1[2], $list2[2])); 
  2255.  
  2256. protected static $libAppend = array('list', 'val', 'separator'); 
  2257. protected function libAppend($args) 
  2258. list($list1, $value, $sep) = $args; 
  2259.  
  2260. $list1 = $this->coerceList($list1, ' '); 
  2261. $sep = $this->listSeparatorForJoin($list1, $sep); 
  2262.  
  2263. return array('list', $sep, array_merge($list1[2], array($value))); 
  2264.  
  2265. protected function libZip($args) 
  2266. foreach ($args as $arg) { 
  2267. $this->assertList($arg); 
  2268.  
  2269. $lists = array(); 
  2270. $firstList = array_shift($args); 
  2271.  
  2272. foreach ($firstList[2] as $key => $item) { 
  2273. $list = array('list', '', array($item)); 
  2274.  
  2275. foreach ($args as $arg) { 
  2276. if (isset($arg[2][$key])) { 
  2277. $list[2][] = $arg[2][$key]; 
  2278. } else { 
  2279. break 2; 
  2280. $lists[] = $list; 
  2281.  
  2282. return array('list', ', ', $lists); 
  2283.  
  2284. protected static $libTypeOf = array('value'); 
  2285. protected function libTypeOf($args) 
  2286. $value = $args[0]; 
  2287.  
  2288. switch ($value[0]) { 
  2289. case 'keyword': 
  2290. if ($value == self::$true || $value == self::$false) { 
  2291. return 'bool'; 
  2292.  
  2293. if ($this->coerceColor($value)) { 
  2294. return 'color'; 
  2295.  
  2296. return 'string'; 
  2297. default: 
  2298. return $value[0]; 
  2299.  
  2300. protected static $libUnit = array('number'); 
  2301. protected function libUnit($args) 
  2302. $num = $args[0]; 
  2303.  
  2304. if ($num[0] == 'number') { 
  2305. return array('string', '"', array($num[2])); 
  2306.  
  2307. return ''; 
  2308.  
  2309. protected static $libUnitless = array('number'); 
  2310. protected function libUnitless($args) 
  2311. $value = $args[0]; 
  2312.  
  2313. return $value[0] == 'number' && empty($value[2]); 
  2314.  
  2315. protected static $libComparable = array('number-1', 'number-2'); 
  2316. protected function libComparable($args) 
  2317. list($number1, $number2) = $args; 
  2318.  
  2319. if (!isset($number1[0]) || $number1[0] != 'number' || !isset($number2[0]) || $number2[0] != 'number') { 
  2320. $this->throwError('Invalid argument(s) for "comparable"'); 
  2321.  
  2322. $number1 = $this->normalizeNumber($number1); 
  2323. $number2 = $this->normalizeNumber($number2); 
  2324.  
  2325. return $number1[2] == $number2[2] || $number1[2] == '' || $number2[2] == ''; 
  2326.  
  2327. /** 
  2328. * Workaround IE7's content counter bug. 
  2329. * @param array $args 
  2330. */ 
  2331. protected function libCounter($args) 
  2332. $list = array_map(array($this, 'compileValue'), $args); 
  2333.  
  2334. return array('string', '', array('counter(' . implode(', ', $list) . ')')); 
  2335.  
  2336. public function throwError($msg = null) 
  2337. if (func_num_args() > 1) { 
  2338. $msg = call_user_func_array('sprintf', func_get_args()); 
  2339.  
  2340. if ($this->sourcePos >= 0 && isset($this->sourceParser)) { 
  2341. $this->sourceParser->throwParseError($msg, $this->sourcePos); 
  2342.  
  2343. throw new \Exception($msg);