Location: PHPKode > scripts > Less.php > lib/entities.less.class.php
<?php
	require_once dirname(__FILE__) . '/common.less.class.php';
	require_once dirname(__FILE__) . '/functions.less.class.php';

	/**
	 * LessCode
	 *
	 * Main class. Use it to check LESS code and compile it.
	 * After initializing, the class will atempt to parse the
	 * code. If it sees anything it doesn't like, it will throw
	 * an exception. If not, you can call and echo output().
	 **/
	class LessCode extends LessScope {
		private $import_path = "./";
		private $base_path = "./";

		/**
		 * LessCode::__construct()
		 *
		 * @param	Mixed		$code		LESS code (optional)
		 **/
		public function __construct($code = false) {
			$this->debug = new LessDebug();
			
			if ($code !== false)
				$this->parse($code);
		}
		
		/**
		 * LessCode::setBasePath($path)
		 *
		 * Define the path for import paths defined between '<' and '>'
		 *
		 * @param	String		$path		Base path
		 **/
		public function setBasePath($path) {
			$this->base_path = $path;
		}
		
		/**
		 * LessCode::output()
		 *
		 * @return	CSS code
		 **/
		public function output() {
			$output = "";
			
			foreach ($this->imports as $import) {
				if (is_object($import)) {
					$output .= $import->output();
				} else {
					$output .= $import;
				}
			}

			foreach ($this->declarations as $declaration) {
				$output .= $declaration->output();
			}
			
			return $output;
		}
		
		/**
		 * LessCode::parseFile($path)
		 *
		 * @param	String		$path		Path to LESS file
		 * @return	Boolean				Success
		 **/
		public function parseFile($path) {
			if (!file_exists($path))
				return false;

			$this->import_path = dirname($path) . '/';

			return $this->parse(file_get_contents($path));
		}
		
		/**
		 * LessCode::parse($data)
		 *
		 * @param	String		$data		LESS code
		 * @return	Boolean				Success
		 **/
		public function parse($data) {
			// strip comments
			$data = preg_replace('|\/\*.*?\*\/|s', '', $data);
			$data = preg_replace('|\s*\/\/.*|', '', $data);
			
			while (strlen($data) > 0) {
				if (substr($data, 0, 8) == "@import ") {
					$data = ltrim(substr($data, 8));
					
					if (in_array($data{0}, array('"', "'", "<"))) {
						$use_base_path = ($data{0} == "<");
						$delim = ($use_base_path ? ">" : $data{0});

						if (($p = strpos($data, $delim, 1)) === false) {
							throw new Exception("Invalid import declaration - missing delimiter '{$delim}'");
						}
						
						$import = substr($data, 1, $p - 1);
						$data = ltrim(substr($data, $p + 1), ' ');
						if ($data{0} != ";" && $data{0} != "\n") {
							throw new Exception("Invalid import declaration - missing ';'");
						}
						$data = ltrim(substr($data, 1));
					} else {
						$use_base_path = false;
						$p1 = strpos($data, ';');
						$p2 = strpos($data, "\n");
						if ($p1 === false && $p2 === false) {
							$import = $data;
							$data = "";
						} elseif ($p1 !== false) {
							$import = rtrim(substr($data, 0, $p1));
							$data = ltrim(substr($data, $p1 + 1));
						} else {
							$import = rtrim(substr($data, 0, $p2));
							$data = ltrim(substr($data, $p2 + 1));
						}
					}
					
					if ($use_base_path) {
						$import = $this->base_path.$import;
					} else {
						$import = $this->import_path.$import;
					}
					if (!file_exists($import)) {
						continue;
					}
					if (substr($import, -4) == '.css') {
						// if it ends with .css, it's a CSS, everything else should be a .less
						$this->imports[] = file_get_contents($import);
					} else {
						if (!file_exists($import) && file_exists($import.'.less')) {
							// if the import path does not exist but there's a $import.less file, use it
							$import .= '.less';
						}
						
						$less = new LessCode();
						foreach ($this->variables as $k => $v)
							$less->setVariable($k, $v);
						$less->parseFile($import);
						
						$this->imports[] = $less;
					}
					continue;
				}
				if ($data{0} == "@") { // variables (maybe..)
					$should_be_variable = (($p = strpos($data, ':')) !== false && strpos(substr($data, 0, $p), '{') === false);

					if ($should_be_variable) {
						if (($p = strpos($data, ':')) === false) {
							throw new Exception("Invalid variable set - invalid sintax");
						}

						$var_name = trim(substr($data, 1, $p - 1));
						$data = ltrim(substr($data, $p + 1));

						if (($p = strpos($data, ';')) === false) {
							throw new Exception("Invalid variable set - no value");
						}

						$var_value = rtrim(substr($data, 0, $p));
						$data = ltrim(substr($data, $p + 1));

						$this->debug->output("Adding variable '{$var_name}'='{$var_value}'");
						$this->variables[$var_name] = $var_value;
						continue;
					}
				}

				if (($p = strpos($data, '{')) === false) {
					throw new Exception("Invalid declaration set - invalid sintax");
				}

				$dec_name = rtrim(substr($data, 0, $p));
				$data = substr($data, $p + 1);
				
				$brk_c = 0; $p = false;
				for ($i = 0; $i < strlen($data); $i++) {
					if ($data{$i} == "{")
						$brk_c++;
					elseif ($data{$i} == "}") {
						$brk_c--;
						if ($brk_c < 0) {
							$p = $i;
							break;
						}
					}
				}

				if ($p === false) {
					throw new Exception("Invalid declaration set - no ending");
				}

				$dec_value = trim(substr($data, 0, $p));
				$data = ltrim(substr($data, $p + 1));

				$this->debug->output("Adding declaration '".$dec_name."'");
				
				$this->declarations[] = new LessDeclaration($dec_name, $dec_value, $this, $this->debug);
			}
			
			return true;
		}
	}

	/**
	 * LessDeclaration
	 *
	 * This is used to store an element declaration or possibly a mixin.
	 * It can contain nested declarations.
	 **/
	class LessDeclaration extends LessScope {
		private $parameters = array();
		private $properties = array();
		private $mixins = array();
		private $names = array();
		private $is_mixin = false;
		private $last_if = false;
		private $last_if_success = true; // avoid @else alone to show up

		public function __construct($names, $data, &$parent, &$debug) {
			$this->parent = $parent;
			$this->debug = $debug;
			$names = ltrim($names);

			if (preg_match('/^(\..+?)\s*\(\s*(.*)\s*\)\s*$/', $names, $m)) {
				$debug->output("This declaration is a mixin");
				$this->is_mixin = true;
				$this->names = array($m[1]);
				
				$params = preg_split('/\s*;\s*/', $m[2], -1, PREG_SPLIT_NO_EMPTY);
				for ($i = 0; $i < count($params); $i++) {
					if ($params[$i]{0} != '@') {
						throw new Exception("Invalid mixin declaration (missing '@' before '{$params[$i]}')");
					}
					$params[$i] = substr($params[$i], 1);
					if (($p = strpos($params[$i], ':')) !== false) {
						$param_name = rtrim(substr($params[$i], 0, $p));
						$param_val = ltrim(substr($params[$i], $p + 1));
					} else {
						$param_name = $params[$i];
						$param_val = null;
					}
					
					if (isset($this->parameters[$param_name])) {
						throw new Exception("Invalid mixin declaration (duplicated parameter '@{$param_name}')");
					}
					$this->parameters[$param_name] = $param_val;
				}
			} else {
				$this->names = preg_split('/\s*,\s*/', trim($names), -1, PREG_SPLIT_NO_EMPTY);
			}
			
			$this->parse($data);
		}
		
		public function parse($data) {
			while (strlen($data) > 0) {
				if ($data{0} == ".") {
					$p = strpos($data, ';');
					$p2 = strpos($data, '{');
					
					if ($p2 === false || ($p2 !== false && $p2 > $p)) {
						// Mixin?
						if (($pp = strpos($data, '(')) !== false && $pp < $p) {
							$pp = strpos($data, ')', $pp);
							$p = strpos($data, ';', $pp);
						}
					}
					if ($p2 === false || ($p2 !== false && $p2 > $p)) {
						if ($p === false) {
							$prop_name = substr($data, 1);
							$data = "";
						} else {
							$prop_name = rtrim(substr($data, 1, $p - 1));
							$data = ltrim(substr($data, $p + 1));
						}
					
						if (($p = strpos($prop_name, '(')) !== false) {
							$prop_params = rtrim(ltrim(substr($prop_name, $p + 1)), ' )');
							$prop_name = rtrim(substr($prop_name, 0, $p));
						} else {
							$prop_params = "";
						}
					
						$this->debug->output("Included mixin '{$prop_name}' '{$prop_params}'");
						$mixin = $this->findMixin($prop_name);
						if ($mixin !== null) {
							$this->debug->output("Mixin found!");
							$mixin->setMixin();
							$this->mixins[] = array(
								'mixin'	=> $mixin,
								'params'=> preg_split('/\s*;\s*/', trim($prop_params))
							);
						}
						continue;
					}
				}
				$p_prop = strpos($data, ':', 1);
				$p_decl = strpos($data, '{');
				$p_prop_end = strpos($data, ';', $p_prop);
				
				if ($p_prop === false && $p_decl === false)
					break;
				
				if ($p_prop !== false && $p_prop_end !== false && ($p_decl === false || ($p_decl !== false && $p_decl > $p_prop && $p_decl > $p_prop_end))) {
					// Property
					$prop_name = rtrim(substr($data, 0, $p_prop));
					$data = ltrim(substr($data, $p_prop + 1));
				
					if (($p = strpos($data, ';')) === false) {
						$prop_value = $data;
						$data = "";
					} else {
						$prop_value = rtrim(substr($data, 0, $p));
						$data = ltrim(substr($data, $p + 1));
					}
				
					if ($prop_name{0} == '@') {
						$this->variables[substr($prop_name, 1)] = $prop_value;
						$this->debug->output("Variable '".substr($prop_name, 1)."'='{$prop_value}'");
					} else {
						$this->properties[] = array($prop_name, $prop_value);
						$this->debug->output("Property '{$prop_name}'='{$prop_value}'");
					}
					continue;
				}

				// Sub-group
				$decl_name = rtrim(substr($data, 0, $p_decl));
				$data = ltrim(substr($data, $p_decl + 1));
				
				// insure that the last declaration of a group has ';' before '}'
				$data = preg_replace('/([^;])\s*}/', '$1; }', $data);
				
				$brk_c = 0; $p = false;
				for ($i = 0; $i < strlen($data); $i++) {
					if ($data{$i} == "{")
						$brk_c++;
					elseif ($data{$i} == "}") {
						$brk_c--;
						if ($brk_c < 0) {
							$p = $i;
							break;
						}
					}
				}

				if ($p === false) {
					throw new Exception("Invalid sub-declaration set - no ending");
				}

				$decl_value = trim(substr($data, 0, $p));
				$data = ltrim(substr($data, $p + 1));
				
				$this->debug->output("Sub-declaration '{$decl_name}' <".$this->buildSubDeclarationName($decl_name).">");

				$this->declarations[] = new LessDeclaration($this->buildSubDeclarationName($decl_name), $decl_value, $this, $this->debug);
			}
		}
		
		public function output() {
			if ($this->is_mixin)
				return "";
			
			// handle @if and @elseif (they are 99% the same)
			if (preg_match('/^(.+)\s*@(else)?if\((.+)\)$/i', $this->names[0], $m)) {
				$this->parent->setLastIf($m[3]);
				
				$if = array($m[3], true, false);
				if (!lessfunction_if($if, new LessProperty("", $this))) {
					$this->parent->setLastIfSuccess(false);
					return "";
				} else {
					$this->parent->setLastIfSuccess(true);
					$this->names[0] = rtrim($m[1]);
				}
			// handle @else
			} elseif (substr(strtolower($this->names[0]), -6) == ' @else') {
				if ($this->parent->getLastIfSuccess()) {
					return "";
				} else {
					$this->names[0] = rtrim(substr($this->names[0], 0, -5));
				}
			}

			$properties = $this->outputProperties();
			if (strlen($properties) > 0 || count($this->mixins) > 0) {
				$output = implode(", ", $this->names) . " {" . $properties;

				foreach ($this->mixins as $mixin)
					$output .= $mixin['mixin']->outputProperties($mixin['params']);
			
				$output .= " }\n";
			} else {
				$output = "";
			}
			
			//
			// Declaration examples that start with '@':
			//
			// CSS3 @keyframes ; @font-face ; ..
			// Webkit @-webkit-keyframes
			//
			if (count($this->declarations) && substr($this->names[0], 0, 1) == '@') {
				$output .= $this->names[0] . " {\n";
				foreach ($this->declarations as $dec)
					$output .= " ".$dec->output();
				$output .= "}\n";
			} else {
				foreach ($this->declarations as $dec)
					$output .= $dec->output();
			}
			
			return $output;
		}
		
		public function outputProperties($params = false) {
			$output = "";
			
			foreach ($this->properties as $prop) {	// $prop[0] = name ; $prop[1] = value
				if ($params !== false) {
					$vars_saved = $this->variables;
					$this->variables = $this->parameters;

					$n = 0;
					foreach ($this->parameters as $k => $v) {
						if (!isset($params[$n]) || strlen(trim($params[$n])) == 0) {
							if ($v === null) {
								throw new Exception("Invalid mixin call {$this->names[0]}. Missing parameter ".($n+1)." - {$k}");
							}
							$params[$n] = $v;
						}
						$this->variables[$k] = $params[$n];
						
						$n++;
					}
					
					$lprop = new LessProperty($prop[1], $this);
					$output .= " {$prop[0]}: " . $lprop->output() . ";";
					
					$this->variables = $vars_saved;
				} else {
					$lprop = new LessProperty($prop[1], $this);
					$output .= " {$prop[0]}: " . $lprop->output() . ";";
				}
			}
			
			return $output;
		}
		
		public function setMixin($is_mixin = true) {
			$this->is_mixin = $is_mixin;
		}
		
		public function getName() {
			return $this->names[0];
		}
		
		public function getLastIfSuccess() {
			return $this->last_if_success;
		}
		
		public function setLastIf($if) {
			$this->last_if = $if;
		}
		
		public function setLastIfSuccess($success) {
			printf("LAST IF %s SUCCESSFULL '%s'\n", $success ? "WAS" : "WAS NOT", $this->last_if);
			$this->last_if_success = $success;
		}
		
		private function buildSubDeclarationName($decl_name) {
			if (substr($this->names[0], 0, 1) == '@') {
				return $decl_name;
			}
			
			$out = array();
			$decl_name = preg_split('/\s*,\s*/', $decl_name, -1, PREG_SPLIT_NO_EMPTY);
			
			foreach ($this->names as $name) {
				foreach ($decl_name as $subname) {
					if ($subname{0} == '&') {
						$out[] = $name . substr($subname, 1);
					} else {
						$out[] = $name . (in_array($subname{0}, array(':', '>', '+')) ? '' : ' ') . $subname;
					}
				}
			}
			
			return implode(', ', $out);
		}
		
		private function findMixin($name) {
			$dec = $this;
			while ($dec !== null) {
				$mixin = $dec->getDeclaration('.'.$name);
				if ($mixin !== null) {
					return $mixin;
				}
				
				for ($i = 0; $i < $dec->totalImports(); $i++) {
					$import = $dec->getImport($i);
					if (is_object($import)) {
						$mixin = $import->getDeclaration('.'.$name);
						if ($mixin !== null) {
							return $mixin;
						}
					}
				}
				
				$dec = $dec->getParent();
			}
			return null;
		}
	}

	/**
	 * LessProperty
	 *
	 * This is used to convert a property value. It can match variables,
	 * units, percentage and colors (and numbers of course).
	 *
	 * It gives priority to * and / operators and also checks for nested
	 * operations (using parenthesis).
	 **/
	class LessProperty {
		public $part_expr = '(\#[a-f0-9]{3,6}|rgba?\(.+\)|[0-9\.]+[a-z]{2}|[0-9\.]+\%?|)';
		public $units_expr = '(%|e(m|x)|p(x|t|c)|in|ft|(m|c)m|k?Hz|deg|g?rad|gr|m?s)';

		private $value;
		private $scope;
		private $unit = false;
		private $percent = false;

		public function __construct($value, &$scope) {
			$this->value = $value;
			$this->scope = $scope;
		}
		
		public function output() {
			// replace variables
			$this->value = preg_replace_callback('/@([a-z0-9_\.\-]+)/i', array($this, 'translateVariable'), $this->value);
			
			for ($i = 0; $i < strlen($this->value); $i++) {
				if (substr($this->value, $i, 1) == "(") {
					if (!preg_match('/(\w[\w\d]+)$/', substr($this->value, 0, $i), $m)) {
						continue;
					}
					$fun = $m[1];
					$c = 1;
			
					for ($j = $i + 1; $j < strlen($this->value); $j++) {
						if (substr($this->value, $j, 1) == "(") {
							$c++;
						} elseif (substr($this->value, $j, 1) == ")") {
							$c--;
					
							if ($c == 0) break;
						}
					}
			
					if ($c == 0) {
						$len = $j - $i - 1;
						$lessfun = new LessFunction($fun, substr($this->value, $i + 1, $len), $this);
						$lessfun = $lessfun->parse();
						
						$this->value = substr($this->value, 0, $i - strlen($fun)) . $lessfun . substr($this->value, $j + 1);
						$i += strlen($lessfun) + 1;
					}
				}
			}

			// find nested operations, the ones between ( and )
			// 16 Aug 2010: I need to test this one...
			do {
				$new_value = preg_replace_callback('/\(\s*(.+?)\s*\)/', array($this, 'translateNestedOperation'), $this->value, 1);
				if ($new_value == $this->value) break;
				
				$this->value = $new_value;
			} while (true);
			
			// replace operations * and /
			do {
				$this->value = preg_replace_callback('/'.$this->part_expr.'\s+([\*\/])\s+'.$this->part_expr.'/', array($this, 'checkExpression'), $this->value, 1, $replaced);
			} while ($replaced > 0);
			
			// replace operations + and -
			do {
				$this->value = preg_replace_callback('/'.$this->part_expr.'\s+([\+\-])\s+'.$this->part_expr.'/', array($this, 'checkExpression'), $this->value, 1, $replaced);
			} while ($replaced > 0);

			return $this->value;
		}
		
		public function checkExpression($match) {
			$p1 = $this->checkPart($match[1]);
			$p2 = $this->checkPart($match[3]);
			
			$op = $match[2];
			if (is_array($p1) && is_array($p2)) {
				// operationg between arrays
				if (!isset($p1[3])) $p1[3] = null;
				if (!isset($p2[3])) $p2[3] = null;
				return $this->buildColor($this->doOp($op, $p1[0], $p2[0]), $this->doOp($op, $p1[1], $p2[1]), $this->doOp($op, $p1[2], $p2[2]), $this->doOp($op, $p1[3], $p2[3]));
			} elseif (is_array($p1)) {
				// operationg between array and value
				if (!isset($p1[3])) $p1[3] = null;
				return $this->buildColor($this->doOp($op, $p1[0], $p2), $this->doOp($op, $p1[1], $p2), $this->doOp($op, $p1[2], $p2), $this->doOp($op, $p1[3], $p2));
			} elseif (is_array($p2)) {
				// operationg between value and array
				if (!isset($p2[3])) $p2[3] = null;
				return $this->buildColor($this->doOp($op, $p1, $p2[0]), $this->doOp($op, $p1, $p2[1]), $this->doOp($op, $p1, $p2[2]), $this->doOp($op, $p1, $p2[3]));
			}
			$val = $this->doOp($match[2], $p1, $p2);
			if ($this->unit !== false)
				return $val . $this->unit;
			if ($this->percent !== false)
				return round($val * 100, 2).'%';
			return $val;
		}
		
		public function doOp($op, $p1, $p2) {
			if ($p1 === null || $p2 === null) return null;
			switch ($op) {
				case '*': return $p1 * $p2;
				case '/': return $p1 / $p2;
				case '+': return $p1 + $p2;
				case '-': return $p1 - $p2;
			}
		}
		
		public function buildColor($c1, $c2, $c3, $alpha = null) {
			if ($alpha !== null) {
				// when alpha channel is involved, rgba() needs to be used
				return sprintf("rgba(%d, %d, %d, %d)", $c1, $c2, $c3, $alpha);
			}
			$c = str_pad(dechex(max(min($c1, 255), 0)), 2, '0', STR_PAD_LEFT)
			   . str_pad(dechex(max(min($c2, 255), 0)), 2, '0', STR_PAD_LEFT)
			   . str_pad(dechex(max(min($c3, 255), 0)), 2, '0', STR_PAD_LEFT);

			return "#{$c}";
		}
		
		public function translateVariable($match) {
			$dec = $this->scope;
			while ($dec !== null) {
				if ($dec->variableExists($match[1])) {
					$prop = new LessProperty($dec->getVariable($match[1]), $this->scope);
					return $prop->output();
				}
				
				for ($i = 0; $i < $dec->totalImports(); $i++) {
					$import = $dec->getImport($i);
					if (is_object($import)) {
						if ($import->variableExists($match[1])) {
							$prop = new LessProperty($import->getVariable($match[1]), $this->scope);
							return $prop->output();
						}
					}
				}
				
				$dec = $dec->getParent();
			}
			return $match[0];
		}
		
		public function translateNestedOperation($match) {
			$before = $match[1];
			$op = new LessProperty($before, $this->scope);
			$after = $op->output();
			
			if ($before == $after)
				return $match[0];
			return $after;
		}
		
		public function checkPart($part) {
			if (substr($part, 0, 1) == '#') {
				// color
				if ($this->unit !== false && $this->unit != 'color') {
					throw new Exception("Mixing units inside property expressions ('{$this->unit}' and 'color')");
				}

				$this->unit = 'color';
				
				$part = substr($part, 1);
				switch (strlen($part)) {
					case 6:
						return array(hexdec(substr($part, 0, 2)), hexdec(substr($part, 2, 2)), hexdec(substr($part, -2)));
					case 3:
						return array(hexdec($part{0}.$part{0}), hexdec($part{1}.$part{1}), hexdec($part{2}.$part{2}));
				}
				throw new Exception("Invalid color format inside property expression");
			} elseif (preg_match('/^(\-?[0-9]+|\-?[0-9]*\.[0-9]+)'.$this->units_expr.'$/', $part, $m)) {
				if ($m[2] == '%') {
					$this->percent = true;
					return $m[1] / 100;
				}
				
				if ($this->unit !== false && $this->unit != $m[2]) {
					throw new Exception("Mixing units inside property expressions ('{$this->unit}' and '{$m[2]}')");
				}

				$this->unit = $m[2];
				return $m[1];
			} elseif (preg_match('/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/', $part, $m)) {
				// color rgb(r, g, b)
				if ($this->unit !== false && $this->unit != 'color') {
					throw new Exception("Mixing units inside property expressions ('{$this->unit}' and 'color')");
				}
				return array($m[1], $m[2], $m[3]);
			} elseif (preg_match('/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/', $part, $m)) {
				// color rgba(r, g, b, a)
				if ($this->unit !== false && $this->unit != 'color') {
					throw new Exception("Mixing units inside property expressions ('{$this->unit}' and 'color')");
				}
				return array($m[1], $m[2], $m[3], $m[4]);
			} else {
				return $part;
			}
		}
	}
	
	class LessFunction {
		public function __construct($name, $params, &$scope) {
			$this->name = $name;
			$this->params = $params;
			$this->scope = $scope;
			
			for ($i = 0; $i < strlen($this->params); $i++) {
				if (substr($this->params, $i, 1) == "(") {
					if (!preg_match('/(\w[\w\d]+)$/', substr($this->params, 0, $i), $m)) {
						continue;
					}
					$fun = $m[1];
					$c = 1;
			
					for ($j = $i + 1; $j < strlen($this->params); $j++) {
						if (substr($this->params, $j, 1) == "(") {
							$c++;
						} elseif (substr($this->params, $j, 1) == ")") {
							$c--;
					
							if ($c == 0) break;
						}
					}
			
					if ($c == 0) {
						$len = $j - $i - 1;
						$lessfun = new LessFunction($fun, substr($this->params, $i + 1, $len), $this->scope);
						$lessfun = $lessfun->parse();
						
						$this->params = substr($this->params, 0, $i - strlen($fun)) . $lessfun . substr($this->params, $j + 1);
						$i += strlen($lessfun) + 1;
					}
				}
			}
		}
		
		public function parse() {
			if (in_array($this->name, array('rgb', 'rgba'))) {
				return sprintf("%s(%s)", $this->name, $this->params);
			}
			
			$fun_call = 'lessfunction_'.$this->name;
			
			if (!function_exists($fun_call)) {
				return sprintf("%s(%s)", $this->name, $this->params);
			}

			$op = new LessProperty($this->params, $this->scope);
			$data = trim($op->output());
			
			$parenthesis = 0;
			$params = array();
			$s = 0;
			for ($i = 0; $i < strlen($data); $i++) {
				switch (substr($data, $i, 1)) {
					case ",":
						if ($parenthesis == 0) {
							$params[] = trim(substr($data, $s, $i - $s));
							$s = $i + 1;
						}
						break;
					case "(":
						$parenthesis++;
						break;
					case ")":
						$parenthesis--;
						break;
				}
			}
			$params[] = trim(substr($data, $s, $i - $s));

			return $fun_call($params, $this->scope);
		}
	}
?>
Return current item: Less.php