PageRenderTime 7ms CodeModel.GetById 3ms app.highlight 68ms RepoModel.GetById 1ms app.codeStats 1ms

/SCSS/scss.inc.php

https://bitbucket.org/Pathou/autres
PHP | 3577 lines | 2934 code | 533 blank | 110 comment | 580 complexity | 32e7110be07e394c81ce91d4e0739937 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2
   3class scssc {
   4	static public $VERSION = "v0.0.3";
   5
   6	static protected $operatorNames = array(
   7		'+' => "add",
   8		'-' => "sub",
   9		'*' => "mul",
  10		'/' => "div",
  11		'%' => "mod",
  12
  13		'==' => "eq",
  14		'!=' => "neq",
  15		'<' => "lt",
  16		'>' => "gt",
  17
  18		'<=' => "lte",
  19		'>=' => "gte",
  20	);
  21
  22	static protected $namespaces = array(
  23		"mixin" => "@",
  24		"function" => "^",
  25	);
  26
  27	static protected $numberPrecision = 3;
  28	static protected $unitTable = array(
  29		"in" => array(
  30			"in" => 1,
  31			"pt" => 72,
  32			"pc" => 6,
  33			"cm" => 2.54,
  34			"mm" => 25.4,
  35		)
  36	);
  37
  38	static public $true = array("keyword", "true");
  39	static public $false = array("keyword", "false");
  40
  41	static public $defaultValue = array("keyword", "");
  42	static public $selfSelector = array("self");
  43
  44	protected $importPaths = array("");
  45	protected $importCache = array();
  46
  47	protected $userFunctions = array();
  48
  49	protected $formatter = "scss_formatter_nested";
  50
  51	function compile($code, $name=null) {
  52		$this->indentLevel = -1;
  53		$this->commentsSeen = array();
  54		$this->extends = array();
  55		$this->extendsMap = array();
  56
  57		$locale = setlocale(LC_NUMERIC, 0);
  58		setlocale(LC_NUMERIC, "C");
  59
  60		$this->parsedFiles = array();
  61		$this->parser = new scss_parser($name);
  62		$tree = $this->parser->parse($code);
  63
  64		$this->formatter = new $this->formatter();
  65
  66		$this->env = null;
  67		$this->scope = null;
  68
  69		$this->compileRoot($tree);
  70		$this->flattenSelectors($this->scope);
  71
  72		ob_start();
  73		$this->formatter->block($this->scope);
  74		$out = ob_get_clean();
  75
  76		setlocale(LC_NUMERIC, $locale);
  77		return $out;
  78	}
  79
  80	protected function pushExtends($target, $origin) {
  81		$i = count($this->extends);
  82		$this->extends[] = array($target, $origin);
  83
  84		foreach ($target as $part) {
  85			if (isset($this->extendsMap[$part])) {
  86				$this->extendsMap[$part][] = $i;
  87			} else {
  88				$this->extendsMap[$part] = array($i);
  89			}
  90		}
  91	}
  92
  93	protected function makeOutputBlock($type, $selectors = null) {
  94		$out = new stdclass;
  95		$out->type = $type;
  96		$out->lines = array();
  97		$out->children = array();
  98		$out->parent = $this->scope;
  99		$out->selectors = $selectors;
 100		$out->depth = $this->env->depth;
 101
 102		return $out;
 103	}
 104
 105	protected function matchExtendsSingle($single, &$out_origin, &$out_rem) {
 106		$counts = array();
 107		foreach ($single as $part) {
 108			if (!is_string($part)) return false; // hmm
 109
 110			if (isset($this->extendsMap[$part])) {
 111				foreach ($this->extendsMap[$part] as $idx) {
 112					$counts[$idx] =
 113						isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
 114				}
 115			}
 116		}
 117
 118		foreach ($counts as $idx => $count) {
 119			list($target, $origin) = $this->extends[$idx];
 120			// check count
 121			if ($count != count($target)) continue;
 122			// check if target is subset of single
 123			if (array_diff(array_intersect($single, $target), $target)) continue;
 124
 125			$out_origin = $origin;
 126			$out_rem = array_diff($single, $target);
 127
 128			return true;
 129		}
 130
 131		return false;
 132	}
 133
 134	protected function combineSelectorSingle($base, $other) {
 135		$tag = null;
 136		$out = array();
 137
 138		foreach (array($base, $other) as $single) {
 139			foreach ($single as $part) {
 140				if (preg_match('/^[^.#:]/', $part)) {
 141					$tag = $part;
 142				} else {
 143					$out[] = $part;
 144				}
 145			}
 146		}
 147
 148		if ($tag) {
 149			array_unshift($out, $tag);
 150		}
 151
 152		return $out;
 153	}
 154
 155	protected function matchExtends($selector, &$out, $from = 0, $initial=true) {
 156		foreach ($selector as $i => $part) {
 157			if ($i < $from) continue;
 158
 159			if ($this->matchExtendsSingle($part, $origin, $rem)) {
 160				$before = array_slice($selector, 0, $i);
 161				$after = array_slice($selector, $i + 1);
 162
 163				foreach ($origin as $new) {
 164					$new[count($new) - 1] =
 165						$this->combineSelectorSingle(end($new), $rem);
 166
 167					$k = 0;
 168					// remove shared parts
 169					if ($initial) {
 170						foreach ($before as $k => $val) {
 171							if (!isset($new[$k]) || $val != $new[$k]) {
 172								break;
 173							}
 174						}
 175					}
 176
 177					$result = array_merge(
 178						$before,
 179						$k > 0 ? array_slice($new, $k) : $new,
 180						$after);
 181
 182
 183					if ($result == $selector) continue;
 184					$out[] = $result;
 185
 186					// recursively check for more matches
 187					$this->matchExtends($result, $out, $i, false);
 188
 189					// selector sequence merging
 190					if (!empty($before) && count($new) > 1) {
 191						$result2 = array_merge(
 192							array_slice($new, 0, -1),
 193							$k > 0 ? array_slice($before, $k) : $before,
 194							array_slice($new, -1),
 195							$after);
 196
 197						$out[] = $result2;
 198					}
 199				}
 200			}
 201		}
 202	}
 203
 204	protected function flattenSelectors($block) {
 205		if ($block->selectors) {
 206			$selectors = array();
 207			foreach ($block->selectors as $s) {
 208				$selectors[] = $s;
 209				if (!is_array($s)) continue;
 210				// check extends
 211				if (!empty($this->extendsMap)) {
 212					$this->matchExtends($s, $selectors);
 213				}
 214			}
 215
 216			$selectors = array_map(array($this, "compileSelector"), $selectors);
 217			$block->selectors = $selectors;
 218		}
 219
 220		foreach ($block->children as $child) {
 221			$this->flattenSelectors($child);
 222		}
 223	}
 224
 225	protected function compileRoot($rootBlock) {
 226		$this->pushEnv($rootBlock);
 227		$this->scope = $this->makeOutputBlock("root");
 228		$this->compileChildren($rootBlock->children, $this->scope);
 229		$this->popEnv();
 230	}
 231
 232	protected function compileMedia($media) {
 233		$this->pushEnv($media);
 234		$parentScope = $this->mediaParent($this->scope);
 235
 236		$this->scope = $this->makeOutputBlock("media", array(
 237			$this->compileMediaQuery($this->multiplyMedia($this->env)))
 238		);
 239
 240		$parentScope->children[] = $this->scope;
 241
 242		$this->compileChildren($media->children, $this->scope);
 243
 244		$this->scope = $this->scope->parent;
 245		$this->popEnv();
 246	}
 247
 248	protected function mediaParent($scope) {
 249		while (!empty($scope->parent)) {
 250			if (!empty($scope->type) && $scope->type != "media") {
 251				break;
 252			}
 253			$scope = $scope->parent;
 254		}
 255
 256		return $scope;
 257	}
 258
 259	// TODO refactor compileNestedBlock and compileMedia into same thing
 260	protected function compileNestedBlock($block, $selectors) {
 261		$this->pushEnv($block);
 262
 263		$this->scope = $this->makeOutputBlock($block->type, $selectors);
 264		$this->scope->parent->children[] = $this->scope;
 265		$this->compileChildren($block->children, $this->scope);
 266
 267		$this->scope = $this->scope->parent;
 268		$this->popEnv();
 269	}
 270
 271	protected function compileBlock($block) {
 272		$env = $this->pushEnv($block);
 273
 274		$env->selectors =
 275			array_map(array($this, "evalSelector"), $block->selectors);
 276
 277		$out = $this->makeOutputBlock(null, $this->multiplySelectors($env));
 278		$this->scope->children[] = $out;
 279		$this->compileChildren($block->children, $out);
 280
 281		$this->popEnv();
 282	}
 283
 284	// joins together .classes and #ids
 285	protected function flattenSelectorSingle($single) {
 286		$joined = array();
 287		foreach ($single as $part) {
 288			if (empty($joined) ||
 289				!is_string($part) ||
 290				preg_match('/[.:#]/', $part))
 291			{
 292				$joined[] = $part;
 293				continue;
 294			}
 295
 296			if (is_array(end($joined))) {
 297				$joined[] = $part;
 298			} else {
 299				$joined[count($joined) - 1] .= $part;
 300			}
 301		}
 302
 303		return $joined;
 304	}
 305
 306	// replaces all the interpolates
 307	protected function evalSelector($selector) {
 308		return array_map(array($this, "evalSelectorPart"), $selector);
 309	}
 310
 311	protected function evalSelectorPart($piece) {
 312		foreach ($piece as &$p) {
 313			if (!is_array($p)) continue;
 314
 315			switch ($p[0]) {
 316			case "interpolate":
 317				$p = $this->compileValue($p);
 318				break;
 319			}
 320		}
 321
 322		return $this->flattenSelectorSingle($piece);
 323	}
 324
 325	// compiles to string
 326	// self(&) should have been replaced by now
 327	protected function compileSelector($selector) {
 328		if (!is_array($selector)) return $selector; // media and the like
 329
 330		return implode(" ", array_map(
 331			array($this, "compileSelectorPart"), $selector));
 332	}
 333
 334	protected function compileSelectorPart($piece) {
 335		foreach ($piece as &$p) {
 336			if (!is_array($p)) continue;
 337
 338			switch ($p[0]) {
 339			case "self":
 340				$p = "&";
 341				break;
 342			default:
 343				$p = $this->compileValue($p);
 344				break;
 345			}
 346		}
 347
 348		return implode($piece);
 349	}
 350
 351	protected function compileChildren($stms, $out) {
 352		foreach ($stms as $stm) {
 353			$ret = $this->compileChild($stm, $out);
 354			if (!is_null($ret)) return $ret;
 355		}
 356	}
 357
 358	protected function compileMediaQuery($query) {
 359		$parts = array();
 360		foreach ($query as $q) {
 361			switch ($q[0]) {
 362			case "mediaType":
 363				$parts[] = implode(" ", array_slice($q, 1));
 364				break;
 365			case "mediaExp":
 366				if (isset($q[2])) {
 367					$parts[] = "($q[1]: " . $this->compileValue($q[2]) . ")";
 368				} else {
 369					$parts[] = "($q[1])";
 370				}
 371				break;
 372			}
 373		}
 374
 375		$out = "@media";
 376		if (!empty($parts)) {
 377			$out = $out . " " . implode(" and ", $parts);
 378		}
 379		return $out;
 380	}
 381
 382	// returns true if the value was something that could be imported
 383	protected function compileImport($rawPath, $out) {
 384		if ($rawPath[0] == "string") {
 385			$path = $this->compileStringContent($rawPath);
 386			if ($path = $this->findImport($path)) {
 387				$this->importFile($path, $out);
 388				return true;
 389			}
 390			return false;
 391		} if ($rawPath[0] == "list") {
 392			// handle a list of strings
 393			if (count($rawPath[2]) == 0) return false;
 394			foreach ($rawPath[2] as $path) {
 395				if ($path[0] != "string") return false;
 396			}
 397
 398			foreach ($rawPath[2] as $path) {
 399				$this->compileImport($path, $out);
 400			}
 401
 402			return true;
 403		}
 404
 405		return false;
 406	}
 407
 408	// return a value to halt execution
 409	protected function compileChild($child, $out) {
 410		switch ($child[0]) {
 411		case "import":
 412			list(,$rawPath) = $child;
 413			$rawPath = $this->reduce($rawPath);
 414			if (!$this->compileImport($rawPath, $out)) {
 415				$out->lines[] = "@import " . $this->compileValue($rawPath) . ";";
 416			}
 417			break;
 418		case "directive":
 419			list(, $directive) = $child;
 420			$s = "@" . $directive->name;
 421			if (!empty($directive->value)) {
 422				$s .= " " . $this->compileValue($directive->value);
 423			}
 424			$this->compileNestedBlock($directive, array($s));
 425			break;
 426		case "media":
 427			$this->compileMedia($child[1]);
 428			break;
 429		case "block":
 430			$this->compileBlock($child[1]);
 431			break;
 432		case "charset":
 433			$out->lines[] = "@charset ".$this->compileValue($child[1]).";";
 434			break;
 435		case "assign":
 436			list(,$name, $value) = $child;
 437			if ($name[0] == "var") {
 438				$isDefault = !empty($child[3]);
 439				if (!$isDefault || $this->get($name[1], true) === true) {
 440					$this->set($name[1], $this->reduce($value));
 441				}
 442				break;
 443			}
 444
 445			$out->lines[] = $this->formatter->property(
 446				$this->compileValue($child[1]),
 447				$this->compileValue($child[2]));
 448			break;
 449		case "comment":
 450			$out->lines[] = $child[1];
 451			break;
 452		case "mixin":
 453		case "function":
 454			list(,$block) = $child;
 455			$this->set(self::$namespaces[$block->type] . $block->name, $block);
 456			break;
 457		case "extend":
 458			list(, $selectors) = $child;
 459			foreach ($selectors as $sel) {
 460				// only use the first one
 461				$sel = current($this->evalSelector($sel));
 462				$this->pushExtends($sel, $out->selectors);
 463			}
 464			break;
 465		case "if":
 466			list(, $if) = $child;
 467			if ($this->reduce($if->cond, true) != self::$false) {
 468				return $this->compileChildren($if->children, $out);
 469			} else {
 470				foreach ($if->cases as $case) {
 471					if ($case->type == "else" ||
 472						$case->type == "elseif" && ($this->reduce($case->cond) != self::$false))
 473					{
 474						return $this->compileChildren($case->children, $out);
 475					}
 476				}
 477			}
 478			break;
 479		case "return":
 480			return $this->reduce($child[1], true);
 481		case "each":
 482			list(,$each) = $child;
 483			$list = $this->reduce($this->coerceList($each->list));
 484			foreach ($list[2] as $item) {
 485				$this->pushEnv();
 486				$this->set($each->var, $item);
 487				// TODO: allow return from here
 488				$this->compileChildren($each->children, $out);
 489				$this->popEnv();
 490			}
 491			break;
 492		case "while":
 493			list(,$while) = $child;
 494			while ($this->reduce($while->cond, true) != self::$false) {
 495				$ret = $this->compileChildren($while->children, $out);
 496				if ($ret) return $ret;
 497			}
 498			break;
 499		case "for":
 500			list(,$for) = $child;
 501			$start = $this->reduce($for->start, true);
 502			$start = $start[1];
 503			$end = $this->reduce($for->end, true);
 504			$end = $end[1];
 505			$d = $start < $end ? 1 : -1;
 506
 507			while (true) {
 508				if ((!$for->until && $start - $d == $end) ||
 509					($for->until && $start == $end))
 510				{
 511					break;
 512				}
 513
 514				$this->set($for->var, array("number", $start, ""));
 515				$start += $d;
 516
 517				$ret = $this->compileChildren($for->children, $out);
 518				if ($ret) return $ret;
 519			}
 520
 521			break;
 522		case "nestedprop":
 523			list(,$prop) = $child;
 524			$prefixed = array();
 525			$prefix = $this->compileValue($prop->prefix) . "-";
 526			foreach ($prop->children as $child) {
 527				if ($child[0] == "assign") {
 528					array_unshift($child[1][2], $prefix);
 529				}
 530				if ($child[0] == "nestedprop") {
 531					array_unshift($child[1]->prefix[2], $prefix);
 532				}
 533				$prefixed[] = $child;
 534			}
 535			$this->compileChildren($prefixed, $out);
 536			break;
 537		case "include": // including a mixin
 538			list(,$name, $argValues) = $child;
 539			$mixin = $this->get(self::$namespaces["mixin"] . $name, false);
 540			if (!$mixin) break; // throw error?
 541
 542			// push scope, apply args
 543			$this->pushEnv();
 544			if (!is_null($mixin->args)) {
 545				$this->applyArguments($mixin->args, $argValues);
 546			}
 547
 548			foreach ($mixin->children as $child) {
 549				$this->compileChild($child, $out);
 550			}
 551
 552			$this->popEnv();
 553
 554			break;
 555		case "debug":
 556			list(,$value, $pos) = $child;
 557			$line = $this->parser->getLineNo($pos);
 558			$value = $this->compileValue($this->reduce($value, true));
 559			fwrite(STDERR, "Line $line DEBUG: $value\n");
 560			break;
 561		default:
 562			throw new exception("unknown child type: $child[0]");
 563		}
 564	}
 565
 566	protected function expToString($exp) {
 567		list(, $op, $left, $right, $inParens, $whiteLeft, $whiteRight) = $exp;
 568		$content = array($left);
 569		if ($whiteLeft) $content[] = " ";
 570		$content[] = $op;
 571		if ($whiteRight) $content[] = " ";
 572		$content[] = $right;
 573		return array("string", "", $content);
 574	}
 575
 576	// should $value cause its operand to eval
 577	protected function shouldEval($value) {
 578		switch ($value[0]) {
 579		case "exp":
 580			if ($value[1] == "/") {
 581				return $this->shouldEval($value[2], $value[3]);
 582			}
 583		case "var":
 584		case "fncall":
 585			return true;
 586		}
 587		return false;
 588	}
 589
 590	protected function reduce($value, $inExp = false) {
 591		list($type) = $value;
 592		switch ($type) {
 593			case "exp":
 594				list(, $op, $left, $right, $inParens) = $value;
 595				$opName = isset(self::$operatorNames[$op]) ? self::$operatorNames[$op] : $op;
 596
 597				$inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
 598
 599				$left = $this->reduce($left, true);
 600				$right = $this->reduce($right, true);
 601
 602				// only do division in special cases
 603				if ($opName == "div" && !$inParens && !$inExp) {
 604					if ($left[0] != "color" && $right[0] != "color") {
 605						return $this->expToString($value);
 606					}
 607				}
 608
 609				$left = $this->coerceForExpression($left);
 610				$right = $this->coerceForExpression($right);
 611
 612				$ltype = $left[0];
 613				$rtype = $right[0];
 614
 615				// this tries:
 616				// 1. op_[op name]_[left type]_[right type]
 617				// 2. op_[left type]_[right type] (passing the op as first arg
 618				// 3. op_[op name]
 619				$fn = "op_${opName}_${ltype}_${rtype}";
 620				if (is_callable(array($this, $fn)) ||
 621					(($fn = "op_${ltype}_${rtype}") &&
 622						is_callable(array($this, $fn)) &&
 623						$passOp = true) ||
 624					(($fn = "op_${opName}") &&
 625						is_callable(array($this, $fn)) &&
 626						$genOp = true))
 627				{
 628					$unitChange = false;
 629					if (!isset($genOp) &&
 630						$left[0] == "number" && $right[0] == "number")
 631					{
 632						$unitChange = true;
 633						if ($opName == "div" && $left[2] == $right[2]) {
 634							$targetUnit = "";
 635						} else {
 636							$targetUnit = $left[2];
 637							$left = $this->normalizeNumber($left);
 638							$right = $this->normalizeNumber($right);
 639						}
 640					}
 641
 642					$shouldEval = $inParens || $inExp;
 643					if (isset($passOp)) {
 644						$out = $this->$fn($op, $left, $right, $shouldEval);
 645					} else {
 646						$out = $this->$fn($left, $right, $shouldEval);
 647					}
 648
 649					if (!is_null($out)) {
 650						if ($unitChange && $out[0] == "number") {
 651							$out = $this->coerceUnit($out, $targetUnit);
 652						}
 653						return $out;
 654					}
 655				}
 656
 657				return $this->expToString($value);
 658			case "unary":
 659				list(, $op, $exp, $inParens) = $value;
 660				$inExp = $inExp || $this->shouldEval($exp);
 661
 662				$exp = $this->reduce($exp);
 663				if ($exp[0] == "number") {
 664					switch ($op) {
 665					case "+":
 666						return $exp;
 667					case "-":
 668						$exp[1] *= -1;
 669						return $exp;
 670					}
 671				}
 672
 673				if ($op == "not") {
 674					if ($inExp || $inParens) {
 675						if ($exp == self::$false) {
 676							return self::$true;
 677						} else {
 678							return self::$false;
 679						}
 680					} else {
 681						$op = $op . " ";
 682					}
 683				}
 684
 685				return array("string", "", array($op, $exp));
 686			case "var":
 687				list(, $name) = $value;
 688				return $this->reduce($this->get($name));
 689			case "list":
 690				foreach ($value[2] as &$item) {
 691					$item = $this->reduce($item);
 692				}
 693				return $value;
 694			case "string":
 695				foreach ($value[2] as &$item) {
 696					if (is_array($item)) {
 697						$item = $this->reduce($item);
 698					}
 699				}
 700				return $value;
 701			case "interpolate":
 702				$value[1] = $this->reduce($value[1]);
 703				return $value;
 704			case "fncall":
 705				list(,$name, $argValues) = $value;
 706
 707				// user defined function?
 708				$func = $this->get(self::$namespaces["function"] . $name, false);
 709				if ($func) {
 710					$this->pushEnv();
 711
 712					// set the args
 713					if (isset($func->args)) {
 714						$this->applyArguments($func->args, $argValues);
 715					}
 716
 717					// throw away lines and children
 718					$tmp = (object)array(
 719						"lines" => array(),
 720						"children" => array()
 721					);
 722					$ret = $this->compileChildren($func->children, $tmp);
 723					$this->popEnv();
 724
 725					return is_null($ret) ? self::$defaultValue : $ret;
 726				}
 727
 728				// built in function
 729				if ($this->callBuiltin($name, $argValues, $returnValue)) {
 730					return $returnValue;
 731				}
 732
 733				// need to flatten the arguments into a list
 734				$listArgs = array();
 735				foreach ((array)$argValues as $arg) {
 736					if (empty($arg[0])) {
 737						$listArgs[] = $this->reduce($arg[1]);
 738					}
 739				}
 740				return array("function", $name, array("list", ",", $listArgs));
 741			default:
 742				return $value;
 743		}
 744	}
 745
 746	// just does physical lengths for now
 747	protected function normalizeNumber($number) {
 748		list(, $value, $unit) = $number;
 749		if (isset(self::$unitTable["in"][$unit])) {
 750			$conv = self::$unitTable["in"][$unit];
 751			return array("number", $value / $conv, "in");
 752		}
 753		return $number;
 754	}
 755
 756	// $number should be normalized
 757	protected function coerceUnit($number, $unit) {
 758		list(, $value, $baseUnit) = $number;
 759		if (isset(self::$unitTable[$baseUnit][$unit])) {
 760			$value = $value * self::$unitTable[$baseUnit][$unit];
 761		}
 762
 763		return array("number", $value, $unit);
 764	}
 765
 766	protected function op_add_number_number($left, $right) {
 767		return array("number", $left[1] + $right[1], $left[2]);
 768	}
 769
 770	protected function op_mul_number_number($left, $right) {
 771		return array("number", $left[1] * $right[1], $left[2]);
 772	}
 773
 774	protected function op_sub_number_number($left, $right) {
 775		return array("number", $left[1] - $right[1], $left[2]);
 776	}
 777
 778	protected function op_div_number_number($left, $right) {
 779		return array("number", $left[1] / $right[1], $left[2]);
 780	}
 781
 782	protected function op_mod_number_number($left, $right) {
 783		return array("number", $left[1] % $right[1], $left[2]);
 784	}
 785
 786	// adding strings
 787	protected function op_add($left, $right) {
 788		if ($strLeft = $this->coerceString($left)) {
 789			if ($right[0] == "string") {
 790				$right[1] = "";
 791			}
 792			$strLeft[2][] = $right;
 793			return $strLeft;
 794		}
 795
 796		if ($strRight = $this->coerceString($right)) {
 797			if ($left[0] == "string") {
 798				$left[1] = "";
 799			}
 800			array_unshift($strRight[2], $left);
 801			return $strRight;
 802		}
 803	}
 804
 805	protected function op_and($left, $right, $shouldEval) {
 806		if (!$shouldEval) return;
 807		if ($left != self::$false) return $right;
 808		return $left;
 809	}
 810
 811	protected function op_or($left, $right, $shouldEval) {
 812		if (!$shouldEval) return;
 813		if ($left != self::$false) return $left;
 814		return $right;
 815	}
 816
 817	protected function op_color_color($op, $left, $right) {
 818		$out = array('color');
 819		foreach (range(1, 3) as $i) {
 820			$lval = isset($left[$i]) ? $left[$i] : 0;
 821			$rval = isset($right[$i]) ? $right[$i] : 0;
 822			switch ($op) {
 823			case '+':
 824				$out[] = $lval + $rval;
 825				break;
 826			case '-':
 827				$out[] = $lval - $rval;
 828				break;
 829			case '*':
 830				$out[] = $lval * $rval;
 831				break;
 832			case '%':
 833				$out[] = $lval % $rval;
 834				break;
 835			case '/':
 836				if ($rval == 0) {
 837					throw new exception("color: Can't divide by zero");
 838				}
 839				$out[] = $lval / $rval;
 840				break;
 841			default:
 842				throw new exception("color: unknow op $op");
 843			}
 844		}
 845
 846		if (isset($left[4])) $out[4] = $left[4];
 847		elseif (isset($right[4])) $out[4] = $right[4];
 848
 849		return $this->fixColor($out);
 850	}
 851
 852	protected function op_color_number($op, $left, $right) {
 853		$value = $right[1];
 854		return $this->op_color_color($op, $left,
 855			array("color", $value, $value, $value));
 856	}
 857
 858	protected function op_number_color($op, $left, $right) {
 859		$value = $left[1];
 860		return $this->op_color_color($op,
 861			array("color", $value, $value, $value), $right);
 862	}
 863
 864	protected function op_eq($left, $right) {
 865		if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
 866			$lStr[1] = "";
 867			$rStr[1] = "";
 868			return $this->toBool($this->compileValue($lStr) == $this->compileValue($rStr));
 869		}
 870
 871		return $this->toBool($left == $right);
 872	}
 873
 874	protected function op_neq($left, $right) {
 875		return $this->toBool($left != $right);
 876	}
 877
 878	protected function op_gte_number_number($left, $right) {
 879		return $this->toBool($left[1] >= $right[1]);
 880	}
 881
 882	protected function op_gt_number_number($left, $right) {
 883		return $this->toBool($left[1] > $right[1]);
 884	}
 885
 886	protected function op_lte_number_number($left, $right) {
 887		return $this->toBool($left[1] <= $right[1]);
 888	}
 889
 890	protected function op_lt_number_number($left, $right) {
 891		return $this->toBool($left[1] < $right[1]);
 892	}
 893
 894	protected function toBool($thing) {
 895		return $thing ? self::$true : self::$false;
 896	}
 897
 898	protected function compileValue($value) {
 899		$value = $this->reduce($value);
 900
 901		list($type) = $value;
 902		switch ($type) {
 903		case "keyword":
 904			return $value[1];
 905		case "color":
 906			// [1] - red component (either number for a %)
 907			// [2] - green component
 908			// [3] - blue component
 909			// [4] - optional alpha component
 910			list(, $r, $g, $b) = $value;
 911
 912			$r = round($r);
 913			$g = round($g);
 914			$b = round($b);
 915
 916			if (count($value) == 5 && $value[4] != 1) { // rgba
 917				return 'rgba('.$r.', '.$g.', '.$b.', '.$value[4].')';
 918			}
 919
 920			$h = sprintf("#%02x%02x%02x", $r, $g, $b);
 921
 922			// Converting hex color to short notation (e.g. #003399 to #039)
 923			if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
 924				$h = '#' . $h[1] . $h[3] . $h[5];
 925			}
 926
 927			return $h;
 928		case "number":
 929			return round($value[1], self::$numberPrecision) . $value[2];
 930		case "string":
 931			return $value[1] . $this->compileStringContent($value) . $value[1];
 932		case "function":
 933			$args = !empty($value[2]) ? $this->compileValue($value[2]) : "";
 934			return "$value[1]($args)";
 935		case "list":
 936			$value = $this->extractInterpolation($value);
 937			if ($value[0] != "list") return $this->compileValue($value);
 938
 939			list(, $delim, $items) = $value;
 940			foreach ($items as &$item) {
 941				$item = $this->compileValue($item);
 942			}
 943			return implode("$delim ", $items);
 944		case "interpolated": # node created by extractInterpolation
 945			list(, $interpolate, $left, $right) = $value;
 946			list(,, $whiteLeft, $whiteRight) = $interpolate;
 947
 948			$left = count($left[2]) > 0 ?
 949				$this->compileValue($left).$whiteLeft : "";
 950
 951			$right = count($right[2]) > 0 ?
 952				$whiteRight.$this->compileValue($right) : "";
 953
 954			return $left.$this->compileValue($interpolate).$right;
 955
 956		case "interpolate": # raw parse node
 957			list(, $exp) = $value;
 958
 959			// strip quotes if it's a string
 960			$reduced = $this->reduce($exp);
 961			if ($reduced[0] == "string") {
 962				$reduced = array("keyword",
 963					$this->compileStringContent($reduced));
 964			}
 965
 966			return $this->compileValue($reduced);
 967		default:
 968			throw new exception("unknown value type: $type");
 969		}
 970	}
 971
 972	protected function compileStringContent($string) {
 973		$parts = array();
 974		foreach ($string[2] as $part) {
 975			if (is_array($part)) {
 976				$parts[] = $this->compileValue($part);
 977			} else {
 978				$parts[] = $part;
 979			}
 980		}
 981
 982		return implode($parts);
 983	}
 984
 985	// doesn't need to be recursive, compileValue will handle that
 986	protected function extractInterpolation($list) {
 987		$items = $list[2];
 988		foreach ($items as $i => $item) {
 989			if ($item[0] == "interpolate") {
 990				$before = array("list", $list[1], array_slice($items, 0, $i));
 991				$after = array("list", $list[1], array_slice($items, $i + 1));
 992				return array("interpolated", $item, $before, $after);
 993			}
 994		}
 995		return $list;
 996	}
 997
 998	// find the final set of selectors
 999	protected function multiplySelectors($env, $childSelectors = null) {
1000		if (is_null($env)) {
1001			return $childSelectors;
1002		}
1003
1004		// skip env, has no selectors
1005		if (empty($env->selectors)) {
1006			return $this->multiplySelectors($env->parent, $childSelectors);
1007		}
1008
1009		if (is_null($childSelectors)) {
1010			$selectors = $env->selectors;
1011		} else {
1012			$selectors = array();
1013			foreach ($env->selectors as $parent) {
1014				foreach ($childSelectors as $child) {
1015					$selectors[] = $this->joinSelectors($parent, $child);
1016				}
1017			}
1018		}
1019
1020		return $this->multiplySelectors($env->parent, $selectors);
1021	}
1022
1023	// looks for & to replace, or append parent before child
1024	protected function joinSelectors($parent, $child) {
1025		$setSelf = false;
1026		$out = array();
1027		foreach ($child as $part) {
1028			$newPart = array();
1029			foreach ($part as $p) {
1030				if ($p == self::$selfSelector) {
1031					$setSelf = true;
1032					foreach ($parent as $i => $parentPart) {
1033						if ($i > 0) {
1034							$out[] = $newPart;
1035							$newPart = array();
1036						}
1037
1038						foreach ($parentPart as $pp) {
1039							$newPart[] = $pp;
1040						}
1041					}
1042				} else {
1043					$newPart[] = $p;
1044				}
1045			}
1046
1047			$out[] = $newPart;
1048		}
1049
1050		return $setSelf ? $out : array_merge($parent, $child);
1051	}
1052
1053	protected function multiplyMedia($env, $childMedia = null) {
1054		if (is_null($env) ||
1055			!empty($env->block->type) && $env->block->type != "media")
1056		{
1057			return $childMedia;
1058		}
1059
1060		// plain old block, skip
1061		if (empty($env->block->type)) {
1062			return $this->multiplyMedia($env->parent, $childMedia);
1063		}
1064
1065		$query = $env->block->query;
1066		if ($childMedia == null) {
1067			$childMedia = $query;
1068		} else {
1069			$childMedia = array_merge($query, $childMedia);
1070		}
1071
1072		return $this->multiplyMedia($env->parent, $childMedia);
1073	}
1074
1075	// convert something to list
1076	protected function coerceList($item, $delim = ",") {
1077		if (!is_null($item) && $item[0] == "list") {
1078			return $item;
1079		}
1080
1081		return array("list", $delim, is_null($item) ? array(): array($item));
1082	}
1083
1084	protected function applyArguments($argDef, $argValues) {
1085		$argValues = (array)$argValues;
1086
1087		$keywordArgs = array();
1088		$remaining = array();
1089
1090		// assign the keyword args
1091		foreach ($argValues as $arg) {
1092			if (!empty($arg[0])) {
1093				$keywordArgs[$arg[0][1]] = $arg[1];
1094			} else {
1095				$remaining[] = $arg[1];
1096			}
1097		}
1098
1099		foreach ($argDef as $i => $arg) {
1100			list($name, $default) = $arg;
1101
1102			if (isset($remaining[$i])) {
1103				$val = $remaining[$i];
1104			} elseif (isset($keywordArgs[$name])) {
1105				$val = $keywordArgs[$name];
1106			} elseif (!empty($default)) {
1107				$val = $default;
1108			} else {
1109				$val = self::$defaultValue;
1110			}
1111
1112			$this->set($name, $this->reduce($val, true));
1113		}
1114	}
1115
1116	protected function pushEnv($block=null) {
1117		$env = new stdclass;
1118		$env->parent = $this->env;
1119		$env->store = array();
1120		$env->block = $block;
1121		$env->depth = isset($this->env->depth) ? $this->env->depth + 1 : 0;
1122
1123		$this->env = $env;
1124		return $env;
1125	}
1126
1127	protected function normalizeName($name) {
1128		return str_replace("-", "_", $name);
1129	}
1130
1131	protected function set($name, $value) {
1132		$name = $this->normalizeName($name);
1133		$this->setExisting($name, $value);
1134	}
1135
1136	protected function setExisting($name, $value, $env = null) {
1137		if (is_null($env)) $env = $this->env;
1138		if (isset($env->store[$name])) {
1139			$env->store[$name] = $value;
1140		} elseif (!is_null($env->parent)) {
1141			$this->setExisting($name, $value, $env->parent);
1142		} else {
1143			$this->env->store[$name] = $value;
1144		}
1145	}
1146
1147	protected function get($name, $defaultValue = null, $env = null) {
1148		$name = $this->normalizeName($name);
1149
1150		if (is_null($env)) $env = $this->env;
1151		if (is_null($defaultValue)) $defaultValue = self::$defaultValue;
1152
1153		if (isset($env->store[$name])) {
1154			return $env->store[$name];
1155		} elseif (!is_null($env->parent)) {
1156			return $this->get($name, $defaultValue, $env->parent);
1157		}
1158
1159		return $defaultValue; // found nothing
1160	}
1161
1162	protected function popEnv() {
1163		$env = $this->env;
1164		$this->env = $this->env->parent;
1165		return $env;
1166	}
1167
1168	public function getParsedFiles() {
1169		return $this->parsedFiles;
1170	}
1171
1172	public function addImportPath($path) {
1173		$this->importPaths[] = $path;
1174	}
1175
1176	public function setImportPaths($path) {
1177		$this->importPaths = (array)$path;
1178	}
1179
1180	public function setFormatter($formatterName) {
1181		$this->formatter = $formatterName;
1182	}
1183
1184	public function registerFunction($name, $func) {
1185		$this->userFunctions[$this->normalizeName($name)] = $func;
1186	}
1187
1188	public function unregisterFunction($name) {
1189		unset($this->userFunctions[$this->normalizeName($name)]);
1190	}
1191
1192	protected function importFile($path, $out) {
1193		// see if tree is cached
1194		$realPath = realpath($path);
1195		if (isset($this->importCache[$realPath])) {
1196			$tree = $this->importCache[$realPath];
1197		} else {
1198			$code = file_get_contents($path);
1199			$parser = new scss_parser($path);
1200			$tree = $parser->parse($code);
1201			$this->parsedFiles[] = $path;
1202
1203			$this->importCache[$realPath] = $tree;
1204		}
1205
1206		$pi = pathinfo($path);
1207		array_unshift($this->importPaths, $pi['dirname']);
1208		$this->compileChildren($tree->children, $out);
1209		array_shift($this->importPaths);
1210	}
1211
1212	// results the file path for an import url if it exists
1213	protected function findImport($url) {
1214		if (preg_match('/\.css|^http:\/\/$/', $url)) return null;
1215
1216		// try the _partial filename
1217		$_url = preg_replace('/[^\/]+$/', '_\0', $url);
1218
1219		foreach ((array)$this->importPaths as $dir) {
1220			foreach (array($url, $_url) as $full) {
1221				$full = $dir .
1222					(!empty($dir) && substr($dir, -1) != '/' ? '/' : '') .
1223					$full;
1224
1225				if ($this->fileExists($file = $full.'.scss') ||
1226					$this->fileExists($file = $full))
1227				{
1228					return $file;
1229				}
1230			}
1231		}
1232
1233		return null;
1234	}
1235
1236	protected function fileExists($name) {
1237		return is_file($name);
1238	}
1239
1240	protected function callBuiltin($name, $args, &$returnValue) {
1241		// try a lib function
1242		$name = $this->normalizeName($name);
1243		$libName = "lib_".$name;
1244		$f = array($this, $libName);
1245		$prototype = isset(self::$$libName) ? self::$$libName : null;
1246
1247		if (is_callable($f)) {
1248			$sorted = $this->sortArgs($prototype, $args);
1249			foreach ($sorted as &$val) {
1250				$val = $this->reduce($val, true);
1251			}
1252			$returnValue = call_user_func($f, $sorted, $this);
1253		} else if (isset($this->userFunctions[$name])) {
1254			// see if we can find a user function
1255			$fn = $this->userFunctions[$name];
1256
1257			foreach ($args as &$val) {
1258				$val = $this->reduce($val[1], true);
1259			}
1260
1261			$returnValue = call_user_func($fn, $args, $this);
1262		}
1263
1264		if (isset($returnValue)) {
1265			// coerce a php value into a scss one
1266			if (is_numeric($returnValue)) {
1267				$returnValue = array('number', $returnValue, "");
1268			} elseif (is_bool($returnValue)) {
1269				$returnValue = $returnValue ? self::$true : self::$false;
1270			} elseif (!is_array($returnValue)) {
1271				$returnValue = array('keyword', $returnValue);
1272			}
1273
1274			return true;
1275		}
1276
1277		return false;
1278	}
1279
1280	// sorts any keyword arguments
1281	// TODO: merge with apply arguments
1282	protected function sortArgs($prototype, $args) {
1283		$keyArgs = array();
1284		$posArgs = array();
1285
1286		foreach ($args as $arg) {
1287			list($key, $value) = $arg;
1288			$key = $key[1];
1289			if (empty($key)) {
1290				$posArgs[] = $value;
1291			} else {
1292				$keyArgs[$key] = $value;
1293			}
1294		}
1295
1296		if (is_null($prototype)) return $posArgs;
1297
1298		$finalArgs = array();
1299		foreach ($prototype as $i => $names) {
1300			if (isset($posArgs[$i])) {
1301				$finalArgs[] = $posArgs[$i];
1302				continue;
1303			}
1304
1305			$set = false;
1306			foreach ((array)$names as $name) {
1307				if (isset($keyArgs[$name])) {
1308					$finalArgs[] = $keyArgs[$name];
1309					$set = true;
1310					break;
1311				}
1312			}
1313
1314			if (!$set) {
1315				$finalArgs[] = null;
1316			}
1317		}
1318
1319		return $finalArgs;
1320	}
1321
1322	protected function coerceForExpression($value) {
1323		if ($color = $this->coerceColor($value)) {
1324			return $color;
1325		}
1326
1327		return $value;
1328	}
1329
1330	protected function coerceColor($value) {
1331		switch ($value[0]) {
1332		case "color": return $value;
1333		case "keyword":
1334			$name = $value[1];
1335			if (isset(self::$cssColors[$name])) {
1336				list($r, $g, $b) = explode(',', self::$cssColors[$name]);
1337				return array('color', $r, $g, $b);
1338			}
1339			return null;
1340		}
1341
1342		return null;
1343	}
1344
1345	protected function coerceString($value) {
1346		switch ($value[0]) {
1347		case "string":
1348			return $value;
1349		case "keyword":
1350			return array("string", "", array($value[1]));
1351		}
1352		return null;
1353	}
1354
1355	protected function assertColor($value) {
1356		if ($color = $this->coerceColor($value)) return $color;
1357		throw new exception("expecting color");
1358	}
1359
1360	protected function assertNumber($value) {
1361		if ($value[0] != "number")
1362			throw new exception("expecting number");
1363		return $value[1];
1364	}
1365
1366	protected function coercePercent($value) {
1367		if ($value[0] == "number") {
1368			if ($value[2] == "%") {
1369				return $value[1] / 100;
1370			}
1371			return $value[1];
1372		}
1373		return 0;
1374	}
1375
1376	// make sure a color's components don't go out of bounds
1377	protected function fixColor($c) {
1378		foreach (range(1, 3) as $i) {
1379			if ($c[$i] < 0) $c[$i] = 0;
1380			if ($c[$i] > 255) $c[$i] = 255;
1381		}
1382
1383		return $c;
1384	}
1385
1386	function toHSL($r, $g, $b) {
1387		$r = $r / 255;
1388		$g = $g / 255;
1389		$b = $b / 255;
1390
1391		$min = min($r, $g, $b);
1392		$max = max($r, $g, $b);
1393
1394		$L = ($min + $max) / 2;
1395		if ($min == $max) {
1396			$S = $H = 0;
1397		} else {
1398			if ($L < 0.5)
1399				$S = ($max - $min)/($max + $min);
1400			else
1401				$S = ($max - $min)/(2.0 - $max - $min);
1402
1403			if ($r == $max) $H = ($g - $b)/($max - $min);
1404			elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
1405			elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
1406
1407		}
1408
1409		return array('hsl',
1410			($H < 0 ? $H + 6 : $H)*60,
1411			$S*100,
1412			$L*100,
1413		);
1414	}
1415
1416	function toRGB_helper($comp, $temp1, $temp2) {
1417		if ($comp < 0) $comp += 1.0;
1418		elseif ($comp > 1) $comp -= 1.0;
1419
1420		if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
1421		if (2 * $comp < 1) return $temp2;
1422		if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
1423
1424		return $temp1;
1425	}
1426
1427	// H from 0 to 360, S and L from 0 to 100
1428	function toRGB($H, $S, $L) {
1429		$H = $H % 360;
1430		if ($H < 0) $H += 360;
1431
1432		$S = min(100, max(0, $S));
1433		$L = min(100, max(0, $L));
1434
1435		$H = $H / 360;
1436		$S = $S / 100;
1437		$L = $L / 100;
1438
1439		if ($S == 0) {
1440			$r = $g = $b = $L;
1441		} else {
1442			$temp2 = $L < 0.5 ?
1443				$L*(1.0 + $S) :
1444				$L + $S - $L * $S;
1445
1446			$temp1 = 2.0 * $L - $temp2;
1447
1448			$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
1449			$g = $this->toRGB_helper($H, $temp1, $temp2);
1450			$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
1451		}
1452
1453		$out = array('color', $r*255, $g*255, $b*255);
1454		return $out;
1455	}
1456
1457	// Built in functions
1458
1459	protected static $lib_if = array("condition", "if-true", "if-false");
1460	protected function lib_if($args) {
1461		list($cond,$t, $f) = $args;
1462		if ($cond == self::$false) return $f;
1463		return $t;
1464	}
1465
1466	protected static $lib_rgb = array("red", "green", "blue");
1467	protected function lib_rgb($args) {
1468		list($r,$g,$b) = $args;
1469		return array("color", $r[1], $g[1], $b[1]);
1470	}
1471
1472	protected static $lib_rgba = array(
1473		array("red", "color"),
1474		"green", "blue", "alpha");
1475	protected function lib_rgba($args) {
1476		if ($color = $this->coerceColor($args[0])) {
1477			$num = is_null($args[1]) ? $args[3] : $args[1];
1478			$alpha = $this->assertNumber($num);
1479			$color[4] = $alpha;
1480			return $color;
1481		}
1482
1483		list($r,$g,$b, $a) = $args;
1484		return array("color", $r[1], $g[1], $b[1], $a[1]);
1485	}
1486
1487	// helper function for adjust_color, change_color, and scale_color
1488	protected function alter_color($args, $fn) {
1489		$color = $this->assertColor($args[0]);
1490
1491		foreach (array(1,2,3,7) as $i) {
1492			if (!is_null($args[$i])) {
1493				$val = $this->assertNumber($args[$i]);
1494				$ii = $i == 7 ? 4 : $i; // alpha
1495				$color[$ii] =
1496					$this->$fn(isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
1497			}
1498		}
1499
1500		if (!is_null($args[4]) || !is_null($args[5]) || !is_null($args[6])) {
1501			$hsl = $this->toHSL($color[1], $color[2], $color[3]);
1502			foreach (array(4,5,6) as $i) {
1503				if (!is_null($args[$i])) {
1504					$val = $this->assertNumber($args[$i]);
1505					$hsl[$i - 3] = $this->$fn($hsl[$i - 3], $val, $i);
1506				}
1507			}
1508
1509			$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
1510			if (isset($color[4])) $rgb[4] = $color[4];
1511			$color = $rgb;
1512		}
1513
1514		return $color;
1515	}
1516
1517	protected static $lib_adjust_color = array(
1518		"color", "red", "green", "blue",
1519		"hue", "saturation", "lightness", "alpha"
1520	);
1521	protected function adjust_color_helper($base, $alter, $i) {
1522		return $base += $alter;
1523	}
1524	protected function lib_adjust_color($args) {
1525		return $this->alter_color($args, "adjust_color_helper");
1526	}
1527
1528	protected static $lib_change_color = array(
1529		"color", "red", "green", "blue",
1530		"hue", "saturation", "lightness", "alpha"
1531	);
1532	protected function change_color_helper($base, $alter, $i) {
1533		return $alter;
1534	}
1535	protected function lib_change_color($args) {
1536		return $this->alter_color($args, "change_color_helper");
1537	}
1538
1539	protected static $lib_scale_color = array(
1540		"color", "red", "green", "blue",
1541		"hue", "saturation", "lightness", "alpha"
1542	);
1543	protected function scale_color_helper($base, $scale, $i) {
1544		// 1,2,3 - rgb
1545		// 4, 5, 6 - hsl
1546		// 7 - a
1547		switch ($i) {
1548		case 1:
1549		case 2:
1550		case 3:
1551			$max = 255; break;
1552		case 4:
1553			$max = 360; break;
1554		case 7:
1555			$max = 1; break;
1556		default:
1557			$max = 100;
1558		}
1559
1560		$scale = $scale / 100;
1561		if ($scale < 0) {
1562			return $base * $scale + $base;
1563		} else {
1564			return ($max - $base) * $scale + $base;
1565		}
1566	}
1567	protected function lib_scale_color($args) {
1568		return $this->alter_color($args, "scale_color_helper");
1569	}
1570
1571	protected static $lib_red = array("color");
1572	protected function lib_red($args) {
1573		list($color) = $args;
1574		return $color[1];
1575	}
1576
1577	protected static $lib_green = array("color");
1578	protected function lib_green($args) {
1579		list($color) = $args;
1580		return $color[2];
1581	}
1582
1583	protected static $lib_blue = array("color");
1584	protected function lib_blue($args) {
1585		list($color) = $args;
1586		return $color[3];
1587	}
1588
1589	protected static $lib_alpha = array("color");
1590	protected function lib_alpha($args) {
1591		if ($color = $this->coerceColor($args[0])) {
1592			return isset($color[4]) ? $color[4] : 1;
1593		}
1594
1595		// this might be the IE function, so return value unchanged
1596		return array("function", "alpha", array("list", ",", $args));
1597	}
1598
1599	protected static $lib_opacity = array("color");
1600	protected function lib_opacity($args) {
1601		return $this->lib_alpha($args);
1602	}
1603
1604	// mix two colors
1605	protected static $lib_mix = array("color-1", "color-2", "weight");
1606	protected function lib_mix($args) {
1607		list($first, $second, $weight) = $args;
1608		$first = $this->assertColor($first);
1609		$second = $this->assertColor($second);
1610
1611		if (is_null($weight)) {
1612			$weight = 0.5;
1613		} else {
1614			$weight = $this->coercePercent($weight);
1615		}
1616
1617		$first_a = isset($first[4]) ? $first[4] : 1;
1618		$second_a = isset($second[4]) ? $second[4] : 1;
1619
1620		$w = $weight * 2 - 1;
1621		$a = $first_a - $second_a;
1622
1623		$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1624		$w2 = 1.0 - $w1;
1625
1626		$new = array('color',
1627			$w1 * $first[1] + $w2 * $second[1],
1628			$w1 * $first[2] + $w2 * $second[2],
1629			$w1 * $first[3] + $w2 * $second[3],
1630		);
1631
1632		if ($first_a != 1.0 || $second_a != 1.0) {
1633			$new[] = $first_a * $weight + $second_a * ($weight - 1);
1634		}
1635
1636		return $this->fixColor($new);
1637	}
1638
1639	protected static $lib_hsl = array("hue", "saturation", "lightness");
1640	protected function lib_hsl($args) {
1641		list($h, $s, $l) = $args;
1642		return $this->toRGB($h[1], $s[1], $l[1]);
1643	}
1644
1645	protected static $lib_hsla = array("hue", "saturation",
1646		"lightness", "alpha");
1647	protected function lib_hsla($args) {
1648		list($h, $s, $l, $a) = $args;
1649		$color = $this->toRGB($h[1], $s[1], $l[1]);
1650		$color[4] = $a[1];
1651		return $color;
1652	}
1653
1654	protected static $lib_hue = array("color");
1655	protected function lib_hue($args) {
1656		$color = $this->assertColor($args[0]);
1657		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
1658		return array("number", $hsl[1], "deg");
1659	}
1660
1661	protected static $lib_saturation = array("color");
1662	protected function lib_saturation($args) {
1663		$color = $this->assertColor($args[0]);
1664		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
1665		return array("number", $hsl[2], "%");
1666	}
1667
1668	protected static $lib_lightness = array("color");
1669	protected function lib_lightness($args) {
1670		$color = $this->assertColor($args[0]);
1671		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
1672		return array("number", $hsl[3], "%");
1673	}
1674
1675
1676	protected function adjustHsl($color, $idx, $amount) {
1677		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
1678		$hsl[$idx] += $amount;
1679		$out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
1680		if (isset($color[4])) $out[4] = $color[4];
1681		return $out;
1682	}
1683
1684	protected static $lib_adjust_hue = array("color", "degrees");
1685	protected function lib_adjust_hue($args) {
1686		$color = $this->assertColor($args[0]);
1687		$degrees = $this->assertNumber($args[1]);
1688		return $this->adjustHsl($color, 1, $degrees);
1689	}
1690
1691	protected static $lib_lighten = array("color", "amount");
1692	protected function lib_lighten($args) {
1693		$color = $this->assertColor($args[0]);
1694		$amount = 100*$this->coercePercent($args[1]);
1695		return $this->adjustHsl($color, 3, $amount);
1696	}
1697
1698	protected static $lib_darken = array("color", "amount");
1699	protected function lib_darken($args) {
1700		$color = $this->assertColor($args[0]);
1701		$amount = 100*$this->coercePercent($args[1]);
1702		return $this->adjustHsl($color, 3, -$amount);
1703	}
1704
1705	protected static $lib_saturate = array("color", "amount");
1706	protected function lib_saturate($args) {
1707		$color = $this->assertColor($args[0]);
1708		$amount = 100*$this->coercePercent($args[1]);
1709		return $this->adjustHsl($color, 2, $amount);
1710	}
1711
1712	protected static $lib_desaturate = array("color", "amount");
1713	protected function lib_desaturate($args) {
1714		$color = $this->assertColor($args[0]);
1715		$amount = 100*$this->coercePercent($args[1]);
1716		return $this->adjustHsl($color, 2, -$amount);
1717	}
1718
1719	protected static $lib_grayscale = array("color");
1720	protected function lib_grayscale($args) {
1721		return $this->adjustHsl($this->assertColor($args[0]), 2, -100);
1722	}
1723
1724	protected static $lib_complement = array("color");
1725	protected function lib_complement($args) {
1726		return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
1727	}
1728
1729	protected static $lib_invert = array("color");
1730	protected function lib_invert($args) {
1731		$color = $this->assertColor($args[0]);
1732		$color[1] = 255 - $color[1];
1733		$color[2] = 255 - $color[2];
1734		$color[3] = 255 - $color[3];
1735		return $color;
1736	}
1737
1738
1739	// increases opacity by amount
1740	protected static $lib_opacify = array("color", "amount");
1741	protected function lib_opacify($args) {
1742		$color = $this->assertColor($args[0]);
1743		$amount = $this->coercePercent($args[1]);
1744
1745		$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
1746		$color[4] = min(1, max(0, $color[4]));
1747		return $color;
1748	}
1749
1750	protected static $lib_fade_in = array("color", "amount");
1751	protected function lib_fade_in($args) {
1752		return $this->lib_opacify($args);
1753	}
1754
1755	// decreases opacity by amount
1756	protected static $lib_transparentize = array("color", "amount");
1757	protected function lib_transparentize($args) {
1758		$color = $this->assertColor($args[0]);
1759		$amount = $this->coercePercent($args[1]);
1760
1761		$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
1762		$color[4] = min(1, max(0, $color[4]));
1763		return $color;
1764	}
1765
1766	protected static $lib_fade_out = array("color", "amount");
1767	protected function lib_fade_out($args) {
1768		return $this->lib_transparentize($args);
1769	}
1770
1771	protected static $lib_unquote = array("string");
1772	protected function lib_unquote($args) {
1773		$str = $args[0];
1774		if ($str[0] == "string") $str[1] = "";
1775		return $str;
1776	}
1777
1778	protected static $lib_quote = array("string");
1779	protected function lib_quote($args) {
1780		$value = $args[0];
1781		if ($value[0] == "string" && !empty($value[1]))
1782			return $value;
1783		return array("string", '"', array($value));
1784	}
1785
1786	protected static $lib_percentage = array("value");
1787	protected function lib_percentage($args) {
1788		return array("number",
1789			$this->coercePercent($args[0]) * 100,
1790			"%");
1791	}
1792
1793	protected static $lib_round = array("value");
1794	protected function lib_round($args) {
1795		$num = $args[0];
1796		$num[1] = round($num[1]);
1797		return $num;
1798	}
1799
1800	protected static $lib_floor = array("value");
1801	protected function lib_floor($args) {
1802		$num = $args[0];
1803		$num[1] = floor($num[1]);
1804		return $num;
1805	}
1806
1807	protected static $lib_ceil = array("value");
1808	protected function lib_ceil($args) {
1809		$num = $args[0];
1810		$num[1] = ceil($num[1]);
1811		return $num;
1812	}
1813
1814	protected static $lib_length = array("list");
1815	protected function lib_length($args) {
1816		$list = $this->coerceList($args[0]);
1817		return count($list[2]);
1818	}
1819
1820	protected static $lib_nth = array("list", "n");
1821	protected function lib_nth($args) {
1822		$list = $this->coerceList($args[0]);
1823		$n = $this->assertNumber($args[1]) - 1;
1824		return isset($list[2][$n]) ? $list[2][$n] : self::$defaultValue;
1825	}
1826
1827
1828	protected function listSeparatorForJoin($list1, $sep) {
1829		if (is_null($sep)) return $list1[1];
1830		switch ($this->compileValue($sep)) {
1831		case "comma":
1832			return ",";
1833		case "space":
1834			return "";
1835		default:
1836			return $list1[1];
1837		}
1838	}
1839
1840	protected static $lib_join = array("list1", "list2", "separator");
1841	protected function lib_join($args) {
1842		list($list1, $list2, $sep) = $args;
1843		$list1 = $this->coerceList($list1, " ");
1844		$list2 = $this->coerceList($list2, " ");
1845		$sep = $this->listSeparatorForJoin($list1, $sep);
1846		return array("list", $sep, array_merge($list1[2], $list2[2]));
1847	}
1848
1849	protected static $lib_append = array("list", "val", "separator");
1850	protected function lib_append($args) {
1851		list($list1, $value, $sep) = $args;
1852		$list1 = $this->coerceList($list1, " ");
1853		$sep = $this->listSeparatorForJoin($list1, $sep);
1854		return array("list", $sep, array_merge($list1[2], array($value)));
1855	}
1856
1857
1858	protected static $lib_type_of = array("value");
1859	protected function lib_type_of($args) {
1860		$value = $args[0];
1861		switch ($value[0]) {
1862		case "keyword":
1863			if ($value == self::$true || $value == self::$false) {
1864				return "bool";
1865			}
1866
1867			if ($this->coerceColor($value)) {
1868				return "color";
1869			}
1870
1871			return "string";
1872		default:
1873			return $value[0];
1874		}
1875	}
1876
1877	protected static $lib_unit = array("number");
1878	protected function lib_unit($args) {
1879		$num = $args[0];
1880		if ($num[0] == "number") {
1881			return array("string", '"', array($num[2]));
1882		}
1883		return "";
1884	}
1885
1886	protected static $lib_unitless = array("number");
1887	protected function lib_unitless($args) {
1888		$value = $args[0];
1889		return $value[0] == "number" && empty($value[2]);
1890	}
1891
1892
1893	protected static $lib_comparable = array("number-1", "number-2");
1894	protected function lib_comparable($args) {
1895		return true; // TODO: THIS
1896	}
1897
1898	static protected $cssColors = array(
1899		'aliceblue' => '240,248,255',
1900		'antiquewhite' => '250,235,215',
1901		'aqua' => '0,255,255',
1902		'aquamarine' => '127,255,212',
1903		'azure' => '240,255,255',
1904		'beige' => '245,245,220',
1905		'bisque' => '255,228,196',
1906		'black' => '0,0,0',
1907		'blanchedalmond' => '255,235,205',
1908		'blue' => '0,0,255',
1909		'blueviolet' => '138,43,226',
1910		'brown' => '165,42,42',
1911		'burlywood' => '222,184,135',
1912		'cadetblue' => '95,158,160',
1913		'chartreuse' => '127,255,0',
1914		'chocolate' => '210,105,30',
1915		'coral' => '255,127,80',
1916		'cornflowerblue' => '100,149,237',
1917		'cornsilk' => '255,248,220',
1918		'crimson' => '220,20,60',
1919		'cyan' => '0,255,255',
1920		'darkblue' => '0,0,139',
1921		'darkcyan' => '0,139,139',
1922		'darkgoldenrod' => '184,134,11',
1923		'darkgray' => '169,169,169',
1924		'darkgreen' => '0,100,0',
1925		'darkgrey' => '169,169,169',
1926		'darkkhaki' => '189,183,107',
1927		'darkmagenta' => '139,0,139',
1928		'darkolivegreen' => '85,107,47',
1929		'darkorange' => '255,140,0',
1930		'darkorchid' => '153,50,204',
1931		'darkred' => '139,0,0',
1932		'darksalmon' => '233,150,122',
1933		'darkseagreen' => '143,188,143',
1934		'darkslateblue' => '72,61,139',
1935		'darkslategray' => '47,79,79',
1936		'darkslategrey' => '47,79,79',
1937		'darkturquoise' => '0,206,209',
1938		'darkviolet' => '148,0,211',
1939		'deeppink' => '255,20,147',
1940		'deepskyblue' => '0,191,255',
1941		'dimgray' => '105,105,105',
1942		'dimgrey' => '105,105,105',
1943		'dodgerblue' => '30,144,255',
1944		'firebrick' => '178,34,34',
1945		'floralwhite' => '255,250,240',
1946		'forestgreen' => '34,139,34',
1947		'fuchsia' => '255,0,255',
1948		'gainsboro' => '220,220,220',
1949		'ghostwhite' => '248,248,255',
1950		'gold' => '255,215,0',
1951		'goldenrod' => '218,165,32',
1952		'gray' => '128,128,128',
1953		'green' => '0,128,0',
1954		'greenyellow' => '173,255,47',
1955		'grey' => '128,128,128',
1956		'honeydew' => '240,255,240',
1957		'hotpink' => '255,105,180',
1958		'indianred' => '205,92,92',
1959		'indigo' => '75,0,130',
1960		'ivory' => '255,255,240',
1961		'khaki' => '240,230,140',
1962		'lavender' => '230,230,250',
1963		'lavenderblush' => '255,240,245',
1964		'lawngreen' => '124,252,0',
1965		'lemonchiffon' => '255,250,205',
1966		'lightblue' => '173,216,230',
1967		'lightcoral' => '240,128,128',
1968		'lightcyan' => '224,255,255',
1969		'lightgoldenrodyellow' => '250,250,210',
1970		'lightgray' => '211,211,211',
1971		'lightgreen' => '144,238,144',
1972		'lightgrey' => '211,211,211',
1973		'lightpink' => '255,182,193',
1974		'lightsalmon' => '255,160,122',
1975		'lightseagreen' => '32,178,170',
1976		'lightskyblue' => '135,206,250',
1977		'lightslategray' => '119,136,153',
1978		'lightslategrey' => '119,136,153',
1979		'lightsteelblue' => '176,196,222',
1980		'lightyellow' => '255,255,224',
1981		'lime' => '0,255,0',
1982		'limegreen' => '50,205,50',
1983		'linen' => '250,240,230',
1984		'magenta' => '255,0,255',
1985		'maroon' => '128,0,0',
1986		'mediumaquamarine' => '102,205,170',
1987		'mediumblue' => '0,0,205',
1988		'mediumorchid' => '186,85,211',
1989		'mediumpurple' => '147,112,219',
1990		'mediumseagreen' => '60,179,113',
1991		'mediumslateblue' => '123,104,238',
1992		'mediumspringgreen' => '0,250,154',
1993		'mediumturquoise' => '72,209,20

Large files files are truncated, but you can click here to view the full file