aboutsummaryrefslogtreecommitdiff
path: root/lib/less.php/Tree/Mixin
diff options
context:
space:
mode:
Diffstat (limited to 'lib/less.php/Tree/Mixin')
-rw-r--r--lib/less.php/Tree/Mixin/Call.php202
-rw-r--r--lib/less.php/Tree/Mixin/Definition.php241
2 files changed, 443 insertions, 0 deletions
diff --git a/lib/less.php/Tree/Mixin/Call.php b/lib/less.php/Tree/Mixin/Call.php
new file mode 100644
index 0000000..04eb426
--- /dev/null
+++ b/lib/less.php/Tree/Mixin/Call.php
@@ -0,0 +1,202 @@
+<?php
+
+
+class Less_Tree_Mixin_Call extends Less_Tree{
+
+ public $selector;
+ public $arguments;
+ public $index;
+ public $currentFileInfo;
+
+ public $important;
+ public $type = 'MixinCall';
+
+ /**
+ * less.js: tree.mixin.Call
+ *
+ */
+ public function __construct($elements, $args, $index, $currentFileInfo, $important = false){
+ $this->selector = new Less_Tree_Selector($elements);
+ $this->arguments = $args;
+ $this->index = $index;
+ $this->currentFileInfo = $currentFileInfo;
+ $this->important = $important;
+ }
+
+ //function accept($visitor){
+ // $this->selector = $visitor->visit($this->selector);
+ // $this->arguments = $visitor->visit($this->arguments);
+ //}
+
+
+ public function compile($env){
+
+ $rules = array();
+ $match = false;
+ $isOneFound = false;
+ $candidates = array();
+ $defaultUsed = false;
+ $conditionResult = array();
+
+ $args = array();
+ foreach($this->arguments as $a){
+ $args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) );
+ }
+
+ foreach($env->frames as $frame){
+
+ $mixins = $frame->find($this->selector);
+
+ if( !$mixins ){
+ continue;
+ }
+
+ $isOneFound = true;
+ $defNone = 0;
+ $defTrue = 1;
+ $defFalse = 2;
+
+ // To make `default()` function independent of definition order we have two "subpasses" here.
+ // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`),
+ // and build candidate list with corresponding flags. Then, when we know all possible matches,
+ // we make a final decision.
+
+ $mixins_len = count($mixins);
+ for( $m = 0; $m < $mixins_len; $m++ ){
+ $mixin = $mixins[$m];
+
+ if( $this->IsRecursive( $env, $mixin ) ){
+ continue;
+ }
+
+ if( $mixin->matchArgs($args, $env) ){
+
+ $candidate = array('mixin' => $mixin, 'group' => $defNone);
+
+ if( $mixin instanceof Less_Tree_Ruleset ){
+
+ for( $f = 0; $f < 2; $f++ ){
+ Less_Tree_DefaultFunc::value($f);
+ $conditionResult[$f] = $mixin->matchCondition( $args, $env);
+ }
+ if( $conditionResult[0] || $conditionResult[1] ){
+ if( $conditionResult[0] != $conditionResult[1] ){
+ $candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse;
+ }
+
+ $candidates[] = $candidate;
+ }
+ }else{
+ $candidates[] = $candidate;
+ }
+
+ $match = true;
+ }
+ }
+
+ Less_Tree_DefaultFunc::reset();
+
+
+ $count = array(0, 0, 0);
+ for( $m = 0; $m < count($candidates); $m++ ){
+ $count[ $candidates[$m]['group'] ]++;
+ }
+
+ if( $count[$defNone] > 0 ){
+ $defaultResult = $defFalse;
+ } else {
+ $defaultResult = $defTrue;
+ if( ($count[$defTrue] + $count[$defFalse]) > 1 ){
+ throw new Exception( 'Ambiguous use of `default()` found when matching for `' . $this->format($args) . '`' );
+ }
+ }
+
+
+ $candidates_length = count($candidates);
+ $length_1 = ($candidates_length == 1);
+
+ for( $m = 0; $m < $candidates_length; $m++){
+ $candidate = $candidates[$m]['group'];
+ if( ($candidate === $defNone) || ($candidate === $defaultResult) ){
+ try{
+ $mixin = $candidates[$m]['mixin'];
+ if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
+ $mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false);
+ $mixin->originalRuleset = $mixins[$m]->originalRuleset;
+ }
+ $rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules);
+ } catch (Exception $e) {
+ //throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']);
+ throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo);
+ }
+ }
+ }
+
+ if( $match ){
+ if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){
+ Less_Tree::ReferencedArray($rules);
+ }
+
+ return $rules;
+ }
+ }
+
+ if( $isOneFound ){
+ throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo);
+
+ }else{
+ throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index);
+ }
+
+ }
+
+ /**
+ * Format the args for use in exception messages
+ *
+ */
+ private function Format($args){
+ $message = array();
+ if( $args ){
+ foreach($args as $a){
+ $argValue = '';
+ if( $a['name'] ){
+ $argValue .= $a['name'] . ':';
+ }
+ if( is_object($a['value']) ){
+ $argValue .= $a['value']->toCSS();
+ }else{
+ $argValue .= '???';
+ }
+ $message[] = $argValue;
+ }
+ }
+ return implode(', ',$message);
+ }
+
+
+ /**
+ * Are we in a recursive mixin call?
+ *
+ * @return bool
+ */
+ private function IsRecursive( $env, $mixin ){
+
+ foreach($env->frames as $recur_frame){
+ if( !($mixin instanceof Less_Tree_Mixin_Definition) ){
+
+ if( $mixin === $recur_frame ){
+ return true;
+ }
+
+ if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
+
+
diff --git a/lib/less.php/Tree/Mixin/Definition.php b/lib/less.php/Tree/Mixin/Definition.php
new file mode 100644
index 0000000..b16d688
--- /dev/null
+++ b/lib/less.php/Tree/Mixin/Definition.php
@@ -0,0 +1,241 @@
+<?php
+
+class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{
+ public $name;
+ public $selectors;
+ public $params;
+ public $arity = 0;
+ public $rules;
+ public $lookups = array();
+ public $required = 0;
+ public $frames = array();
+ public $condition;
+ public $variadic;
+ public $type = 'MixinDefinition';
+
+
+ // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition
+ public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = array() ){
+ $this->name = $name;
+ $this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name))));
+
+ $this->params = $params;
+ $this->condition = $condition;
+ $this->variadic = $variadic;
+ $this->rules = $rules;
+
+ if( $params ){
+ $this->arity = count($params);
+ foreach( $params as $p ){
+ if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) {
+ $this->required++;
+ }
+ }
+ }
+
+ $this->frames = $frames;
+ $this->SetRulesetIndex();
+ }
+
+
+
+ //function accept( $visitor ){
+ // $this->params = $visitor->visit($this->params);
+ // $this->rules = $visitor->visit($this->rules);
+ // $this->condition = $visitor->visit($this->condition);
+ //}
+
+
+ public function toCSS(){
+ return '';
+ }
+
+ // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams
+ public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){
+ $frame = new Less_Tree_Ruleset(null, array());
+ $params = $this->params;
+ $mixinEnv = null;
+ $argsLength = 0;
+
+ if( $args ){
+ $argsLength = count($args);
+ for($i = 0; $i < $argsLength; $i++ ){
+ $arg = $args[$i];
+
+ if( $arg && $arg['name'] ){
+ $isNamedFound = false;
+
+ foreach($params as $j => $param){
+ if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) {
+ $evaldArguments[$j] = $arg['value']->compile($env);
+ array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) );
+ $isNamedFound = true;
+ break;
+ }
+ }
+ if ($isNamedFound) {
+ array_splice($args, $i, 1);
+ $i--;
+ $argsLength--;
+ continue;
+ } else {
+ throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found');
+ }
+ }
+ }
+ }
+
+ $argIndex = 0;
+ foreach($params as $i => $param){
+
+ if ( isset($evaldArguments[$i]) ){ continue; }
+
+ $arg = null;
+ if( isset($args[$argIndex]) ){
+ $arg = $args[$argIndex];
+ }
+
+ if (isset($param['name']) && $param['name']) {
+
+ if( isset($param['variadic']) ){
+ $varargs = array();
+ for ($j = $argIndex; $j < $argsLength; $j++) {
+ $varargs[] = $args[$j]['value']->compile($env);
+ }
+ $expression = new Less_Tree_Expression($varargs);
+ array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env)));
+ }else{
+ $val = ($arg && $arg['value']) ? $arg['value'] : false;
+
+ if ($val) {
+ $val = $val->compile($env);
+ } else if ( isset($param['value']) ) {
+
+ if( !$mixinEnv ){
+ $mixinEnv = new Less_Environment();
+ $mixinEnv->frames = array_merge( array($frame), $mixinFrames);
+ }
+
+ $val = $param['value']->compile($mixinEnv);
+ $frame->resetCache();
+ } else {
+ throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")");
+ }
+
+ array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val));
+ $evaldArguments[$i] = $val;
+ }
+ }
+
+ if ( isset($param['variadic']) && $args) {
+ for ($j = $argIndex; $j < $argsLength; $j++) {
+ $evaldArguments[$j] = $args[$j]['value']->compile($env);
+ }
+ }
+ $argIndex++;
+ }
+
+ ksort($evaldArguments);
+ $evaldArguments = array_values($evaldArguments);
+
+ return $frame;
+ }
+
+ public function compile($env) {
+ if( $this->frames ){
+ return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames );
+ }
+ return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames );
+ }
+
+ public function evalCall($env, $args = NULL, $important = NULL) {
+
+ Less_Environment::$mixin_stack++;
+
+ $_arguments = array();
+
+ if( $this->frames ){
+ $mixinFrames = array_merge($this->frames, $env->frames);
+ }else{
+ $mixinFrames = $env->frames;
+ }
+
+ $frame = $this->compileParams($env, $mixinFrames, $args, $_arguments);
+
+ $ex = new Less_Tree_Expression($_arguments);
+ array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env)));
+
+
+ $ruleset = new Less_Tree_Ruleset(null, $this->rules);
+ $ruleset->originalRuleset = $this->ruleset_id;
+
+
+ $ruleSetEnv = new Less_Environment();
+ $ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames );
+ $ruleset = $ruleset->compile( $ruleSetEnv );
+
+ if( $important ){
+ $ruleset = $ruleset->makeImportant();
+ }
+
+ Less_Environment::$mixin_stack--;
+
+ return $ruleset;
+ }
+
+
+ public function matchCondition($args, $env) {
+
+ if( !$this->condition ){
+ return true;
+ }
+
+ // set array to prevent error on array_merge
+ if(!is_array($this->frames)) {
+ $this->frames = array();
+ }
+
+ $frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args );
+
+ $compile_env = new Less_Environment();
+ $compile_env->frames = array_merge(
+ array($frame) // the parameter variables
+ , $this->frames // the parent namespace/mixin frames
+ , $env->frames // the current environment frames
+ );
+
+ $compile_env->functions = $env->functions;
+
+ return (bool)$this->condition->compile($compile_env);
+ }
+
+ public function matchArgs($args, $env = NULL){
+ $argsLength = count($args);
+
+ if( !$this->variadic ){
+ if( $argsLength < $this->required ){
+ return false;
+ }
+ if( $argsLength > count($this->params) ){
+ return false;
+ }
+ }else{
+ if( $argsLength < ($this->required - 1)){
+ return false;
+ }
+ }
+
+ $len = min($argsLength, $this->arity);
+
+ for( $i = 0; $i < $len; $i++ ){
+ if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){
+ if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+}