0,0 → 1,345 |
<?php |
|
namespace de\pointedears\css\least; |
|
require_once __DIR__ . '/../../string/parser/Lexer.php'; |
require_once __DIR__ . '/../../string/parser/Parser.php'; |
|
/** |
* Parses a LEAST stylesheet into a CSS stylesheet |
* |
* @author Thomas 'PointedEars' Lahn |
* @property-read string $compiled |
*/ |
class Parser extends \de\pointedears\string\parser\Parser |
{ |
/** |
* CSS stylesheet code compiled from the LEAST stylesheet |
* |
* @var string |
*/ |
protected $_compiled; |
|
/** |
* Array of arrays of template variables, one inner array for |
* each scope level. |
* @var array[array] |
*/ |
protected $_vars = array(array()); |
|
/** |
* The nesting level of the current variable scope. |
* |
* Used to hold variable definitions per nesting level. |
* |
* @var int |
*/ |
protected $_scope_level = 0; |
|
/** |
* Cache for lookup results. |
* |
* The lookup cache improves parser performance with |
* nested scopes. By contrast to <code>$_vars</code>, it is |
* a _shallow_ storage for template variable definitions and |
* represents the current _certain_ variable knowledge of the |
* parser, i.e. variables that have been defined in outer scopes |
* and the same scope, and whose values have been looked up in |
* <code>$_vars</code> before. |
* |
* The lookup cache is replaced by the variables of the outer |
* scope when a scope is exited because definitions in the |
* outer scope may have been shadowed by the inner scope. |
* Note that definitions from the second-next, third-next |
* aso. scopes need to be looked up again then. |
* |
* @var array |
*/ |
protected $_lookup_cache = array(); |
|
/** |
* Mixins |
* @var array |
*/ |
protected $_mixins = array(); |
|
/** |
* <code>true</code> if the parser is parsing a mix-in |
* @var boolean |
*/ |
protected $_in_mixin = false; |
|
/** |
* The currently parsed mix-in |
* @var Mixin |
*/ |
protected $_current_mixin; |
|
/** |
* The nesting level in the currently parsed mix-in, 0 again when |
* it just ended |
* @var int |
*/ |
protected $_mixin_level = 0; |
|
public function __construct ($code) |
{ |
$lexer = new \de\pointedears\string\parser\Lexer($code); |
|
$nl = 'n'; |
$escape = '\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f]'; |
$lexer->addTokens(array( |
'(?P<PHP><\?php.*?\?>)', |
"/\*[^*]*\*+([^/*][^*]*\*+)*/", |
"\"([^\n\r\f\\\"]|\\{$nl}|{$escape})*\"", |
"'([^\n\r\f\\']|\\{$nl}|{$escape})*'", |
// '\.(?P<mixin_def>[\w-]+)\s*\((?P<params>.*?)\)\s*(?!;)', |
// '\.(?P<mixin_call>[\w-]+)\s*\((?P<args>.*)?\)\s*;\\s*', |
'[{}]', |
// '\$(?P<name>\w+)\s*=\s*(?P<value>\'.+?\'|".+"|.+?)\s*;\s*', |
// '\$(?P<ref>\w+)' |
)); |
|
$lexer->ignoreCase = true; |
$lexer->dotAll = true; |
|
parent::__construct($lexer); |
$this->_compiled = $code; |
} |
|
protected function defineVar($name, $value) |
{ |
$scope_level = $this->_scope_level; |
|
if (defined('DEBUG') && DEBUG > 0) |
{ |
echo "<?php // Least.php: Found definition \${$name} = `{$value}'" |
. " at scope level {$scope_level} ?>\n"; |
} |
|
$this->_vars[$scope_level][$name] = $value; |
$this->_lookup_cache[$name] = $value; |
} |
|
/** |
* Resolves a template variable reference against the template scope chain |
* @param string $ref |
* @return mixed |
*/ |
protected function resolveVar ($name) |
{ |
$scope_level = $this->_scope_level + 1; |
$orig_level = $scope_level - 1; |
|
if (defined('DEBUG') && DEBUG > 0) |
{ |
echo "<?php // Least.php: Found reference \${$name} at scope level " |
. ($scope_level - 1) . " ?>\n"; |
} |
|
/* Try the lookup cache for this scope level first */ |
if (array_key_exists($name, $this->_lookup_cache)) |
{ |
$value = $this->_lookup_cache[$name]; |
|
if (defined('DEBUG') && DEBUG > 1) |
{ |
echo "<?php // Least.php: Resolved reference \${$name}" |
. " at scope level {$orig_level}" |
. " from lookup cache, value `{$value}' ?>\n"; |
} |
|
return $value; |
} |
|
while ($scope_level--) |
{ |
if (array_key_exists($name, $this->_vars[$scope_level])) |
{ |
$value = $this->_vars[$scope_level][$name]; |
$this->_lookup_cache[$name] = $value; |
|
if (defined('DEBUG') && DEBUG > 1) |
{ |
echo "<?php // Least.php: Resolved reference \${$name}" |
. " from scope level {$orig_level}" |
. " at scope level {$scope_level}, value `{$value}' ?>\n"; |
} |
|
return $value; |
} |
} |
|
if (defined('DEBUG') && DEBUG > 0) |
{ |
echo "<?php // Least.php: WARNING: Unresolved reference \${$name}" |
. " at scope level {$orig_level}. Variable not in scope? ?>\n"; |
} |
} |
|
protected function defineMixin($name, $params) |
{ |
$scope_level = $this->_scope_level; |
|
if (defined('DEBUG') && DEBUG > 0) |
{ |
echo "<?php // Least.php: Found mixin definition {$name}($params)" |
. " at scope level {$scope_level}. ?>\n"; |
} |
|
$mixin = new Mixin($params); |
$this->_mixins[$name] = $mixin; |
|
return $mixin; |
} |
|
protected function callMixin($name, $args) |
{ |
return $this->$_mixins[$name]->apply($args); |
} |
|
public function parseToken ($matches) |
{ |
ini_set('html_errors', 0); |
// var_dump($matches); |
// /* Get match offset and match length */ |
// $match = $matches[0]; |
// $match_start = $match[1]; |
// $match_length = mb_strlen($match[0]); |
|
// /* Transform $matches to the format it is usually set as (without PREG_OFFSET_CAPTURE set) */ |
// $matches = array_map(function ($m) { |
// return $m[0]; |
// }, $matches); |
// $match = $matches[0]; |
|
// if (defined('DEBUG') && DEBUG > 1) |
// { |
// echo print_r(array( |
// 'match_start' => $match_start, |
// 'match_length' => $match_length, |
// 'matches' => $matches, |
// 'in_mixin' => $in_mixin, |
// 'scope_level' => $this->_scope_level, |
// ), true) . "\n"; |
// } |
|
// if (isset($matches['mixin_def']) && $matches['mixin_def']) |
// { |
// $this->_in_mixin = true; |
// $this->_current_mixin = $this->defineMixin($matches['mixin_def'], $matches['params']); |
// // $code = self::replace(mb_strlen($match), '', $code, $match_start); |
// // $match_length = 0; |
// } |
// else if (isset($matches['mixin_call']) && $matches['mixin_call']) |
// { |
// //return |
// $this->callMixin($matches['mixin_call'], $matches['args']); |
// //$code = self::replace(mb_strlen($match), '', $code, $match_start); |
// //$match_length = 0; |
// $mixin_start = $match_start; |
// } |
// else if ($match === '{') |
// { |
// ++$this->_scope_level; |
// $this->_vars[$this->_scope_level] = array(); |
|
// if ($this->_in_mixin) |
// { |
// if ($mixin_body_start === 0) |
// { |
// $mixin_body_start = $match_start; |
// } |
|
// ++$this->_mixin_level; |
// } |
// } |
// else if ($match === '}') |
// { |
// --$this->_scope_level; |
// $this->_lookup_cache = $this->_vars[$this->_scope_level]; |
|
// if ($this->_in_mixin) |
// { |
// --$this->_mixin_level; |
// if ($this->_mixin_level === 0) |
// { |
// $this->_in_mixin = false; |
// $this->_current_mixin->setBody(substr($code, $mixin_start + 1, $match_start - $mixin_start - 1)); |
// echo '"'.print_r(substr($code, $mixin_start, $match_start - $mixin_start + 1), true) . "\"\n"; |
// // $code = self::replace($match_start - $mixin_start + 1, '', $code, $mixin_start); |
// // $match_length = 0; |
// } |
// } |
// } |
// else if (isset($matches['name']) && $matches['name']) |
// { |
// $this->defineVar($matches['name'], $matches['value']); |
// $code = self::replace(mb_strlen($match), '', $code, $match_start); |
// $match_length = 0; |
// } |
// else if (isset($matches['ref']) && $matches['ref']) |
// { |
// $name = $matches['ref']; |
|
// if (!$in_mixin || !in_array($name, $mixin->params, true)) |
// { |
// $value = $this->resolveVar($matches['ref']); |
// $this->_lexer->text = self::replace(mb_strlen($match), $value, $code, $match_start); |
// $this->_lexer->offset -= $match_length + mb_strlen($value); |
// } |
// } |
} |
|
public function getCompiled () |
{ |
return $this->_compiled; |
} |
} |
|
class Mixin extends \de\pointedears\Base |
{ |
/** |
* Parameters of the mixin |
* @var array |
*/ |
protected $_params; |
|
/** |
* Body of the mixin |
* @var string |
*/ |
protected $_body = ''; |
|
/** |
* @param string $params |
*/ |
public function __construct ($params, $body = '') |
{ |
$this->_params = is_array($params) |
? $params |
: array_map( |
function ($e) { |
return preg_replace('/^\$/', '', $e); |
}, |
preg_split('/\s*,\s*/', $params)); |
|
if ($body !== '') |
{ |
$this->setBody($body); |
} |
} |
|
public function setBody ($body) |
{ |
$this->body = (string) $body; |
} |
|
/** |
* @param string $arguments |
*/ |
public function apply ($arguments) |
{ |
return ''; |
} |
} |