123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691 |
- <?php
- require_once dirname( __FILE__ ).'/Cache.php';
- /**
- * Class for parsing and compiling less files into css
- *
- * @package Less
- * @subpackage parser
- *
- */
- class Less_Parser {
- /**
- * Default parser options
- */
- public static $default_options = array(
- 'compress' => false, // option - whether to compress
- 'strictUnits' => false, // whether units need to evaluate correctly
- 'strictMath' => false, // whether math has to be within parenthesis
- 'relativeUrls' => true, // option - whether to adjust URL's to be relative
- 'urlArgs' => '', // whether to add args into url tokens
- 'numPrecision' => 8,
- 'import_dirs' => array(),
- 'import_callback' => null,
- 'cache_dir' => null,
- 'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback';
- 'cache_callback_get' => null,
- 'cache_callback_set' => null,
- 'sourceMap' => false, // whether to output a source map
- 'sourceMapBasepath' => null,
- 'sourceMapWriteTo' => null,
- 'sourceMapURL' => null,
- 'indentation' => ' ',
- 'plugins' => array(),
- );
- public static $options = array();
- private $input; // Less input string
- private $input_len; // input string length
- private $pos; // current index in `input`
- private $saveStack = array(); // holds state for backtracking
- private $furthest;
- private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding
- /**
- * @var Less_Environment
- */
- private $env;
- protected $rules = array();
- private static $imports = array();
- public static $has_extends = false;
- public static $next_id = 0;
- /**
- * Filename to contents of all parsed the files
- *
- * @var array
- */
- public static $contentsMap = array();
- /**
- * @param Less_Environment|array|null $env
- */
- public function __construct( $env = null ) {
- // Top parser on an import tree must be sure there is one "env"
- // which will then be passed around by reference.
- if ( $env instanceof Less_Environment ) {
- $this->env = $env;
- } else {
- $this->SetOptions( Less_Parser::$default_options );
- $this->Reset( $env );
- }
- // mbstring.func_overload > 1 bugfix
- // The encoding value must be set for each source file,
- // therefore, to conserve resources and improve the speed of this design is taken here
- if ( ini_get( 'mbstring.func_overload' ) ) {
- $this->mb_internal_encoding = ini_get( 'mbstring.internal_encoding' );
- @ini_set( 'mbstring.internal_encoding', 'ascii' );
- }
- }
- /**
- * Reset the parser state completely
- *
- */
- public function Reset( $options = null ) {
- $this->rules = array();
- self::$imports = array();
- self::$has_extends = false;
- self::$imports = array();
- self::$contentsMap = array();
- $this->env = new Less_Environment( $options );
- // set new options
- if ( is_array( $options ) ) {
- $this->SetOptions( Less_Parser::$default_options );
- $this->SetOptions( $options );
- }
- $this->env->Init();
- }
- /**
- * Set one or more compiler options
- * options: import_dirs, cache_dir, cache_method
- *
- */
- public function SetOptions( $options ) {
- foreach ( $options as $option => $value ) {
- $this->SetOption( $option, $value );
- }
- }
- /**
- * Set one compiler option
- *
- */
- public function SetOption( $option, $value ) {
- switch ( $option ) {
- case 'import_dirs':
- $this->SetImportDirs( $value );
- return;
- case 'cache_dir':
- if ( is_string( $value ) ) {
- Less_Cache::SetCacheDir( $value );
- Less_Cache::CheckCacheDir();
- }
- return;
- }
- Less_Parser::$options[$option] = $value;
- }
- /**
- * Registers a new custom function
- *
- * @param string $name function name
- * @param callable $callback callback
- */
- public function registerFunction( $name, $callback ) {
- $this->env->functions[$name] = $callback;
- }
- /**
- * Removed an already registered function
- *
- * @param string $name function name
- */
- public function unregisterFunction( $name ) {
- if ( isset( $this->env->functions[$name] ) )
- unset( $this->env->functions[$name] );
- }
- /**
- * Get the current css buffer
- *
- * @return string
- */
- public function getCss() {
- $precision = ini_get( 'precision' );
- @ini_set( 'precision', 16 );
- $locale = setlocale( LC_NUMERIC, 0 );
- setlocale( LC_NUMERIC, "C" );
- try {
- $root = new Less_Tree_Ruleset( array(), $this->rules );
- $root->root = true;
- $root->firstRoot = true;
- $this->PreVisitors( $root );
- self::$has_extends = false;
- $evaldRoot = $root->compile( $this->env );
- $this->PostVisitors( $evaldRoot );
- if ( Less_Parser::$options['sourceMap'] ) {
- $generator = new Less_SourceMap_Generator( $evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options );
- // will also save file
- // FIXME: should happen somewhere else?
- $css = $generator->generateCSS();
- } else {
- $css = $evaldRoot->toCSS();
- }
- if ( Less_Parser::$options['compress'] ) {
- $css = preg_replace( '/(^(\s)+)|((\s)+$)/', '', $css );
- }
- } catch ( Exception $exc ) {
- // Intentional fall-through so we can reset environment
- }
- // reset php settings
- @ini_set( 'precision', $precision );
- setlocale( LC_NUMERIC, $locale );
- // If you previously defined $this->mb_internal_encoding
- // is required to return the encoding as it was before
- if ( $this->mb_internal_encoding != '' ) {
- @ini_set( "mbstring.internal_encoding", $this->mb_internal_encoding );
- $this->mb_internal_encoding = '';
- }
- // Rethrow exception after we handled resetting the environment
- if ( !empty( $exc ) ) {
- throw $exc;
- }
- return $css;
- }
- public function findValueOf( $varName ) {
- foreach ( $this->rules as $rule ) {
- if ( isset( $rule->variable ) && ( $rule->variable == true ) && ( str_replace( "@", "", $rule->name ) == $varName ) ) {
- return $this->getVariableValue( $rule );
- }
- }
- return null;
- }
- /**
- *
- * this function gets the private rules variable and returns an array of the found variables
- * it uses a helper method getVariableValue() that contains the logic ot fetch the value from the rule object
- *
- * @return array
- */
- public function getVariables() {
- $variables = array();
- $not_variable_type = array(
- 'Comment', // this include less comments ( // ) and css comments (/* */)
- 'Import', // do not search variables in included files @import
- 'Ruleset', // selectors (.someclass, #someid, …)
- 'Operation', //
- );
- // @TODO run compilation if not runned yet
- foreach ( $this->rules as $key => $rule ) {
- if ( in_array( $rule->type, $not_variable_type ) ) {
- continue;
- }
- // Note: it seems rule->type is always Rule when variable = true
- if ( $rule->type == 'Rule' && $rule->variable ) {
- $variables[$rule->name] = $this->getVariableValue( $rule );
- } else {
- if ( $rule->type == 'Comment' ) {
- $variables[] = $this->getVariableValue( $rule );
- }
- }
- }
- return $variables;
- }
- public function findVarByName( $var_name ) {
- foreach ( $this->rules as $rule ) {
- if ( isset( $rule->variable ) && ( $rule->variable == true ) ) {
- if ( $rule->name == $var_name ) {
- return $this->getVariableValue( $rule );
- }
- }
- }
- return null;
- }
- /**
- *
- * This method gets the value of the less variable from the rules object.
- * Since the objects vary here we add the logic for extracting the css/less value.
- *
- * @param $var
- *
- * @return bool|string
- */
- private function getVariableValue( $var ) {
- if ( !is_a( $var, 'Less_Tree' ) ) {
- throw new Exception( 'var is not a Less_Tree object' );
- }
- switch ( $var->type ) {
- case 'Color':
- return $this->rgb2html( $var->rgb );
- case 'Unit':
- return $var->value. $var->unit->numerator[0];
- case 'Variable':
- return $this->findVarByName( $var->name );
- case 'Keyword':
- return $var->value;
- case 'Rule':
- return $this->getVariableValue( $var->value );
- case 'Value':
- $value = '';
- foreach ( $var->value as $sub_value ) {
- $value .= $this->getVariableValue( $sub_value ).' ';
- }
- return $value;
- case 'Quoted':
- return $var->quote.$var->value.$var->quote;
- case 'Dimension':
- $value = $var->value;
- if ( $var->unit && $var->unit->numerator ) {
- $value .= $var->unit->numerator[0];
- }
- return $value;
- case 'Expression':
- $value = "";
- foreach ( $var->value as $item ) {
- $value .= $this->getVariableValue( $item )." ";
- }
- return $value;
- case 'Operation':
- throw new Exception( 'getVariables() require Less to be compiled. please use $parser->getCss() before calling getVariables()' );
- case 'Comment':
- case 'Import':
- case 'Ruleset':
- default:
- throw new Exception( "type missing in switch/case getVariableValue for ".$var->type );
- }
- return false;
- }
- private function rgb2html( $r, $g = -1, $b = -1 ) {
- if ( is_array( $r ) && sizeof( $r ) == 3 )
- list( $r, $g, $b ) = $r;
- $r = intval( $r ); $g = intval( $g );
- $b = intval( $b );
- $r = dechex( $r < 0 ? 0 : ( $r > 255 ? 255 : $r ) );
- $g = dechex( $g < 0 ? 0 : ( $g > 255 ? 255 : $g ) );
- $b = dechex( $b < 0 ? 0 : ( $b > 255 ? 255 : $b ) );
- $color = ( strlen( $r ) < 2 ? '0' : '' ).$r;
- $color .= ( strlen( $g ) < 2 ? '0' : '' ).$g;
- $color .= ( strlen( $b ) < 2 ? '0' : '' ).$b;
- return '#'.$color;
- }
- /**
- * Run pre-compile visitors
- *
- */
- private function PreVisitors( $root ) {
- if ( Less_Parser::$options['plugins'] ) {
- foreach ( Less_Parser::$options['plugins'] as $plugin ) {
- if ( !empty( $plugin->isPreEvalVisitor ) ) {
- $plugin->run( $root );
- }
- }
- }
- }
- /**
- * Run post-compile visitors
- *
- */
- private function PostVisitors( $evaldRoot ) {
- $visitors = array();
- $visitors[] = new Less_Visitor_joinSelector();
- if ( self::$has_extends ) {
- $visitors[] = new Less_Visitor_processExtends();
- }
- $visitors[] = new Less_Visitor_toCSS();
- if ( Less_Parser::$options['plugins'] ) {
- foreach ( Less_Parser::$options['plugins'] as $plugin ) {
- if ( property_exists( $plugin, 'isPreEvalVisitor' ) && $plugin->isPreEvalVisitor ) {
- continue;
- }
- if ( property_exists( $plugin, 'isPreVisitor' ) && $plugin->isPreVisitor ) {
- array_unshift( $visitors, $plugin );
- } else {
- $visitors[] = $plugin;
- }
- }
- }
- for ( $i = 0; $i < count( $visitors ); $i++ ) {
- $visitors[$i]->run( $evaldRoot );
- }
- }
- /**
- * Parse a Less string into css
- *
- * @param string $str The string to convert
- * @param string $uri_root The url of the file
- * @return Less_Tree_Ruleset|Less_Parser
- */
- public function parse( $str, $file_uri = null ) {
- if ( !$file_uri ) {
- $uri_root = '';
- $filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less';
- } else {
- $file_uri = self::WinPath( $file_uri );
- $filename = $file_uri;
- $uri_root = dirname( $file_uri );
- }
- $previousFileInfo = $this->env->currentFileInfo;
- $uri_root = self::WinPath( $uri_root );
- $this->SetFileInfo( $filename, $uri_root );
- $this->input = $str;
- $this->_parse();
- if ( $previousFileInfo ) {
- $this->env->currentFileInfo = $previousFileInfo;
- }
- return $this;
- }
- /**
- * Parse a Less string from a given file
- *
- * @throws Less_Exception_Parser
- * @param string $filename The file to parse
- * @param string $uri_root The url of the file
- * @param bool $returnRoot Indicates whether the return value should be a css string a root node
- * @return Less_Tree_Ruleset|Less_Parser
- */
- public function parseFile( $filename, $uri_root = '', $returnRoot = false ) {
- if ( !file_exists( $filename ) ) {
- $this->Error( sprintf( 'File `%s` not found.', $filename ) );
- }
- // fix uri_root?
- // Instead of The mixture of file path for the first argument and directory path for the second argument has bee
- if ( !$returnRoot && !empty( $uri_root ) && basename( $uri_root ) == basename( $filename ) ) {
- $uri_root = dirname( $uri_root );
- }
- $previousFileInfo = $this->env->currentFileInfo;
- if ( $filename ) {
- $filename = self::AbsPath( $filename, true );
- }
- $uri_root = self::WinPath( $uri_root );
- $this->SetFileInfo( $filename, $uri_root );
- self::AddParsedFile( $filename );
- if ( $returnRoot ) {
- $rules = $this->GetRules( $filename );
- $return = new Less_Tree_Ruleset( array(), $rules );
- } else {
- $this->_parse( $filename );
- $return = $this;
- }
- if ( $previousFileInfo ) {
- $this->env->currentFileInfo = $previousFileInfo;
- }
- return $return;
- }
- /**
- * Allows a user to set variables values
- * @param array $vars
- * @return Less_Parser
- */
- public function ModifyVars( $vars ) {
- $this->input = Less_Parser::serializeVars( $vars );
- $this->_parse();
- return $this;
- }
- /**
- * @param string $filename
- */
- public function SetFileInfo( $filename, $uri_root = '' ) {
- $filename = Less_Environment::normalizePath( $filename );
- $dirname = preg_replace( '/[^\/\\\\]*$/', '', $filename );
- if ( !empty( $uri_root ) ) {
- $uri_root = rtrim( $uri_root, '/' ).'/';
- }
- $currentFileInfo = array();
- // entry info
- if ( isset( $this->env->currentFileInfo ) ) {
- $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath'];
- $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri'];
- $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath'];
- } else {
- $currentFileInfo['entryPath'] = $dirname;
- $currentFileInfo['entryUri'] = $uri_root;
- $currentFileInfo['rootpath'] = $dirname;
- }
- $currentFileInfo['currentDirectory'] = $dirname;
- $currentFileInfo['currentUri'] = $uri_root.basename( $filename );
- $currentFileInfo['filename'] = $filename;
- $currentFileInfo['uri_root'] = $uri_root;
- // inherit reference
- if ( isset( $this->env->currentFileInfo['reference'] ) && $this->env->currentFileInfo['reference'] ) {
- $currentFileInfo['reference'] = true;
- }
- $this->env->currentFileInfo = $currentFileInfo;
- }
- /**
- * @deprecated 1.5.1.2
- *
- */
- public function SetCacheDir( $dir ) {
- if ( !file_exists( $dir ) ) {
- if ( mkdir( $dir ) ) {
- return true;
- }
- throw new Less_Exception_Parser( 'Less.php cache directory couldn\'t be created: '.$dir );
- } elseif ( !is_dir( $dir ) ) {
- throw new Less_Exception_Parser( 'Less.php cache directory doesn\'t exist: '.$dir );
- } elseif ( !is_writable( $dir ) ) {
- throw new Less_Exception_Parser( 'Less.php cache directory isn\'t writable: '.$dir );
- } else {
- $dir = self::WinPath( $dir );
- Less_Cache::$cache_dir = rtrim( $dir, '/' ).'/';
- return true;
- }
- }
- /**
- * Set a list of directories or callbacks the parser should use for determining import paths
- *
- * @param array $dirs
- */
- public function SetImportDirs( $dirs ) {
- Less_Parser::$options['import_dirs'] = array();
- foreach ( $dirs as $path => $uri_root ) {
- $path = self::WinPath( $path );
- if ( !empty( $path ) ) {
- $path = rtrim( $path, '/' ).'/';
- }
- if ( !is_callable( $uri_root ) ) {
- $uri_root = self::WinPath( $uri_root );
- if ( !empty( $uri_root ) ) {
- $uri_root = rtrim( $uri_root, '/' ).'/';
- }
- }
- Less_Parser::$options['import_dirs'][$path] = $uri_root;
- }
- }
- /**
- * @param string $file_path
- */
- private function _parse( $file_path = null ) {
- $this->rules = array_merge( $this->rules, $this->GetRules( $file_path ) );
- }
- /**
- * Return the results of parsePrimary for $file_path
- * Use cache and save cached results if possible
- *
- * @param string|null $file_path
- */
- private function GetRules( $file_path ) {
- $this->SetInput( $file_path );
- $cache_file = $this->CacheFile( $file_path );
- if ( $cache_file ) {
- if ( Less_Parser::$options['cache_method'] == 'callback' ) {
- if ( is_callable( Less_Parser::$options['cache_callback_get'] ) ) {
- $cache = call_user_func_array(
- Less_Parser::$options['cache_callback_get'],
- array( $this, $file_path, $cache_file )
- );
- if ( $cache ) {
- $this->UnsetInput();
- return $cache;
- }
- }
- } elseif ( file_exists( $cache_file ) ) {
- switch ( Less_Parser::$options['cache_method'] ) {
- // Using serialize
- // Faster but uses more memory
- case 'serialize':
- $cache = unserialize( file_get_contents( $cache_file ) );
- if ( $cache ) {
- touch( $cache_file );
- $this->UnsetInput();
- return $cache;
- }
- break;
- // Using generated php code
- case 'var_export':
- case 'php':
- $this->UnsetInput();
- return include $cache_file;
- }
- }
- }
- $rules = $this->parsePrimary();
- if ( $this->pos < $this->input_len ) {
- throw new Less_Exception_Chunk( $this->input, null, $this->furthest, $this->env->currentFileInfo );
- }
- $this->UnsetInput();
- // save the cache
- if ( $cache_file ) {
- if ( Less_Parser::$options['cache_method'] == 'callback' ) {
- if ( is_callable( Less_Parser::$options['cache_callback_set'] ) ) {
- call_user_func_array(
- Less_Parser::$options['cache_callback_set'],
- array( $this, $file_path, $cache_file, $rules )
- );
- }
- } else {
- // msg('write cache file');
- switch ( Less_Parser::$options['cache_method'] ) {
- case 'serialize':
- file_put_contents( $cache_file, serialize( $rules ) );
- break;
- case 'php':
- file_put_contents( $cache_file, '<?php return '.self::ArgString( $rules ).'; ?>' );
- break;
- case 'var_export':
- // Requires __set_state()
- file_put_contents( $cache_file, '<?php return '.var_export( $rules, true ).'; ?>' );
- break;
- }
- Less_Cache::CleanCache();
- }
- }
- return $rules;
- }
- /**
- * Set up the input buffer
- *
- */
- public function SetInput( $file_path ) {
- if ( $file_path ) {
- $this->input = file_get_contents( $file_path );
- }
- $this->pos = $this->furthest = 0;
- // Remove potential UTF Byte Order Mark
- $this->input = preg_replace( '/\\G\xEF\xBB\xBF/', '', $this->input );
- $this->input_len = strlen( $this->input );
- if ( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ) {
- $uri = $this->env->currentFileInfo['currentUri'];
- Less_Parser::$contentsMap[$uri] = $this->input;
- }
- }
- /**
- * Free up some memory
- *
- */
- public function UnsetInput() {
- unset( $this->input, $this->pos, $this->input_len, $this->furthest );
- $this->saveStack = array();
- }
- public function CacheFile( $file_path ) {
- if ( $file_path && $this->CacheEnabled() ) {
- $env = get_object_vars( $this->env );
- unset( $env['frames'] );
- $parts = array();
- $parts[] = $file_path;
- $parts[] = filesize( $file_path );
- $parts[] = filemtime( $file_path );
- $parts[] = $env;
- $parts[] = Less_Version::cache_version;
- $parts[] = Less_Parser::$options['cache_method'];
- return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1( json_encode( $parts ) ), 16, 36 ) . '.lesscache';
- }
- }
- static function AddParsedFile( $file ) {
- self::$imports[] = $file;
- }
- static function AllParsedFiles() {
- return self::$imports;
- }
- /**
- * @param string $file
- */
- static function FileParsed( $file ) {
- return in_array( $file, self::$imports );
- }
- function save() {
- $this->saveStack[] = $this->pos;
- }
- private function restore() {
- $this->pos = array_pop( $this->saveStack );
- }
- private function forget() {
- array_pop( $this->saveStack );
- }
- /**
- * Determine if the character at the specified offset from the current position is a white space.
- *
- * @param int $offset
- *
- * @return bool
- */
- private function isWhitespace( $offset = 0 ) {
- return strpos( " \t\n\r\v\f", $this->input[$this->pos + $offset] ) !== false;
- }
- /**
- * Parse from a token, regexp or string, and move forward if match
- *
- * @param array $toks
- * @return array
- */
- private function match( $toks ) {
- // The match is confirmed, add the match length to `this::pos`,
- // and consume any extra white-space characters (' ' || '\n')
- // which come after that. The reason for this is that LeSS's
- // grammar is mostly white-space insensitive.
- //
- foreach ( $toks as $tok ) {
- $char = $tok[0];
- if ( $char === '/' ) {
- $match = $this->MatchReg( $tok );
- if ( $match ) {
- return count( $match ) === 1 ? $match[0] : $match;
- }
- } elseif ( $char === '#' ) {
- $match = $this->MatchChar( $tok[1] );
- } else {
- // Non-terminal, match using a function call
- $match = $this->$tok();
- }
- if ( $match ) {
- return $match;
- }
- }
- }
- /**
- * @param string[] $toks
- *
- * @return string
- */
- private function MatchFuncs( $toks ) {
- if ( $this->pos < $this->input_len ) {
- foreach ( $toks as $tok ) {
- $match = $this->$tok();
- if ( $match ) {
- return $match;
- }
- }
- }
- }
- // Match a single character in the input,
- private function MatchChar( $tok ) {
- if ( ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok ) ) {
- $this->skipWhitespace( 1 );
- return $tok;
- }
- }
- // Match a regexp from the current start point
- private function MatchReg( $tok ) {
- if ( preg_match( $tok, $this->input, $match, 0, $this->pos ) ) {
- $this->skipWhitespace( strlen( $match[0] ) );
- return $match;
- }
- }
- /**
- * Same as match(), but don't change the state of the parser,
- * just return the match.
- *
- * @param string $tok
- * @return integer
- */
- public function PeekReg( $tok ) {
- return preg_match( $tok, $this->input, $match, 0, $this->pos );
- }
- /**
- * @param string $tok
- */
- public function PeekChar( $tok ) {
- // return ($this->input[$this->pos] === $tok );
- return ( $this->pos < $this->input_len ) && ( $this->input[$this->pos] === $tok );
- }
- /**
- * @param integer $length
- */
- public function skipWhitespace( $length ) {
- $this->pos += $length;
- for ( ; $this->pos < $this->input_len; $this->pos++ ) {
- $c = $this->input[$this->pos];
- if ( ( $c !== "\n" ) && ( $c !== "\r" ) && ( $c !== "\t" ) && ( $c !== ' ' ) ) {
- break;
- }
- }
- }
- /**
- * @param string $tok
- * @param string|null $msg
- */
- public function expect( $tok, $msg = NULL ) {
- $result = $this->match( array( $tok ) );
- if ( !$result ) {
- $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg );
- } else {
- return $result;
- }
- }
- /**
- * @param string $tok
- */
- public function expectChar( $tok, $msg = null ) {
- $result = $this->MatchChar( $tok );
- if ( !$result ) {
- $msg = $msg ? $msg : "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'";
- $this->Error( $msg );
- } else {
- return $result;
- }
- }
- //
- // Here in, the parsing rules/functions
- //
- // The basic structure of the syntax tree generated is as follows:
- //
- // Ruleset -> Rule -> Value -> Expression -> Entity
- //
- // Here's some LESS code:
- //
- // .class {
- // color: #fff;
- // border: 1px solid #000;
- // width: @w + 4px;
- // > .child {...}
- // }
- //
- // And here's what the parse tree might look like:
- //
- // Ruleset (Selector '.class', [
- // Rule ("color", Value ([Expression [Color #fff]]))
- // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
- // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]]))
- // Ruleset (Selector [Element '>', '.child'], [...])
- // ])
- //
- // In general, most rules will try to parse a token with the `$()` function, and if the return
- // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
- // first, before parsing, that's when we use `peek()`.
- //
- //
- // The `primary` rule is the *entry* and *exit* point of the parser.
- // The rules here can appear at any level of the parse tree.
- //
- // The recursive nature of the grammar is an interplay between the `block`
- // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
- // as represented by this simplified grammar:
- //
- // primary → (ruleset | rule)+
- // ruleset → selector+ block
- // block → '{' primary '}'
- //
- // Only at one point is the primary rule not called from the
- // block rule: at the root level.
- //
- private function parsePrimary() {
- $root = array();
- while ( true ) {
- if ( $this->pos >= $this->input_len ) {
- break;
- }
- $node = $this->parseExtend( true );
- if ( $node ) {
- $root = array_merge( $root, $node );
- continue;
- }
- // $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective'));
- $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective' ) );
- if ( $node ) {
- $root[] = $node;
- } elseif ( !$this->MatchReg( '/\\G[\s\n;]+/' ) ) {
- break;
- }
- if ( $this->PeekChar( '}' ) ) {
- break;
- }
- }
- return $root;
- }
- // We create a Comment node for CSS comments `/* */`,
- // but keep the LeSS comments `//` silent, by just skipping
- // over them.
- private function parseComment() {
- if ( $this->input[$this->pos] !== '/' ) {
- return;
- }
- if ( $this->input[$this->pos + 1] === '/' ) {
- $match = $this->MatchReg( '/\\G\/\/.*/' );
- return $this->NewObj4( 'Less_Tree_Comment', array( $match[0], true, $this->pos, $this->env->currentFileInfo ) );
- }
- // $comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/');
- $comment = $this->MatchReg( '/\\G\/\*(?s).*?\*+\/\n?/' );// not the same as less.js to prevent fatal errors
- if ( $comment ) {
- return $this->NewObj4( 'Less_Tree_Comment', array( $comment[0], false, $this->pos, $this->env->currentFileInfo ) );
- }
- }
- private function parseComments() {
- $comments = array();
- while ( $this->pos < $this->input_len ) {
- $comment = $this->parseComment();
- if ( !$comment ) {
- break;
- }
- $comments[] = $comment;
- }
- return $comments;
- }
- //
- // A string, which supports escaping " and '
- //
- // "milky way" 'he\'s the one!'
- //
- private function parseEntitiesQuoted() {
- $j = $this->pos;
- $e = false;
- $index = $this->pos;
- if ( $this->input[$this->pos] === '~' ) {
- $j++;
- $e = true; // Escaped strings
- }
- $char = $this->input[$j];
- if ( $char !== '"' && $char !== "'" ) {
- return;
- }
- if ( $e ) {
- $this->MatchChar( '~' );
- }
- $matched = $this->MatchQuoted( $char, $j + 1 );
- if ( $matched === false ) {
- return;
- }
- $quoted = $char.$matched.$char;
- return $this->NewObj5( 'Less_Tree_Quoted', array( $quoted, $matched, $e, $index, $this->env->currentFileInfo ) );
- }
- /**
- * When PCRE JIT is enabled in php, regular expressions don't work for matching quoted strings
- *
- * $regex = '/\\G\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/';
- * $regex = '/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"/';
- *
- */
- private function MatchQuoted( $quote_char, $i ) {
- $matched = '';
- while ( $i < $this->input_len ) {
- $c = $this->input[$i];
- // escaped character
- if ( $c === '\\' ) {
- $matched .= $c . $this->input[$i + 1];
- $i += 2;
- continue;
- }
- if ( $c === $quote_char ) {
- $this->pos = $i + 1;
- $this->skipWhitespace( 0 );
- return $matched;
- }
- if ( $c === "\r" || $c === "\n" ) {
- return false;
- }
- $i++;
- $matched .= $c;
- }
- return false;
- }
- //
- // A catch-all word, such as:
- //
- // black border-collapse
- //
- private function parseEntitiesKeyword() {
- // $k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/');
- $k = $this->MatchReg( '/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/' );
- if ( $k ) {
- $k = $k[0];
- $color = $this->fromKeyword( $k );
- if ( $color ) {
- return $color;
- }
- return $this->NewObj1( 'Less_Tree_Keyword', $k );
- }
- }
- // duplicate of Less_Tree_Color::FromKeyword
- private function FromKeyword( $keyword ) {
- $keyword = strtolower( $keyword );
- if ( Less_Colors::hasOwnProperty( $keyword ) ) {
- // detect named color
- return $this->NewObj1( 'Less_Tree_Color', substr( Less_Colors::color( $keyword ), 1 ) );
- }
- if ( $keyword === 'transparent' ) {
- return $this->NewObj3( 'Less_Tree_Color', array( array( 0, 0, 0 ), 0, true ) );
- }
- }
- //
- // A function call
- //
- // rgb(255, 0, 255)
- //
- // We also try to catch IE's `alpha()`, but let the `alpha` parser
- // deal with the details.
- //
- // The arguments are parsed with the `entities.arguments` parser.
- //
- private function parseEntitiesCall() {
- $index = $this->pos;
- if ( !preg_match( '/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name, 0, $this->pos ) ) {
- return;
- }
- $name = $name[1];
- $nameLC = strtolower( $name );
- if ( $nameLC === 'url' ) {
- return null;
- }
- $this->pos += strlen( $name );
- if ( $nameLC === 'alpha' ) {
- $alpha_ret = $this->parseAlpha();
- if ( $alpha_ret ) {
- return $alpha_ret;
- }
- }
- $this->MatchChar( '(' ); // Parse the '(' and consume whitespace.
- $args = $this->parseEntitiesArguments();
- if ( !$this->MatchChar( ')' ) ) {
- return;
- }
- if ( $name ) {
- return $this->NewObj4( 'Less_Tree_Call', array( $name, $args, $index, $this->env->currentFileInfo ) );
- }
- }
- /**
- * Parse a list of arguments
- *
- * @return array
- */
- private function parseEntitiesArguments() {
- $args = array();
- while ( true ) {
- $arg = $this->MatchFuncs( array( 'parseEntitiesAssignment','parseExpression' ) );
- if ( !$arg ) {
- break;
- }
- $args[] = $arg;
- if ( !$this->MatchChar( ',' ) ) {
- break;
- }
- }
- return $args;
- }
- private function parseEntitiesLiteral() {
- return $this->MatchFuncs( array( 'parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor' ) );
- }
- // Assignments are argument entities for calls.
- // They are present in ie filter properties as shown below.
- //
- // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
- //
- private function parseEntitiesAssignment() {
- $key = $this->MatchReg( '/\\G\w+(?=\s?=)/' );
- if ( !$key ) {
- return;
- }
- if ( !$this->MatchChar( '=' ) ) {
- return;
- }
- $value = $this->parseEntity();
- if ( $value ) {
- return $this->NewObj2( 'Less_Tree_Assignment', array( $key[0], $value ) );
- }
- }
- //
- // Parse url() tokens
- //
- // We use a specific rule for urls, because they don't really behave like
- // standard function calls. The difference is that the argument doesn't have
- // to be enclosed within a string, so it can't be parsed as an Expression.
- //
- private function parseEntitiesUrl() {
- if ( $this->input[$this->pos] !== 'u' || !$this->matchReg( '/\\Gurl\(/' ) ) {
- return;
- }
- $value = $this->match( array( 'parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/' ) );
- if ( !$value ) {
- $value = '';
- }
- $this->expectChar( ')' );
- if ( isset( $value->value ) || $value instanceof Less_Tree_Variable ) {
- return $this->NewObj2( 'Less_Tree_Url', array( $value, $this->env->currentFileInfo ) );
- }
- return $this->NewObj2( 'Less_Tree_Url', array( $this->NewObj1( 'Less_Tree_Anonymous', $value ), $this->env->currentFileInfo ) );
- }
- //
- // A Variable entity, such as `@fink`, in
- //
- // width: @fink + 2px
- //
- // We use a different parser for variable definitions,
- // see `parsers.variable`.
- //
- private function parseEntitiesVariable() {
- $index = $this->pos;
- if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G@@?[\w-]+/' ) ) ) {
- return $this->NewObj3( 'Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo ) );
- }
- }
- // A variable entity using the protective {} e.g. @{var}
- private function parseEntitiesVariableCurly() {
- $index = $this->pos;
- if ( $this->input_len > ( $this->pos + 1 ) && $this->input[$this->pos] === '@' && ( $curly = $this->MatchReg( '/\\G@\{([\w-]+)\}/' ) ) ) {
- return $this->NewObj3( 'Less_Tree_Variable', array( '@'.$curly[1], $index, $this->env->currentFileInfo ) );
- }
- }
- //
- // A Hexadecimal color
- //
- // #4F3C2F
- //
- // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
- //
- private function parseEntitiesColor() {
- if ( $this->PeekChar( '#' ) && ( $rgb = $this->MatchReg( '/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/' ) ) ) {
- return $this->NewObj1( 'Less_Tree_Color', $rgb[1] );
- }
- }
- //
- // A Dimension, that is, a number and a unit
- //
- // 0.5em 95%
- //
- private function parseEntitiesDimension() {
- $c = @ord( $this->input[$this->pos] );
- // Is the first char of the dimension 0-9, '.', '+' or '-'
- if ( ( $c > 57 || $c < 43 ) || $c === 47 || $c == 44 ) {
- return;
- }
- $value = $this->MatchReg( '/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/' );
- if ( $value ) {
- if ( isset( $value[2] ) ) {
- return $this->NewObj2( 'Less_Tree_Dimension', array( $value[1],$value[2] ) );
- }
- return $this->NewObj1( 'Less_Tree_Dimension', $value[1] );
- }
- }
- //
- // A unicode descriptor, as is used in unicode-range
- //
- // U+0?? or U+00A1-00A9
- //
- function parseUnicodeDescriptor() {
- $ud = $this->MatchReg( '/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/' );
- if ( $ud ) {
- return $this->NewObj1( 'Less_Tree_UnicodeDescriptor', $ud[0] );
- }
- }
- //
- // JavaScript code to be evaluated
- //
- // `window.location.href`
- //
- private function parseEntitiesJavascript() {
- $e = false;
- $j = $this->pos;
- if ( $this->input[$j] === '~' ) {
- $j++;
- $e = true;
- }
- if ( $this->input[$j] !== '`' ) {
- return;
- }
- if ( $e ) {
- $this->MatchChar( '~' );
- }
- $str = $this->MatchReg( '/\\G`([^`]*)`/' );
- if ( $str ) {
- return $this->NewObj3( 'Less_Tree_Javascript', array( $str[1], $this->pos, $e ) );
- }
- }
- //
- // The variable part of a variable definition. Used in the `rule` parser
- //
- // @fink:
- //
- private function parseVariable() {
- if ( $this->PeekChar( '@' ) && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*:/' ) ) ) {
- return $name[1];
- }
- }
- //
- // The variable part of a variable definition. Used in the `rule` parser
- //
- // @fink();
- //
- private function parseRulesetCall() {
- if ( $this->input[$this->pos] === '@' && ( $name = $this->MatchReg( '/\\G(@[\w-]+)\s*\(\s*\)\s*;/' ) ) ) {
- return $this->NewObj1( 'Less_Tree_RulesetCall', $name[1] );
- }
- }
- //
- // extend syntax - used to extend selectors
- //
- function parseExtend( $isRule = false ) {
- $index = $this->pos;
- $extendList = array();
- if ( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ) { return;
- }
- do{
- $option = null;
- $elements = array();
- while ( true ) {
- $option = $this->MatchReg( '/\\G(all)(?=\s*(\)|,))/' );
- if ( $option ) { break;
- }
- $e = $this->parseElement();
- if ( !$e ) { break;
- }
- $elements[] = $e;
- }
- if ( $option ) {
- $option = $option[1];
- }
- $extendList[] = $this->NewObj3( 'Less_Tree_Extend', array( $this->NewObj1( 'Less_Tree_Selector', $elements ), $option, $index ) );
- }while ( $this->MatchChar( "," ) );
- $this->expect( '/\\G\)/' );
- if ( $isRule ) {
- $this->expect( '/\\G;/' );
- }
- return $extendList;
- }
- //
- // A Mixin call, with an optional argument list
- //
- // #mixins > .square(#fff);
- // .rounded(4px, black);
- // .button;
- //
- // The `while` loop is there because mixins can be
- // namespaced, but we only support the child and descendant
- // selector for now.
- //
- private function parseMixinCall() {
- $char = $this->input[$this->pos];
- if ( $char !== '.' && $char !== '#' ) {
- return;
- }
- $index = $this->pos;
- $this->save(); // stop us absorbing part of an invalid selector
- $elements = $this->parseMixinCallElements();
- if ( $elements ) {
- if ( $this->MatchChar( '(' ) ) {
- $returned = $this->parseMixinArgs( true );
- $args = $returned['args'];
- $this->expectChar( ')' );
- } else {
- $args = array();
- }
- $important = $this->parseImportant();
- if ( $this->parseEnd() ) {
- $this->forget();
- return $this->NewObj5( 'Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important ) );
- }
- }
- $this->restore();
- }
- private function parseMixinCallElements() {
- $elements = array();
- $c = null;
- while ( true ) {
- $elemIndex = $this->pos;
- $e = $this->MatchReg( '/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/' );
- if ( !$e ) {
- break;
- }
- $elements[] = $this->NewObj4( 'Less_Tree_Element', array( $c, $e[0], $elemIndex, $this->env->currentFileInfo ) );
- $c = $this->MatchChar( '>' );
- }
- return $elements;
- }
- /**
- * @param boolean $isCall
- */
- private function parseMixinArgs( $isCall ) {
- $expressions = array();
- $argsSemiColon = array();
- $isSemiColonSeperated = null;
- $argsComma = array();
- $expressionContainsNamed = null;
- $name = null;
- $returner = array( 'args' => array(), 'variadic' => false );
- $this->save();
- while ( true ) {
- if ( $isCall ) {
- $arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) );
- } else {
- $this->parseComments();
- if ( $this->input[ $this->pos ] === '.' && $this->MatchReg( '/\\G\.{3}/' ) ) {
- $returner['variadic'] = true;
- if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
- $isSemiColonSeperated = true;
- }
- if ( $isSemiColonSeperated ) {
- $argsSemiColon[] = array( 'variadic' => true );
- } else {
- $argsComma[] = array( 'variadic' => true );
- }
- break;
- }
- $arg = $this->MatchFuncs( array( 'parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword' ) );
- }
- if ( !$arg ) {
- break;
- }
- $nameLoop = null;
- if ( $arg instanceof Less_Tree_Expression ) {
- $arg->throwAwayComments();
- }
- $value = $arg;
- $val = null;
- if ( $isCall ) {
- // Variable
- if ( property_exists( $arg, 'value' ) && count( $arg->value ) == 1 ) {
- $val = $arg->value[0];
- }
- } else {
- $val = $arg;
- }
- if ( $val instanceof Less_Tree_Variable ) {
- if ( $this->MatchChar( ':' ) ) {
- if ( $expressions ) {
- if ( $isSemiColonSeperated ) {
- $this->Error( 'Cannot mix ; and , as delimiter types' );
- }
- $expressionContainsNamed = true;
- }
- // we do not support setting a ruleset as a default variable - it doesn't make sense
- // However if we do want to add it, there is nothing blocking it, just don't error
- // and remove isCall dependency below
- $value = null;
- if ( $isCall ) {
- $value = $this->parseDetachedRuleset();
- }
- if ( !$value ) {
- $value = $this->parseExpression();
- }
- if ( !$value ) {
- if ( $isCall ) {
- $this->Error( 'could not understand value for named argument' );
- } else {
- $this->restore();
- $returner['args'] = array();
- return $returner;
- }
- }
- $nameLoop = ( $name = $val->name );
- } elseif ( !$isCall && $this->MatchReg( '/\\G\.{3}/' ) ) {
- $returner['variadic'] = true;
- if ( $this->MatchChar( ";" ) && !$isSemiColonSeperated ) {
- $isSemiColonSeperated = true;
- }
- if ( $isSemiColonSeperated ) {
- $argsSemiColon[] = array( 'name' => $arg->name, 'variadic' => true );
- } else {
- $argsComma[] = array( 'name' => $arg->name, 'variadic' => true );
- }
- break;
- } elseif ( !$isCall ) {
- $name = $nameLoop = $val->name;
- $value = null;
- }
- }
- if ( $value ) {
- $expressions[] = $value;
- }
- $argsComma[] = array( 'name' => $nameLoop, 'value' => $value );
- if ( $this->MatchChar( ',' ) ) {
- continue;
- }
- if ( $this->MatchChar( ';' ) || $isSemiColonSeperated ) {
- if ( $expressionContainsNamed ) {
- $this->Error( 'Cannot mix ; and , as delimiter types' );
- }
- $isSemiColonSeperated = true;
- if ( count( $expressions ) > 1 ) {
- $value = $this->NewObj1( 'Less_Tree_Value', $expressions );
- }
- $argsSemiColon[] = array( 'name' => $name, 'value' => $value );
- $name = null;
- $expressions = array();
- $expressionContainsNamed = false;
- }
- }
- $this->forget();
- $returner['args'] = ( $isSemiColonSeperated ? $argsSemiColon : $argsComma );
- return $returner;
- }
- //
- // A Mixin definition, with a list of parameters
- //
- // .rounded (@radius: 2px, @color) {
- // ...
- // }
- //
- // Until we have a finer grained state-machine, we have to
- // do a look-ahead, to make sure we don't have a mixin call.
- // See the `rule` function for more information.
- //
- // We start by matching `.rounded (`, and then proceed on to
- // the argument list, which has optional default values.
- // We store the parameters in `params`, with a `value` key,
- // if there is a value, such as in the case of `@radius`.
- //
- // Once we've got our params list, and a closing `)`, we parse
- // the `{...}` block.
- //
- private function parseMixinDefinition() {
- $cond = null;
- $char = $this->input[$this->pos];
- if ( ( $char !== '.' && $char !== '#' ) || ( $char === '{' && $this->PeekReg( '/\\G[^{]*\}/' ) ) ) {
- return;
- }
- $this->save();
- $match = $this->MatchReg( '/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/' );
- if ( $match ) {
- $name = $match[1];
- $argInfo = $this->parseMixinArgs( false );
- $params = $argInfo['args'];
- $variadic = $argInfo['variadic'];
- // .mixincall("@{a}");
- // looks a bit like a mixin definition..
- // also
- // .mixincall(@a: {rule: set;});
- // so we have to be nice and restore
- if ( !$this->MatchChar( ')' ) ) {
- $this->furthest = $this->pos;
- $this->restore();
- return;
- }
- $this->parseComments();
- if ( $this->MatchReg( '/\\Gwhen/' ) ) { // Guard
- $cond = $this->expect( 'parseConditions', 'Expected conditions' );
- }
- $ruleset = $this->parseBlock();
- if ( is_array( $ruleset ) ) {
- $this->forget();
- return $this->NewObj5( 'Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic ) );
- }
- $this->restore();
- } else {
- $this->forget();
- }
- }
- //
- // Entities are the smallest recognized token,
- // and can be found inside a rule's value.
- //
- private function parseEntity() {
- return $this->MatchFuncs( array( 'parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment' ) );
- }
- //
- // A Rule terminator. Note that we use `peek()` to check for '}',
- // because the `block` rule will be expecting it, but we still need to make sure
- // it's there, if ';' was omitted.
- //
- private function parseEnd() {
- return $this->MatchChar( ';' ) || $this->PeekChar( '}' );
- }
- //
- // IE's alpha function
- //
- // alpha(opacity=88)
- //
- private function parseAlpha() {
- if ( !$this->MatchReg( '/\\G\(opacity=/i' ) ) {
- return;
- }
- $value = $this->MatchReg( '/\\G[0-9]+/' );
- if ( $value ) {
- $value = $value[0];
- } else {
- $value = $this->parseEntitiesVariable();
- if ( !$value ) {
- return;
- }
- }
- $this->expectChar( ')' );
- return $this->NewObj1( 'Less_Tree_Alpha', $value );
- }
- //
- // A Selector Element
- //
- // div
- // + h1
- // #socks
- // input[type="text"]
- //
- // Elements are the building blocks for Selectors,
- // they are made out of a `Combinator` (see combinator rule),
- // and an element name, such as a tag a class, or `*`.
- //
- private function parseElement() {
- $c = $this->parseCombinator();
- $index = $this->pos;
- $e = $this->match( array( '/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/',
- '#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly' ) );
- if ( is_null( $e ) ) {
- $this->save();
- if ( $this->MatchChar( '(' ) ) {
- if ( ( $v = $this->parseSelector() ) && $this->MatchChar( ')' ) ) {
- $e = $this->NewObj1( 'Less_Tree_Paren', $v );
- $this->forget();
- } else {
- $this->restore();
- }
- } else {
- $this->forget();
- }
- }
- if ( !is_null( $e ) ) {
- return $this->NewObj4( 'Less_Tree_Element', array( $c, $e, $index, $this->env->currentFileInfo ) );
- }
- }
- //
- // Combinators combine elements together, in a Selector.
- //
- // Because our parser isn't white-space sensitive, special care
- // has to be taken, when parsing the descendant combinator, ` `,
- // as it's an empty space. We have to check the previous character
- // in the input, to see if it's a ` ` character.
- //
- private function parseCombinator() {
- if ( $this->pos < $this->input_len ) {
- $c = $this->input[$this->pos];
- if ( $c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ) {
- $this->pos++;
- if ( $this->input[$this->pos] === '^' ) {
- $c = '^^';
- $this->pos++;
- }
- $this->skipWhitespace( 0 );
- return $c;
- }
- if ( $this->pos > 0 && $this->isWhitespace( -1 ) ) {
- return ' ';
- }
- }
- }
- //
- // A CSS selector (see selector below)
- // with less extensions e.g. the ability to extend and guard
- //
- private function parseLessSelector() {
- return $this->parseSelector( true );
- }
- //
- // A CSS Selector
- //
- // .class > div + h1
- // li a:hover
- //
- // Selectors are made out of one or more Elements, see above.
- //
- private function parseSelector( $isLess = false ) {
- $elements = array();
- $extendList = array();
- $condition = null;
- $when = false;
- $extend = false;
- $e = null;
- $c = null;
- $index = $this->pos;
- while ( ( $isLess && ( $extend = $this->parseExtend() ) ) || ( $isLess && ( $when = $this->MatchReg( '/\\Gwhen/' ) ) ) || ( $e = $this->parseElement() ) ) {
- if ( $when ) {
- $condition = $this->expect( 'parseConditions', 'expected condition' );
- } elseif ( $condition ) {
- // error("CSS guard can only be used at the end of selector");
- } elseif ( $extend ) {
- $extendList = array_merge( $extendList, $extend );
- } else {
- // if( count($extendList) ){
- //error("Extend can only be used at the end of selector");
- //}
- if ( $this->pos < $this->input_len ) {
- $c = $this->input[ $this->pos ];
- }
- $elements[] = $e;
- $e = null;
- }
- if ( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')' ) { break;
- }
- }
- if ( $elements ) {
- return $this->NewObj5( 'Less_Tree_Selector', array( $elements, $extendList, $condition, $index, $this->env->currentFileInfo ) );
- }
- if ( $extendList ) {
- $this->Error( 'Extend must be used to extend a selector, it cannot be used on its own' );
- }
- }
- private function parseTag() {
- return ( $tag = $this->MatchReg( '/\\G[A-Za-z][A-Za-z-]*[0-9]?/' ) ) ? $tag : $this->MatchChar( '*' );
- }
- private function parseAttribute() {
- $val = null;
- if ( !$this->MatchChar( '[' ) ) {
- return;
- }
- $key = $this->parseEntitiesVariableCurly();
- if ( !$key ) {
- $key = $this->expect( '/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/' );
- }
- $op = $this->MatchReg( '/\\G[|~*$^]?=/' );
- if ( $op ) {
- $val = $this->match( array( 'parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly' ) );
- }
- $this->expectChar( ']' );
- return $this->NewObj3( 'Less_Tree_Attribute', array( $key, $op === null ? null : $op[0], $val ) );
- }
- //
- // The `block` rule is used by `ruleset` and `mixin.definition`.
- // It's a wrapper around the `primary` rule, with added `{}`.
- //
- private function parseBlock() {
- if ( $this->MatchChar( '{' ) ) {
- $content = $this->parsePrimary();
- if ( $this->MatchChar( '}' ) ) {
- return $content;
- }
- }
- }
- private function parseBlockRuleset() {
- $block = $this->parseBlock();
- if ( $block ) {
- $block = $this->NewObj2( 'Less_Tree_Ruleset', array( null, $block ) );
- }
- return $block;
- }
- private function parseDetachedRuleset() {
- $blockRuleset = $this->parseBlockRuleset();
- if ( $blockRuleset ) {
- return $this->NewObj1( 'Less_Tree_DetachedRuleset', $blockRuleset );
- }
- }
- //
- // div, .class, body > p {...}
- //
- private function parseRuleset() {
- $selectors = array();
- $this->save();
- while ( true ) {
- $s = $this->parseLessSelector();
- if ( !$s ) {
- break;
- }
- $selectors[] = $s;
- $this->parseComments();
- if ( $s->condition && count( $selectors ) > 1 ) {
- $this->Error( 'Guards are only currently allowed on a single selector.' );
- }
- if ( !$this->MatchChar( ',' ) ) {
- break;
- }
- if ( $s->condition ) {
- $this->Error( 'Guards are only currently allowed on a single selector.' );
- }
- $this->parseComments();
- }
- if ( $selectors ) {
- $rules = $this->parseBlock();
- if ( is_array( $rules ) ) {
- $this->forget();
- return $this->NewObj2( 'Less_Tree_Ruleset', array( $selectors, $rules ) ); // Less_Environment::$strictImports
- }
- }
- // Backtrack
- $this->furthest = $this->pos;
- $this->restore();
- }
- /**
- * Custom less.php parse function for finding simple name-value css pairs
- * ex: width:100px;
- *
- */
- private function parseNameValue() {
- $index = $this->pos;
- $this->save();
- // $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/');
- $match = $this->MatchReg( '/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/' );
- if ( $match ) {
- if ( $match[4] == '}' ) {
- $this->pos = $index + strlen( $match[0] ) - 1;
- }
- if ( $match[3] ) {
- $match[2] .= ' !important';
- }
- return $this->NewObj4( 'Less_Tree_NameValue', array( $match[1], $match[2], $index, $this->env->currentFileInfo ) );
- }
- $this->restore();
- }
- private function parseRule( $tryAnonymous = null ) {
- $merge = false;
- $startOfRule = $this->pos;
- $c = $this->input[$this->pos];
- if ( $c === '.' || $c === '#' || $c === '&' ) {
- return;
- }
- $this->save();
- $name = $this->MatchFuncs( array( 'parseVariable','parseRuleProperty' ) );
- if ( $name ) {
- $isVariable = is_string( $name );
- $value = null;
- if ( $isVariable ) {
- $value = $this->parseDetachedRuleset();
- }
- $important = null;
- if ( !$value ) {
- // prefer to try to parse first if its a variable or we are compressing
- // but always fallback on the other one
- //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){
- if ( !$tryAnonymous && ( Less_Parser::$options['compress'] || $isVariable ) ) {
- $value = $this->MatchFuncs( array( 'parseValue','parseAnonymousValue' ) );
- } else {
- $value = $this->MatchFuncs( array( 'parseAnonymousValue','parseValue' ) );
- }
- $important = $this->parseImportant();
- // a name returned by this.ruleProperty() is always an array of the form:
- // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
- // where each item is a tree.Keyword or tree.Variable
- if ( !$isVariable && is_array( $name ) ) {
- $nm = array_pop( $name );
- if ( $nm->value ) {
- $merge = $nm->value;
- }
- }
- }
- if ( $value && $this->parseEnd() ) {
- $this->forget();
- return $this->NewObj6( 'Less_Tree_Rule', array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo ) );
- } else {
- $this->furthest = $this->pos;
- $this->restore();
- if ( $value && !$tryAnonymous ) {
- return $this->parseRule( true );
- }
- }
- } else {
- $this->forget();
- }
- }
- function parseAnonymousValue() {
- if ( preg_match( '/\\G([^@+\/\'"*`(;{}-]*);/', $this->input, $match, 0, $this->pos ) ) {
- $this->pos += strlen( $match[1] );
- return $this->NewObj1( 'Less_Tree_Anonymous', $match[1] );
- }
- }
- //
- // An @import directive
- //
- // @import "lib";
- //
- // Depending on our environment, importing is done differently:
- // In the browser, it's an XHR request, in Node, it would be a
- // file-system operation. The function used for importing is
- // stored in `import`, which we pass to the Import constructor.
- //
- private function parseImport() {
- $this->save();
- $dir = $this->MatchReg( '/\\G@import?\s+/' );
- if ( $dir ) {
- $options = $this->parseImportOptions();
- $path = $this->MatchFuncs( array( 'parseEntitiesQuoted','parseEntitiesUrl' ) );
- if ( $path ) {
- $features = $this->parseMediaFeatures();
- if ( $this->MatchChar( ';' ) ) {
- if ( $features ) {
- $features = $this->NewObj1( 'Less_Tree_Value', $features );
- }
- $this->forget();
- return $this->NewObj5( 'Less_Tree_Import', array( $path, $features, $options, $this->pos, $this->env->currentFileInfo ) );
- }
- }
- }
- $this->restore();
- }
- private function parseImportOptions() {
- $options = array();
- // list of options, surrounded by parens
- if ( !$this->MatchChar( '(' ) ) {
- return $options;
- }
- do{
- $optionName = $this->parseImportOption();
- if ( $optionName ) {
- $value = true;
- switch ( $optionName ) {
- case "css":
- $optionName = "less";
- $value = false;
- break;
- case "once":
- $optionName = "multiple";
- $value = false;
- break;
- }
- $options[$optionName] = $value;
- if ( !$this->MatchChar( ',' ) ) { break;
- }
- }
- }while ( $optionName );
- $this->expectChar( ')' );
- return $options;
- }
- private function parseImportOption() {
- $opt = $this->MatchReg( '/\\G(less|css|multiple|once|inline|reference|optional)/' );
- if ( $opt ) {
- return $opt[1];
- }
- }
- private function parseMediaFeature() {
- $nodes = array();
- do{
- $e = $this->MatchFuncs( array( 'parseEntitiesKeyword','parseEntitiesVariable' ) );
- if ( $e ) {
- $nodes[] = $e;
- } elseif ( $this->MatchChar( '(' ) ) {
- $p = $this->parseProperty();
- $e = $this->parseValue();
- if ( $this->MatchChar( ')' ) ) {
- if ( $p && $e ) {
- $r = $this->NewObj7( 'Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true ) );
- $nodes[] = $this->NewObj1( 'Less_Tree_Paren', $r );
- } elseif ( $e ) {
- $nodes[] = $this->NewObj1( 'Less_Tree_Paren', $e );
- } else {
- return null;
- }
- } else return null;
- }
- } while ( $e );
- if ( $nodes ) {
- return $this->NewObj1( 'Less_Tree_Expression', $nodes );
- }
- }
- private function parseMediaFeatures() {
- $features = array();
- do{
- $e = $this->parseMediaFeature();
- if ( $e ) {
- $features[] = $e;
- if ( !$this->MatchChar( ',' ) ) break;
- } else {
- $e = $this->parseEntitiesVariable();
- if ( $e ) {
- $features[] = $e;
- if ( !$this->MatchChar( ',' ) ) break;
- }
- }
- } while ( $e );
- return $features ? $features : null;
- }
- private function parseMedia() {
- if ( $this->MatchReg( '/\\G@media/' ) ) {
- $features = $this->parseMediaFeatures();
- $rules = $this->parseBlock();
- if ( is_array( $rules ) ) {
- return $this->NewObj4( 'Less_Tree_Media', array( $rules, $features, $this->pos, $this->env->currentFileInfo ) );
- }
- }
- }
- //
- // A CSS Directive
- //
- // @charset "utf-8";
- //
- private function parseDirective() {
- if ( !$this->PeekChar( '@' ) ) {
- return;
- }
- $rules = null;
- $index = $this->pos;
- $hasBlock = true;
- $hasIdentifier = false;
- $hasExpression = false;
- $hasUnknown = false;
- $value = $this->MatchFuncs( array( 'parseImport','parseMedia' ) );
- if ( $value ) {
- return $value;
- }
- $this->save();
- $name = $this->MatchReg( '/\\G@[a-z-]+/' );
- if ( !$name ) return;
- $name = $name[0];
- $nonVendorSpecificName = $name;
- $pos = strpos( $name, '-', 2 );
- if ( $name[1] == '-' && $pos > 0 ) {
- $nonVendorSpecificName = "@" . substr( $name, $pos + 1 );
- }
- switch ( $nonVendorSpecificName ) {
- /*
- case "@font-face":
- case "@viewport":
- case "@top-left":
- case "@top-left-corner":
- case "@top-center":
- case "@top-right":
- case "@top-right-corner":
- case "@bottom-left":
- case "@bottom-left-corner":
- case "@bottom-center":
- case "@bottom-right":
- case "@bottom-right-corner":
- case "@left-top":
- case "@left-middle":
- case "@left-bottom":
- case "@right-top":
- case "@right-middle":
- case "@right-bottom":
- hasBlock = true;
- break;
- */
- case "@charset":
- $hasIdentifier = true;
- $hasBlock = false;
- break;
- case "@namespace":
- $hasExpression = true;
- $hasBlock = false;
- break;
- case "@keyframes":
- $hasIdentifier = true;
- break;
- case "@host":
- case "@page":
- case "@document":
- case "@supports":
- $hasUnknown = true;
- break;
- }
- if ( $hasIdentifier ) {
- $value = $this->parseEntity();
- if ( !$value ) {
- $this->error( "expected " . $name . " identifier" );
- }
- } else if ( $hasExpression ) {
- $value = $this->parseExpression();
- if ( !$value ) {
- $this->error( "expected " . $name. " expression" );
- }
- } else if ( $hasUnknown ) {
- $value = $this->MatchReg( '/\\G[^{;]+/' );
- if ( $value ) {
- $value = $this->NewObj1( 'Less_Tree_Anonymous', trim( $value[0] ) );
- }
- }
- if ( $hasBlock ) {
- $rules = $this->parseBlockRuleset();
- }
- if ( $rules || ( !$hasBlock && $value && $this->MatchChar( ';' ) ) ) {
- $this->forget();
- return $this->NewObj5( 'Less_Tree_Directive', array( $name, $value, $rules, $index, $this->env->currentFileInfo ) );
- }
- $this->restore();
- }
- //
- // A Value is a comma-delimited list of Expressions
- //
- // font-family: Baskerville, Georgia, serif;
- //
- // In a Rule, a Value represents everything after the `:`,
- // and before the `;`.
- //
- private function parseValue() {
- $expressions = array();
- do{
- $e = $this->parseExpression();
- if ( $e ) {
- $expressions[] = $e;
- if ( !$this->MatchChar( ',' ) ) {
- break;
- }
- }
- }while ( $e );
- if ( $expressions ) {
- return $this->NewObj1( 'Less_Tree_Value', $expressions );
- }
- }
- private function parseImportant() {
- if ( $this->PeekChar( '!' ) && $this->MatchReg( '/\\G! *important/' ) ) {
- return ' !important';
- }
- }
- private function parseSub() {
- if ( $this->MatchChar( '(' ) ) {
- $a = $this->parseAddition();
- if ( $a ) {
- $this->expectChar( ')' );
- return $this->NewObj2( 'Less_Tree_Expression', array( array( $a ), true ) ); // instead of $e->parens = true so the value is cached
- }
- }
- }
- /**
- * Parses multiplication operation
- *
- * @return Less_Tree_Operation|null
- */
- function parseMultiplication() {
- $return = $m = $this->parseOperand();
- if ( $return ) {
- while ( true ) {
- $isSpaced = $this->isWhitespace( -1 );
- if ( $this->PeekReg( '/\\G\/[*\/]/' ) ) {
- break;
- }
- $op = $this->MatchChar( '/' );
- if ( !$op ) {
- $op = $this->MatchChar( '*' );
- if ( !$op ) {
- break;
- }
- }
- $a = $this->parseOperand();
- if ( !$a ) { break;
- }
- $m->parensInOp = true;
- $a->parensInOp = true;
- $return = $this->NewObj3( 'Less_Tree_Operation', array( $op, array( $return, $a ), $isSpaced ) );
- }
- }
- return $return;
- }
- /**
- * Parses an addition operation
- *
- * @return Less_Tree_Operation|null
- */
- private function parseAddition() {
- $return = $m = $this->parseMultiplication();
- if ( $return ) {
- while ( true ) {
- $isSpaced = $this->isWhitespace( -1 );
- $op = $this->MatchReg( '/\\G[-+]\s+/' );
- if ( $op ) {
- $op = $op[0];
- } else {
- if ( !$isSpaced ) {
- $op = $this->match( array( '#+','#-' ) );
- }
- if ( !$op ) {
- break;
- }
- }
- $a = $this->parseMultiplication();
- if ( !$a ) {
- break;
- }
- $m->parensInOp = true;
- $a->parensInOp = true;
- $return = $this->NewObj3( 'Less_Tree_Operation', array( $op, array( $return, $a ), $isSpaced ) );
- }
- }
- return $return;
- }
- /**
- * Parses the conditions
- *
- * @return Less_Tree_Condition|null
- */
- private function parseConditions() {
- $index = $this->pos;
- $return = $a = $this->parseCondition();
- if ( $a ) {
- while ( true ) {
- if ( !$this->PeekReg( '/\\G,\s*(not\s*)?\(/' ) || !$this->MatchChar( ',' ) ) {
- break;
- }
- $b = $this->parseCondition();
- if ( !$b ) {
- break;
- }
- $return = $this->NewObj4( 'Less_Tree_Condition', array( 'or', $return, $b, $index ) );
- }
- return $return;
- }
- }
- private function parseCondition() {
- $index = $this->pos;
- $negate = false;
- $c = null;
- if ( $this->MatchReg( '/\\Gnot/' ) ) $negate = true;
- $this->expectChar( '(' );
- $a = $this->MatchFuncs( array( 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ) );
- if ( $a ) {
- $op = $this->MatchReg( '/\\G(?:>=|<=|=<|[<=>])/' );
- if ( $op ) {
- $b = $this->MatchFuncs( array( 'parseAddition','parseEntitiesKeyword','parseEntitiesQuoted' ) );
- if ( $b ) {
- $c = $this->NewObj5( 'Less_Tree_Condition', array( $op[0], $a, $b, $index, $negate ) );
- } else {
- $this->Error( 'Unexpected expression' );
- }
- } else {
- $k = $this->NewObj1( 'Less_Tree_Keyword', 'true' );
- $c = $this->NewObj5( 'Less_Tree_Condition', array( '=', $a, $k, $index, $negate ) );
- }
- $this->expectChar( ')' );
- return $this->MatchReg( '/\\Gand/' ) ? $this->NewObj3( 'Less_Tree_Condition', array( 'and', $c, $this->parseCondition() ) ) : $c;
- }
- }
- /**
- * An operand is anything that can be part of an operation,
- * such as a Color, or a Variable
- *
- */
- private function parseOperand() {
- $negate = false;
- $offset = $this->pos + 1;
- if ( $offset >= $this->input_len ) {
- return;
- }
- $char = $this->input[$offset];
- if ( $char === '@' || $char === '(' ) {
- $negate = $this->MatchChar( '-' );
- }
- $o = $this->MatchFuncs( array( 'parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall' ) );
- if ( $negate ) {
- $o->parensInOp = true;
- $o = $this->NewObj1( 'Less_Tree_Negative', $o );
- }
- return $o;
- }
- /**
- * Expressions either represent mathematical operations,
- * or white-space delimited Entities.
- *
- * 1px solid black
- * @var * 2
- *
- * @return Less_Tree_Expression|null
- */
- private function parseExpression() {
- $entities = array();
- do{
- $e = $this->MatchFuncs( array( 'parseAddition','parseEntity' ) );
- if ( $e ) {
- $entities[] = $e;
- // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
- if ( !$this->PeekReg( '/\\G\/[\/*]/' ) ) {
- $delim = $this->MatchChar( '/' );
- if ( $delim ) {
- $entities[] = $this->NewObj1( 'Less_Tree_Anonymous', $delim );
- }
- }
- }
- }while ( $e );
- if ( $entities ) {
- return $this->NewObj1( 'Less_Tree_Expression', $entities );
- }
- }
- /**
- * Parse a property
- * eg: 'min-width', 'orientation', etc
- *
- * @return string
- */
- private function parseProperty() {
- $name = $this->MatchReg( '/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/' );
- if ( $name ) {
- return $name[1];
- }
- }
- /**
- * Parse a rule property
- * eg: 'color', 'width', 'height', etc
- *
- * @return string
- */
- private function parseRuleProperty() {
- $offset = $this->pos;
- $name = array();
- $index = array();
- $length = 0;
- $this->rulePropertyMatch( '/\\G(\*?)/', $offset, $length, $index, $name );
- while ( $this->rulePropertyMatch( '/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name ) ); // !
- if ( ( count( $name ) > 1 ) && $this->rulePropertyMatch( '/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name ) ) {
- // at last, we have the complete match now. move forward,
- // convert name particles to tree objects and return:
- $this->skipWhitespace( $length );
- if ( $name[0] === '' ) {
- array_shift( $name );
- array_shift( $index );
- }
- foreach ( $name as $k => $s ) {
- if ( !$s || $s[0] !== '@' ) {
- $name[$k] = $this->NewObj1( 'Less_Tree_Keyword', $s );
- } else {
- $name[$k] = $this->NewObj3( 'Less_Tree_Variable', array( '@' . substr( $s, 2, -1 ), $index[$k], $this->env->currentFileInfo ) );
- }
- }
- return $name;
- }
- }
- private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ) {
- preg_match( $re, $this->input, $a, 0, $offset );
- if ( $a ) {
- $index[] = $this->pos + $length;
- $length += strlen( $a[0] );
- $offset += strlen( $a[0] );
- $name[] = $a[1];
- return true;
- }
- }
- public static function serializeVars( $vars ) {
- $s = '';
- foreach ( $vars as $name => $value ) {
- $s .= ( ( $name[0] === '@' ) ? '' : '@' ) . $name .': '. $value . ( ( substr( $value, -1 ) === ';' ) ? '' : ';' );
- }
- return $s;
- }
- /**
- * Some versions of php have trouble with method_exists($a,$b) if $a is not an object
- *
- * @param string $b
- */
- public static function is_method( $a, $b ) {
- return is_object( $a ) && method_exists( $a, $b );
- }
- /**
- * Round numbers similarly to javascript
- * eg: 1.499999 to 1 instead of 2
- *
- */
- public static function round( $i, $precision = 0 ) {
- $precision = pow( 10, $precision );
- $i = $i * $precision;
- $ceil = ceil( $i );
- $floor = floor( $i );
- if ( ( $ceil - $i ) <= ( $i - $floor ) ) {
- return $ceil / $precision;
- } else {
- return $floor / $precision;
- }
- }
- /**
- * Create Less_Tree_* objects and optionally generate a cache string
- *
- * @return mixed
- */
- public function NewObj0( $class ) {
- $obj = new $class();
- if ( $this->CacheEnabled() ) {
- $obj->cache_string = ' new '.$class.'()';
- }
- return $obj;
- }
- public function NewObj1( $class, $arg ) {
- $obj = new $class( $arg );
- if ( $this->CacheEnabled() ) {
- $obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString( $arg ).')';
- }
- return $obj;
- }
- public function NewObj2( $class, $args ) {
- $obj = new $class( $args[0], $args[1] );
- if ( $this->CacheEnabled() ) {
- $this->ObjCache( $obj, $class, $args );
- }
- return $obj;
- }
- public function NewObj3( $class, $args ) {
- $obj = new $class( $args[0], $args[1], $args[2] );
- if ( $this->CacheEnabled() ) {
- $this->ObjCache( $obj, $class, $args );
- }
- return $obj;
- }
- public function NewObj4( $class, $args ) {
- $obj = new $class( $args[0], $args[1], $args[2], $args[3] );
- if ( $this->CacheEnabled() ) {
- $this->ObjCache( $obj, $class, $args );
- }
- return $obj;
- }
- public function NewObj5( $class, $args ) {
- $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] );
- if ( $this->CacheEnabled() ) {
- $this->ObjCache( $obj, $class, $args );
- }
- return $obj;
- }
- public function NewObj6( $class, $args ) {
- $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] );
- if ( $this->CacheEnabled() ) {
- $this->ObjCache( $obj, $class, $args );
- }
- return $obj;
- }
- public function NewObj7( $class, $args ) {
- $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] );
- if ( $this->CacheEnabled() ) {
- $this->ObjCache( $obj, $class, $args );
- }
- return $obj;
- }
- // caching
- public function ObjCache( $obj, $class, $args = array() ) {
- $obj->cache_string = ' new '.$class.'('. self::ArgCache( $args ).')';
- }
- public function ArgCache( $args ) {
- return implode( ',', array_map( array( 'Less_Parser','ArgString' ), $args ) );
- }
- /**
- * Convert an argument to a string for use in the parser cache
- *
- * @return string
- */
- public static function ArgString( $arg ) {
- $type = gettype( $arg );
- if ( $type === 'object' ) {
- $string = $arg->cache_string;
- unset( $arg->cache_string );
- return $string;
- } elseif ( $type === 'array' ) {
- $string = ' Array(';
- foreach ( $arg as $k => $a ) {
- $string .= var_export( $k, true ).' => '.self::ArgString( $a ).',';
- }
- return $string . ')';
- }
- return var_export( $arg, true );
- }
- public function Error( $msg ) {
- throw new Less_Exception_Parser( $msg, null, $this->furthest, $this->env->currentFileInfo );
- }
- public static function WinPath( $path ) {
- return str_replace( '\\', '/', $path );
- }
- public static function AbsPath( $path, $winPath = false ) {
- if ( strpos( $path, '//' ) !== false && preg_match( '_^(https?:)?//\\w+(\\.\\w+)+/\\w+_i', $path ) ) {
- return $winPath ? '' : false;
- } else {
- $path = realpath( $path );
- if ( $winPath ) {
- $path = self::WinPath( $path );
- }
- return $path;
- }
- }
- public function CacheEnabled() {
- return ( Less_Parser::$options['cache_method'] && ( Less_Cache::$cache_dir || ( Less_Parser::$options['cache_method'] == 'callback' ) ) );
- }
- }
|