1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
require_once( dirname(__FILE__).'/Cache.php'); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Class for parsing and compiling less files into css |
7
|
|
|
* |
8
|
|
|
* @package Less |
9
|
|
|
* @subpackage parser |
10
|
|
|
* |
11
|
|
|
*/ |
12
|
|
|
class Less_Parser{ |
|
|
|
|
13
|
|
|
|
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Default parser options |
17
|
|
|
*/ |
18
|
|
|
public static $default_options = array( |
19
|
|
|
'compress' => false, // option - whether to compress |
20
|
|
|
'strictUnits' => false, // whether units need to evaluate correctly |
21
|
|
|
'strictMath' => false, // whether math has to be within parenthesis |
22
|
|
|
'relativeUrls' => true, // option - whether to adjust URL's to be relative |
23
|
|
|
'urlArgs' => array(), // whether to add args into url tokens |
24
|
|
|
'numPrecision' => 8, |
25
|
|
|
|
26
|
|
|
'import_dirs' => array(), |
27
|
|
|
'import_callback' => null, |
28
|
|
|
'cache_dir' => null, |
29
|
|
|
'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback'; |
|
|
|
|
30
|
|
|
'cache_callback_get' => null, |
31
|
|
|
'cache_callback_set' => null, |
32
|
|
|
|
33
|
|
|
'sourceMap' => false, // whether to output a source map |
34
|
|
|
'sourceMapBasepath' => null, |
35
|
|
|
'sourceMapWriteTo' => null, |
36
|
|
|
'sourceMapURL' => null, |
37
|
|
|
|
38
|
|
|
'plugins' => array(), |
39
|
|
|
|
40
|
|
|
); |
41
|
|
|
|
42
|
|
|
public static $options = array(); |
43
|
|
|
|
44
|
|
|
|
45
|
|
|
private $input; // Less input string |
46
|
|
|
private $input_len; // input string length |
47
|
|
|
private $pos; // current index in `input` |
48
|
|
|
private $saveStack = array(); // holds state for backtracking |
49
|
|
|
private $furthest; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var Less_Environment |
53
|
|
|
*/ |
54
|
|
|
private $env; |
55
|
|
|
|
56
|
|
|
private $rules = array(); |
57
|
|
|
|
58
|
|
|
private static $imports = array(); |
59
|
|
|
|
60
|
|
|
public static $has_extends = false; |
61
|
|
|
|
62
|
|
|
public static $next_id = 0; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Filename to contents of all parsed the files |
66
|
|
|
* |
67
|
|
|
* @var array |
68
|
|
|
*/ |
69
|
|
|
public static $contentsMap = array(); |
70
|
|
|
|
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @param Less_Environment|array|null $env |
74
|
|
|
*/ |
75
|
|
|
public function __construct( $env = null ){ |
76
|
|
|
|
77
|
|
|
// Top parser on an import tree must be sure there is one "env" |
78
|
|
|
// which will then be passed around by reference. |
79
|
|
|
if( $env instanceof Less_Environment ){ |
80
|
|
|
$this->env = $env; |
81
|
|
|
}else{ |
82
|
|
|
$this->SetOptions(Less_Parser::$default_options); |
83
|
|
|
$this->Reset( $env ); |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Reset the parser state completely |
91
|
|
|
* |
92
|
|
|
*/ |
93
|
|
|
public function Reset( $options = null ){ |
94
|
|
|
$this->rules = array(); |
95
|
|
|
self::$imports = array(); |
96
|
|
|
self::$has_extends = false; |
97
|
|
|
self::$imports = array(); |
98
|
|
|
self::$contentsMap = array(); |
99
|
|
|
|
100
|
|
|
$this->env = new Less_Environment($options); |
|
|
|
|
101
|
|
|
$this->env->Init(); |
102
|
|
|
|
103
|
|
|
//set new options |
104
|
|
|
if( is_array($options) ){ |
105
|
|
|
$this->SetOptions(Less_Parser::$default_options); |
106
|
|
|
$this->SetOptions($options); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Set one or more compiler options |
112
|
|
|
* options: import_dirs, cache_dir, cache_method |
113
|
|
|
* |
114
|
|
|
*/ |
115
|
|
|
public function SetOptions( $options ){ |
116
|
|
|
foreach($options as $option => $value){ |
117
|
|
|
$this->SetOption($option,$value); |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Set one compiler option |
123
|
|
|
* |
124
|
|
|
*/ |
125
|
|
|
public function SetOption($option,$value){ |
126
|
|
|
|
127
|
|
|
switch($option){ |
128
|
|
|
|
129
|
|
|
case 'import_dirs': |
130
|
|
|
$this->SetImportDirs($value); |
131
|
|
|
return; |
132
|
|
|
|
133
|
|
|
case 'cache_dir': |
134
|
|
|
if( is_string($value) ){ |
135
|
|
|
Less_Cache::SetCacheDir($value); |
136
|
|
|
Less_Cache::CheckCacheDir(); |
137
|
|
|
} |
138
|
|
|
return; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
Less_Parser::$options[$option] = $value; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Registers a new custom function |
146
|
|
|
* |
147
|
|
|
* @param string $name function name |
148
|
|
|
* @param callable $callback callback |
149
|
|
|
*/ |
150
|
|
|
public function registerFunction($name, $callback) { |
151
|
|
|
$this->env->functions[$name] = $callback; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Removed an already registered function |
156
|
|
|
* |
157
|
|
|
* @param string $name function name |
158
|
|
|
*/ |
159
|
|
|
public function unregisterFunction($name) { |
160
|
|
|
if( isset($this->env->functions[$name]) ) |
161
|
|
|
unset($this->env->functions[$name]); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Get the current css buffer |
167
|
|
|
* |
168
|
|
|
* @return string |
169
|
|
|
*/ |
170
|
|
|
public function getCss(){ |
171
|
|
|
|
172
|
|
|
$precision = ini_get('precision'); |
173
|
|
|
@ini_set('precision',16); |
|
|
|
|
174
|
|
|
$locale = setlocale(LC_NUMERIC, 0); |
175
|
|
|
setlocale(LC_NUMERIC, "C"); |
176
|
|
|
|
177
|
|
|
$root = new Less_Tree_Ruleset(array(), $this->rules ); |
178
|
|
|
$root->root = true; |
179
|
|
|
$root->firstRoot = true; |
180
|
|
|
|
181
|
|
|
|
182
|
|
|
$this->PreVisitors($root); |
183
|
|
|
|
184
|
|
|
self::$has_extends = false; |
185
|
|
|
$evaldRoot = $root->compile($this->env); |
186
|
|
|
|
187
|
|
|
|
188
|
|
|
|
189
|
|
|
$this->PostVisitors($evaldRoot); |
190
|
|
|
|
191
|
|
|
if( Less_Parser::$options['sourceMap'] ){ |
192
|
|
|
$generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options ); |
193
|
|
|
// will also save file |
194
|
|
|
// FIXME: should happen somewhere else? |
195
|
|
|
$css = $generator->generateCSS(); |
196
|
|
|
}else{ |
197
|
|
|
$css = $evaldRoot->toCSS(); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
if( Less_Parser::$options['compress'] ){ |
201
|
|
|
$css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
//reset php settings |
205
|
|
|
@ini_set('precision',$precision); |
|
|
|
|
206
|
|
|
setlocale(LC_NUMERIC, $locale); |
207
|
|
|
|
208
|
|
|
return $css; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Run pre-compile visitors |
213
|
|
|
* |
214
|
|
|
*/ |
215
|
|
|
private function PreVisitors($root){ |
216
|
|
|
|
217
|
|
|
if( Less_Parser::$options['plugins'] ){ |
218
|
|
|
foreach(Less_Parser::$options['plugins'] as $plugin){ |
219
|
|
|
if( !empty($plugin->isPreEvalVisitor) ){ |
220
|
|
|
$plugin->run($root); |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Run post-compile visitors |
229
|
|
|
* |
230
|
|
|
*/ |
231
|
|
|
private function PostVisitors($evaldRoot){ |
232
|
|
|
|
233
|
|
|
$visitors = array(); |
234
|
|
|
$visitors[] = new Less_Visitor_joinSelector(); |
235
|
|
|
if( self::$has_extends ){ |
236
|
|
|
$visitors[] = new Less_Visitor_processExtends(); |
237
|
|
|
} |
238
|
|
|
$visitors[] = new Less_Visitor_toCSS(); |
239
|
|
|
|
240
|
|
|
|
241
|
|
|
if( Less_Parser::$options['plugins'] ){ |
242
|
|
|
foreach(Less_Parser::$options['plugins'] as $plugin){ |
243
|
|
|
if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){ |
244
|
|
|
continue; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){ |
248
|
|
|
array_unshift( $visitors, $plugin); |
249
|
|
|
}else{ |
250
|
|
|
$visitors[] = $plugin; |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
|
256
|
|
|
for($i = 0; $i < count($visitors); $i++ ){ |
|
|
|
|
257
|
|
|
$visitors[$i]->run($evaldRoot); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Parse a Less string into css |
265
|
|
|
* |
266
|
|
|
* @param string $str The string to convert |
267
|
|
|
* @param string $uri_root The url of the file |
|
|
|
|
268
|
|
|
* @return Less_Tree_Ruleset|Less_Parser |
269
|
|
|
*/ |
270
|
|
|
public function parse( $str, $file_uri = null ){ |
271
|
|
|
|
272
|
|
|
if( !$file_uri ){ |
273
|
|
|
$uri_root = ''; |
274
|
|
|
$filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less'; |
275
|
|
|
}else{ |
276
|
|
|
$file_uri = self::WinPath($file_uri); |
277
|
|
|
$filename = basename($file_uri); |
278
|
|
|
$uri_root = dirname($file_uri); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
$previousFileInfo = $this->env->currentFileInfo; |
282
|
|
|
$uri_root = self::WinPath($uri_root); |
283
|
|
|
$this->SetFileInfo($filename, $uri_root); |
284
|
|
|
|
285
|
|
|
$this->input = $str; |
286
|
|
|
$this->_parse(); |
287
|
|
|
|
288
|
|
|
if( $previousFileInfo ){ |
289
|
|
|
$this->env->currentFileInfo = $previousFileInfo; |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
return $this; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Parse a Less string from a given file |
298
|
|
|
* |
299
|
|
|
* @throws Less_Exception_Parser |
300
|
|
|
* @param string $filename The file to parse |
301
|
|
|
* @param string $uri_root The url of the file |
302
|
|
|
* @param bool $returnRoot Indicates whether the return value should be a css string a root node |
303
|
|
|
* @return Less_Tree_Ruleset|Less_Parser |
304
|
|
|
*/ |
305
|
|
|
public function parseFile( $filename, $uri_root = '', $returnRoot = false){ |
306
|
|
|
|
307
|
|
|
if( !file_exists($filename) ){ |
308
|
|
|
$this->Error(sprintf('File `%s` not found.', $filename)); |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
|
312
|
|
|
// fix uri_root? |
313
|
|
|
// Instead of The mixture of file path for the first argument and directory path for the second argument has bee |
314
|
|
|
if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){ |
315
|
|
|
$uri_root = dirname($uri_root); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
|
319
|
|
|
$previousFileInfo = $this->env->currentFileInfo; |
320
|
|
|
$filename = self::WinPath($filename); |
321
|
|
|
$uri_root = self::WinPath($uri_root); |
322
|
|
|
$this->SetFileInfo($filename, $uri_root); |
323
|
|
|
|
324
|
|
|
self::AddParsedFile($filename); |
325
|
|
|
|
326
|
|
|
if( $returnRoot ){ |
327
|
|
|
$rules = $this->GetRules( $filename ); |
328
|
|
|
$return = new Less_Tree_Ruleset(array(), $rules ); |
329
|
|
|
}else{ |
330
|
|
|
$this->_parse( $filename ); |
331
|
|
|
$return = $this; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
if( $previousFileInfo ){ |
335
|
|
|
$this->env->currentFileInfo = $previousFileInfo; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
return $return; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Allows a user to set variables values |
344
|
|
|
* @param array $vars |
345
|
|
|
* @return Less_Parser |
346
|
|
|
*/ |
347
|
|
|
public function ModifyVars( $vars ){ |
348
|
|
|
|
349
|
|
|
$this->input = Less_Parser::serializeVars( $vars ); |
350
|
|
|
$this->_parse(); |
351
|
|
|
|
352
|
|
|
return $this; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* @param string $filename |
358
|
|
|
*/ |
359
|
|
|
public function SetFileInfo( $filename, $uri_root = ''){ |
360
|
|
|
|
361
|
|
|
$filename = Less_Environment::normalizePath($filename); |
362
|
|
|
$dirname = preg_replace('/[^\/\\\\]*$/','',$filename); |
363
|
|
|
|
364
|
|
|
if( !empty($uri_root) ){ |
365
|
|
|
$uri_root = rtrim($uri_root,'/').'/'; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
$currentFileInfo = array(); |
369
|
|
|
|
370
|
|
|
//entry info |
371
|
|
|
if( isset($this->env->currentFileInfo) ){ |
372
|
|
|
$currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath']; |
373
|
|
|
$currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri']; |
374
|
|
|
$currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath']; |
375
|
|
|
|
376
|
|
|
}else{ |
377
|
|
|
$currentFileInfo['entryPath'] = $dirname; |
378
|
|
|
$currentFileInfo['entryUri'] = $uri_root; |
379
|
|
|
$currentFileInfo['rootpath'] = $dirname; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
$currentFileInfo['currentDirectory'] = $dirname; |
383
|
|
|
$currentFileInfo['currentUri'] = $uri_root.basename($filename); |
384
|
|
|
$currentFileInfo['filename'] = $filename; |
385
|
|
|
$currentFileInfo['uri_root'] = $uri_root; |
386
|
|
|
|
387
|
|
|
|
388
|
|
|
//inherit reference |
389
|
|
|
if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){ |
390
|
|
|
$currentFileInfo['reference'] = true; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
$this->env->currentFileInfo = $currentFileInfo; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* @deprecated 1.5.1.2 |
399
|
|
|
* |
400
|
|
|
*/ |
401
|
|
|
public function SetCacheDir( $dir ){ |
402
|
|
|
|
403
|
|
|
if( !file_exists($dir) ){ |
404
|
|
|
if( mkdir($dir) ){ |
405
|
|
|
return true; |
406
|
|
|
} |
407
|
|
|
throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir); |
408
|
|
|
|
409
|
|
|
}elseif( !is_dir($dir) ){ |
410
|
|
|
throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir); |
411
|
|
|
|
412
|
|
|
}elseif( !is_writable($dir) ){ |
413
|
|
|
throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir); |
414
|
|
|
|
415
|
|
|
}else{ |
416
|
|
|
$dir = self::WinPath($dir); |
417
|
|
|
Less_Cache::$cache_dir = rtrim($dir,'/').'/'; |
|
|
|
|
418
|
|
|
return true; |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
|
423
|
|
|
/** |
424
|
|
|
* Set a list of directories or callbacks the parser should use for determining import paths |
425
|
|
|
* |
426
|
|
|
* @param array $dirs |
427
|
|
|
*/ |
428
|
|
|
public function SetImportDirs( $dirs ){ |
429
|
|
|
Less_Parser::$options['import_dirs'] = array(); |
430
|
|
|
|
431
|
|
|
foreach($dirs as $path => $uri_root){ |
432
|
|
|
|
433
|
|
|
$path = self::WinPath($path); |
434
|
|
|
if( !empty($path) ){ |
435
|
|
|
$path = rtrim($path,'/').'/'; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
if ( !is_callable($uri_root) ){ |
439
|
|
|
$uri_root = self::WinPath($uri_root); |
440
|
|
|
if( !empty($uri_root) ){ |
441
|
|
|
$uri_root = rtrim($uri_root,'/').'/'; |
442
|
|
|
} |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
Less_Parser::$options['import_dirs'][$path] = $uri_root; |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* @param string $file_path |
451
|
|
|
*/ |
452
|
|
|
private function _parse( $file_path = null ){ |
453
|
|
|
if (ini_get("mbstring.func_overload")) { |
454
|
|
|
$mb_internal_encoding = ini_get("mbstring.internal_encoding"); |
455
|
|
|
@ini_set("mbstring.internal_encoding", "ascii"); |
|
|
|
|
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
$this->rules = array_merge($this->rules, $this->GetRules( $file_path )); |
459
|
|
|
|
460
|
|
|
//reset php settings |
461
|
|
|
if (isset($mb_internal_encoding)) { |
462
|
|
|
@ini_set("mbstring.internal_encoding", $mb_internal_encoding); |
|
|
|
|
463
|
|
|
} |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
|
467
|
|
|
/** |
468
|
|
|
* Return the results of parsePrimary for $file_path |
469
|
|
|
* Use cache and save cached results if possible |
470
|
|
|
* |
471
|
|
|
* @param string|null $file_path |
472
|
|
|
*/ |
473
|
|
|
private function GetRules( $file_path ){ |
474
|
|
|
|
475
|
|
|
$this->SetInput($file_path); |
476
|
|
|
|
477
|
|
|
$cache_file = $this->CacheFile( $file_path ); |
478
|
|
|
if( $cache_file ){ |
|
|
|
|
479
|
|
|
if( Less_Parser::$options['cache_method'] == 'callback' ){ |
480
|
|
|
if( is_callable(Less_Parser::$options['cache_callback_get']) ){ |
481
|
|
|
$cache = call_user_func_array( |
482
|
|
|
Less_Parser::$options['cache_callback_get'], |
483
|
|
|
array($this, $file_path, $cache_file) |
484
|
|
|
); |
485
|
|
|
|
486
|
|
|
if( $cache ){ |
487
|
|
|
$this->UnsetInput(); |
488
|
|
|
return $cache; |
489
|
|
|
} |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
}elseif( file_exists($cache_file) ){ |
493
|
|
|
switch(Less_Parser::$options['cache_method']){ |
494
|
|
|
|
495
|
|
|
// Using serialize |
496
|
|
|
// Faster but uses more memory |
497
|
|
|
case 'serialize': |
498
|
|
|
$cache = unserialize(file_get_contents($cache_file)); |
499
|
|
|
if( $cache ){ |
500
|
|
|
touch($cache_file); |
501
|
|
|
$this->UnsetInput(); |
502
|
|
|
return $cache; |
503
|
|
|
} |
504
|
|
|
break; |
505
|
|
|
|
506
|
|
|
|
507
|
|
|
// Using generated php code |
508
|
|
|
case 'var_export': |
509
|
|
|
case 'php': |
510
|
|
|
$this->UnsetInput(); |
511
|
|
|
return include($cache_file); |
512
|
|
|
} |
513
|
|
|
} |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
$rules = $this->parsePrimary(); |
517
|
|
|
|
518
|
|
|
if( $this->pos < $this->input_len ){ |
519
|
|
|
throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo); |
|
|
|
|
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
$this->UnsetInput(); |
523
|
|
|
|
524
|
|
|
|
525
|
|
|
//save the cache |
526
|
|
|
if( $cache_file ){ |
|
|
|
|
527
|
|
|
if( Less_Parser::$options['cache_method'] == 'callback' ){ |
528
|
|
|
if( is_callable(Less_Parser::$options['cache_callback_set']) ){ |
529
|
|
|
call_user_func_array( |
530
|
|
|
Less_Parser::$options['cache_callback_set'], |
531
|
|
|
array($this, $file_path, $cache_file, $rules) |
532
|
|
|
); |
533
|
|
|
} |
534
|
|
|
|
535
|
|
|
}else{ |
536
|
|
|
//msg('write cache file'); |
537
|
|
|
switch(Less_Parser::$options['cache_method']){ |
538
|
|
|
case 'serialize': |
539
|
|
|
file_put_contents( $cache_file, serialize($rules) ); |
540
|
|
|
break; |
541
|
|
|
case 'php': |
542
|
|
|
file_put_contents( $cache_file, '<?php return '.self::ArgString($rules).'; ?>' ); |
543
|
|
|
break; |
544
|
|
|
case 'var_export': |
545
|
|
|
//Requires __set_state() |
546
|
|
|
file_put_contents( $cache_file, '<?php return '.var_export($rules,true).'; ?>' ); |
547
|
|
|
break; |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
Less_Cache::CleanCache(); |
551
|
|
|
} |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
return $rules; |
555
|
|
|
} |
556
|
|
|
|
557
|
|
|
|
558
|
|
|
/** |
559
|
|
|
* Set up the input buffer |
560
|
|
|
* |
561
|
|
|
*/ |
562
|
|
|
public function SetInput( $file_path ){ |
563
|
|
|
|
564
|
|
|
if( $file_path ){ |
565
|
|
|
$this->input = file_get_contents( $file_path ); |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
$this->pos = $this->furthest = 0; |
569
|
|
|
|
570
|
|
|
// Remove potential UTF Byte Order Mark |
571
|
|
|
$this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input); |
572
|
|
|
$this->input_len = strlen($this->input); |
573
|
|
|
|
574
|
|
|
|
575
|
|
|
if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){ |
|
|
|
|
576
|
|
|
$uri = $this->env->currentFileInfo['currentUri']; |
577
|
|
|
Less_Parser::$contentsMap[$uri] = $this->input; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
|
583
|
|
|
/** |
584
|
|
|
* Free up some memory |
585
|
|
|
* |
586
|
|
|
*/ |
587
|
|
|
public function UnsetInput(){ |
588
|
|
|
unset($this->input, $this->pos, $this->input_len, $this->furthest); |
589
|
|
|
$this->saveStack = array(); |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
|
593
|
|
|
public function CacheFile( $file_path ){ |
594
|
|
|
|
595
|
|
|
if( $file_path && $this->CacheEnabled() ){ |
596
|
|
|
|
597
|
|
|
$env = get_object_vars($this->env); |
598
|
|
|
unset($env['frames']); |
599
|
|
|
|
600
|
|
|
$parts = array(); |
601
|
|
|
$parts[] = $file_path; |
602
|
|
|
$parts[] = filesize( $file_path ); |
603
|
|
|
$parts[] = filemtime( $file_path ); |
604
|
|
|
$parts[] = $env; |
605
|
|
|
$parts[] = Less_Version::cache_version; |
606
|
|
|
$parts[] = Less_Parser::$options['cache_method']; |
607
|
|
|
return Less_Cache::$cache_dir.'lessphp_'.base_convert( sha1(json_encode($parts) ), 16, 36).'.lesscache'; |
608
|
|
|
} |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
|
612
|
|
|
static function AddParsedFile($file){ |
|
|
|
|
613
|
|
|
self::$imports[] = $file; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
static function AllParsedFiles(){ |
|
|
|
|
617
|
|
|
return self::$imports; |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
/** |
621
|
|
|
* @param string $file |
622
|
|
|
*/ |
623
|
|
|
static function FileParsed($file){ |
|
|
|
|
624
|
|
|
return in_array($file,self::$imports); |
625
|
|
|
} |
626
|
|
|
|
627
|
|
|
|
628
|
|
|
function save() { |
|
|
|
|
629
|
|
|
$this->saveStack[] = $this->pos; |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
private function restore() { |
633
|
|
|
$this->pos = array_pop($this->saveStack); |
634
|
|
|
} |
635
|
|
|
|
636
|
|
|
private function forget(){ |
637
|
|
|
array_pop($this->saveStack); |
638
|
|
|
} |
639
|
|
|
|
640
|
|
|
|
641
|
|
|
private function isWhitespace($offset = 0) { |
642
|
|
|
return preg_match('/\s/',$this->input[ $this->pos + $offset]); |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
/** |
646
|
|
|
* Parse from a token, regexp or string, and move forward if match |
647
|
|
|
* |
648
|
|
|
* @param array $toks |
649
|
|
|
* @return array |
650
|
|
|
*/ |
651
|
|
|
private function match($toks){ |
652
|
|
|
|
653
|
|
|
// The match is confirmed, add the match length to `this::pos`, |
654
|
|
|
// and consume any extra white-space characters (' ' || '\n') |
655
|
|
|
// which come after that. The reason for this is that LeSS's |
656
|
|
|
// grammar is mostly white-space insensitive. |
657
|
|
|
// |
658
|
|
|
|
659
|
|
|
foreach($toks as $tok){ |
660
|
|
|
|
661
|
|
|
$char = $tok[0]; |
662
|
|
|
|
663
|
|
|
if( $char === '/' ){ |
664
|
|
|
$match = $this->MatchReg($tok); |
665
|
|
|
|
666
|
|
|
if( $match ){ |
667
|
|
|
return count($match) === 1 ? $match[0] : $match; |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
}elseif( $char === '#' ){ |
671
|
|
|
$match = $this->MatchChar($tok[1]); |
672
|
|
|
|
673
|
|
|
}else{ |
674
|
|
|
// Non-terminal, match using a function call |
675
|
|
|
$match = $this->$tok(); |
676
|
|
|
|
677
|
|
|
} |
678
|
|
|
|
679
|
|
|
if( $match ){ |
680
|
|
|
return $match; |
681
|
|
|
} |
682
|
|
|
} |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
/** |
686
|
|
|
* @param string[] $toks |
687
|
|
|
* |
688
|
|
|
* @return string |
689
|
|
|
*/ |
690
|
|
|
private function MatchFuncs($toks){ |
691
|
|
|
|
692
|
|
|
if( $this->pos < $this->input_len ){ |
693
|
|
|
foreach($toks as $tok){ |
694
|
|
|
$match = $this->$tok(); |
695
|
|
|
if( $match ){ |
696
|
|
|
return $match; |
697
|
|
|
} |
698
|
|
|
} |
699
|
|
|
} |
700
|
|
|
|
701
|
|
|
} |
702
|
|
|
|
703
|
|
|
// Match a single character in the input, |
704
|
|
|
private function MatchChar($tok){ |
705
|
|
|
if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){ |
706
|
|
|
$this->skipWhitespace(1); |
707
|
|
|
return $tok; |
708
|
|
|
} |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
// Match a regexp from the current start point |
712
|
|
|
private function MatchReg($tok){ |
713
|
|
|
|
714
|
|
|
if( preg_match($tok, $this->input, $match, 0, $this->pos) ){ |
715
|
|
|
$this->skipWhitespace(strlen($match[0])); |
716
|
|
|
return $match; |
717
|
|
|
} |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
|
721
|
|
|
/** |
722
|
|
|
* Same as match(), but don't change the state of the parser, |
723
|
|
|
* just return the match. |
724
|
|
|
* |
725
|
|
|
* @param string $tok |
726
|
|
|
* @return integer |
727
|
|
|
*/ |
728
|
|
|
public function PeekReg($tok){ |
729
|
|
|
return preg_match($tok, $this->input, $match, 0, $this->pos); |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
/** |
733
|
|
|
* @param string $tok |
734
|
|
|
*/ |
735
|
|
|
public function PeekChar($tok){ |
736
|
|
|
//return ($this->input[$this->pos] === $tok ); |
|
|
|
|
737
|
|
|
return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok ); |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
|
741
|
|
|
/** |
742
|
|
|
* @param integer $length |
743
|
|
|
*/ |
744
|
|
|
public function skipWhitespace($length){ |
745
|
|
|
|
746
|
|
|
$this->pos += $length; |
747
|
|
|
|
748
|
|
|
for(; $this->pos < $this->input_len; $this->pos++ ){ |
749
|
|
|
$c = $this->input[$this->pos]; |
750
|
|
|
|
751
|
|
|
if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){ |
752
|
|
|
break; |
753
|
|
|
} |
754
|
|
|
} |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
|
758
|
|
|
/** |
759
|
|
|
* @param string $tok |
760
|
|
|
* @param string|null $msg |
761
|
|
|
*/ |
762
|
|
View Code Duplication |
public function expect($tok, $msg = NULL) { |
|
|
|
|
763
|
|
|
$result = $this->match( array($tok) ); |
764
|
|
|
if (!$result) { |
|
|
|
|
765
|
|
|
$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg ); |
766
|
|
|
} else { |
767
|
|
|
return $result; |
768
|
|
|
} |
769
|
|
|
} |
770
|
|
|
|
771
|
|
|
/** |
772
|
|
|
* @param string $tok |
773
|
|
|
*/ |
774
|
|
View Code Duplication |
public function expectChar($tok, $msg = null ){ |
|
|
|
|
775
|
|
|
$result = $this->MatchChar($tok); |
776
|
|
|
if( !$result ){ |
777
|
|
|
$this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg ); |
778
|
|
|
}else{ |
779
|
|
|
return $result; |
780
|
|
|
} |
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
// |
784
|
|
|
// Here in, the parsing rules/functions |
785
|
|
|
// |
786
|
|
|
// The basic structure of the syntax tree generated is as follows: |
787
|
|
|
// |
788
|
|
|
// Ruleset -> Rule -> Value -> Expression -> Entity |
789
|
|
|
// |
790
|
|
|
// Here's some LESS code: |
791
|
|
|
// |
792
|
|
|
// .class { |
793
|
|
|
// color: #fff; |
794
|
|
|
// border: 1px solid #000; |
|
|
|
|
795
|
|
|
// width: @w + 4px; |
796
|
|
|
// > .child {...} |
797
|
|
|
// } |
798
|
|
|
// |
799
|
|
|
// And here's what the parse tree might look like: |
800
|
|
|
// |
801
|
|
|
// Ruleset (Selector '.class', [ |
|
|
|
|
802
|
|
|
// Rule ("color", Value ([Expression [Color #fff]])) |
|
|
|
|
803
|
|
|
// Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) |
|
|
|
|
804
|
|
|
// Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) |
|
|
|
|
805
|
|
|
// Ruleset (Selector [Element '>', '.child'], [...]) |
|
|
|
|
806
|
|
|
// ]) |
807
|
|
|
// |
808
|
|
|
// In general, most rules will try to parse a token with the `$()` function, and if the return |
809
|
|
|
// value is truly, will return a new node, of the relevant type. Sometimes, we need to check |
810
|
|
|
// first, before parsing, that's when we use `peek()`. |
811
|
|
|
// |
812
|
|
|
|
813
|
|
|
// |
814
|
|
|
// The `primary` rule is the *entry* and *exit* point of the parser. |
815
|
|
|
// The rules here can appear at any level of the parse tree. |
816
|
|
|
// |
817
|
|
|
// The recursive nature of the grammar is an interplay between the `block` |
818
|
|
|
// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, |
819
|
|
|
// as represented by this simplified grammar: |
820
|
|
|
// |
821
|
|
|
// primary → (ruleset | rule)+ |
822
|
|
|
// ruleset → selector+ block |
823
|
|
|
// block → '{' primary '}' |
824
|
|
|
// |
825
|
|
|
// Only at one point is the primary rule not called from the |
826
|
|
|
// block rule: at the root level. |
827
|
|
|
// |
828
|
|
|
private function parsePrimary(){ |
829
|
|
|
$root = array(); |
830
|
|
|
|
831
|
|
|
while( true ){ |
832
|
|
|
|
833
|
|
|
if( $this->pos >= $this->input_len ){ |
834
|
|
|
break; |
835
|
|
|
} |
836
|
|
|
|
837
|
|
|
$node = $this->parseExtend(true); |
838
|
|
|
if( $node ){ |
|
|
|
|
839
|
|
|
$root = array_merge($root,$node); |
840
|
|
|
continue; |
841
|
|
|
} |
842
|
|
|
|
843
|
|
|
//$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective')); |
|
|
|
|
844
|
|
|
$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective')); |
845
|
|
|
|
846
|
|
|
if( $node ){ |
847
|
|
|
$root[] = $node; |
848
|
|
|
}elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){ |
849
|
|
|
break; |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
if( $this->PeekChar('}') ){ |
853
|
|
|
break; |
854
|
|
|
} |
855
|
|
|
} |
856
|
|
|
|
857
|
|
|
return $root; |
858
|
|
|
} |
859
|
|
|
|
860
|
|
|
|
861
|
|
|
|
862
|
|
|
// We create a Comment node for CSS comments `/* */`, |
863
|
|
|
// but keep the LeSS comments `//` silent, by just skipping |
864
|
|
|
// over them. |
865
|
|
|
private function parseComment(){ |
866
|
|
|
|
867
|
|
|
if( $this->input[$this->pos] !== '/' ){ |
868
|
|
|
return; |
869
|
|
|
} |
870
|
|
|
|
871
|
|
|
if( $this->input[$this->pos+1] === '/' ){ |
872
|
|
|
$match = $this->MatchReg('/\\G\/\/.*/'); |
873
|
|
|
return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo)); |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
//$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/'); |
|
|
|
|
877
|
|
|
$comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors |
878
|
|
|
if( $comment ){ |
879
|
|
|
return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo)); |
880
|
|
|
} |
881
|
|
|
} |
882
|
|
|
|
883
|
|
|
private function parseComments(){ |
884
|
|
|
$comments = array(); |
885
|
|
|
|
886
|
|
|
while( $this->pos < $this->input_len ){ |
887
|
|
|
$comment = $this->parseComment(); |
888
|
|
|
if( !$comment ){ |
889
|
|
|
break; |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
$comments[] = $comment; |
893
|
|
|
} |
894
|
|
|
|
895
|
|
|
return $comments; |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
|
899
|
|
|
|
900
|
|
|
// |
901
|
|
|
// A string, which supports escaping " and ' |
902
|
|
|
// |
903
|
|
|
// "milky way" 'he\'s the one!' |
904
|
|
|
// |
905
|
|
|
private function parseEntitiesQuoted() { |
906
|
|
|
$j = $this->pos; |
907
|
|
|
$e = false; |
908
|
|
|
$index = $this->pos; |
909
|
|
|
|
910
|
|
|
if( $this->input[$this->pos] === '~' ){ |
911
|
|
|
$j++; |
912
|
|
|
$e = true; // Escaped strings |
913
|
|
|
} |
914
|
|
|
|
915
|
|
|
if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){ |
916
|
|
|
return; |
917
|
|
|
} |
918
|
|
|
|
919
|
|
|
if ($e) { |
920
|
|
|
$this->MatchChar('~'); |
921
|
|
|
} |
922
|
|
|
|
923
|
|
|
// Fix for #124: match escaped newlines |
924
|
|
|
//$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/'); |
925
|
|
|
$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"|\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/'); |
926
|
|
|
|
927
|
|
|
if( $str ){ |
928
|
|
|
$result = $str[0][0] == '"' ? $str[1] : $str[2]; |
929
|
|
|
return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) ); |
930
|
|
|
} |
931
|
|
|
return; |
932
|
|
|
} |
933
|
|
|
|
934
|
|
|
|
935
|
|
|
// |
936
|
|
|
// A catch-all word, such as: |
937
|
|
|
// |
938
|
|
|
// black border-collapse |
939
|
|
|
// |
940
|
|
|
private function parseEntitiesKeyword(){ |
941
|
|
|
|
942
|
|
|
//$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/'); |
|
|
|
|
943
|
|
|
$k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/'); |
944
|
|
|
if( $k ){ |
945
|
|
|
$k = $k[0]; |
946
|
|
|
$color = $this->fromKeyword($k); |
947
|
|
|
if( $color ){ |
948
|
|
|
return $color; |
949
|
|
|
} |
950
|
|
|
return $this->NewObj1('Less_Tree_Keyword',$k); |
951
|
|
|
} |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
// duplicate of Less_Tree_Color::FromKeyword |
955
|
|
|
private function FromKeyword( $keyword ){ |
956
|
|
|
$keyword = strtolower($keyword); |
957
|
|
|
|
958
|
|
|
if( Less_Colors::hasOwnProperty($keyword) ){ |
959
|
|
|
// detect named color |
960
|
|
|
return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1)); |
961
|
|
|
} |
962
|
|
|
|
963
|
|
|
if( $keyword === 'transparent' ){ |
964
|
|
|
return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true)); |
965
|
|
|
} |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
// |
969
|
|
|
// A function call |
970
|
|
|
// |
971
|
|
|
// rgb(255, 0, 255) |
|
|
|
|
972
|
|
|
// |
973
|
|
|
// We also try to catch IE's `alpha()`, but let the `alpha` parser |
974
|
|
|
// deal with the details. |
975
|
|
|
// |
976
|
|
|
// The arguments are parsed with the `entities.arguments` parser. |
977
|
|
|
// |
978
|
|
|
private function parseEntitiesCall(){ |
979
|
|
|
$index = $this->pos; |
980
|
|
|
|
981
|
|
|
if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){ |
982
|
|
|
return; |
983
|
|
|
} |
984
|
|
|
$name = $name[1]; |
985
|
|
|
$nameLC = strtolower($name); |
986
|
|
|
|
987
|
|
|
if ($nameLC === 'url') { |
988
|
|
|
return null; |
989
|
|
|
} |
990
|
|
|
|
991
|
|
|
$this->pos += strlen($name); |
992
|
|
|
|
993
|
|
|
if( $nameLC === 'alpha' ){ |
994
|
|
|
$alpha_ret = $this->parseAlpha(); |
995
|
|
|
if( $alpha_ret ){ |
996
|
|
|
return $alpha_ret; |
997
|
|
|
} |
998
|
|
|
} |
999
|
|
|
|
1000
|
|
|
$this->MatchChar('('); // Parse the '(' and consume whitespace. |
1001
|
|
|
|
1002
|
|
|
$args = $this->parseEntitiesArguments(); |
1003
|
|
|
|
1004
|
|
|
if( !$this->MatchChar(')') ){ |
1005
|
|
|
return; |
1006
|
|
|
} |
1007
|
|
|
|
1008
|
|
|
if ($name) { |
1009
|
|
|
return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) ); |
1010
|
|
|
} |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
|
|
/** |
1014
|
|
|
* Parse a list of arguments |
1015
|
|
|
* |
1016
|
|
|
* @return array |
1017
|
|
|
*/ |
1018
|
|
|
private function parseEntitiesArguments(){ |
1019
|
|
|
|
1020
|
|
|
$args = array(); |
1021
|
|
|
while( true ){ |
1022
|
|
|
$arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') ); |
1023
|
|
|
if( !$arg ){ |
1024
|
|
|
break; |
1025
|
|
|
} |
1026
|
|
|
|
1027
|
|
|
$args[] = $arg; |
1028
|
|
|
if( !$this->MatchChar(',') ){ |
1029
|
|
|
break; |
1030
|
|
|
} |
1031
|
|
|
} |
1032
|
|
|
return $args; |
1033
|
|
|
} |
1034
|
|
|
|
1035
|
|
|
private function parseEntitiesLiteral(){ |
1036
|
|
|
return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') ); |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
|
|
// Assignments are argument entities for calls. |
1040
|
|
|
// They are present in ie filter properties as shown below. |
1041
|
|
|
// |
1042
|
|
|
// filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) |
1043
|
|
|
// |
1044
|
|
|
private function parseEntitiesAssignment() { |
1045
|
|
|
|
1046
|
|
|
$key = $this->MatchReg('/\\G\w+(?=\s?=)/'); |
1047
|
|
|
if( !$key ){ |
1048
|
|
|
return; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
if( !$this->MatchChar('=') ){ |
1052
|
|
|
return; |
1053
|
|
|
} |
1054
|
|
|
|
1055
|
|
|
$value = $this->parseEntity(); |
1056
|
|
|
if( $value ){ |
1057
|
|
|
return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value)); |
1058
|
|
|
} |
1059
|
|
|
} |
1060
|
|
|
|
1061
|
|
|
// |
1062
|
|
|
// Parse url() tokens |
1063
|
|
|
// |
1064
|
|
|
// We use a specific rule for urls, because they don't really behave like |
1065
|
|
|
// standard function calls. The difference is that the argument doesn't have |
1066
|
|
|
// to be enclosed within a string, so it can't be parsed as an Expression. |
1067
|
|
|
// |
1068
|
|
|
private function parseEntitiesUrl(){ |
1069
|
|
|
|
1070
|
|
|
|
1071
|
|
|
if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){ |
1072
|
|
|
return; |
1073
|
|
|
} |
1074
|
|
|
|
1075
|
|
|
$value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') ); |
1076
|
|
|
if( !$value ){ |
|
|
|
|
1077
|
|
|
$value = ''; |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
|
1081
|
|
|
$this->expectChar(')'); |
1082
|
|
|
|
1083
|
|
|
|
1084
|
|
|
if( isset($value->value) || $value instanceof Less_Tree_Variable ){ |
1085
|
|
|
return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo)); |
1086
|
|
|
} |
1087
|
|
|
|
1088
|
|
|
return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) ); |
1089
|
|
|
} |
1090
|
|
|
|
1091
|
|
|
|
1092
|
|
|
// |
1093
|
|
|
// A Variable entity, such as `@fink`, in |
1094
|
|
|
// |
1095
|
|
|
// width: @fink + 2px |
1096
|
|
|
// |
1097
|
|
|
// We use a different parser for variable definitions, |
1098
|
|
|
// see `parsers.variable`. |
1099
|
|
|
// |
1100
|
|
|
private function parseEntitiesVariable(){ |
1101
|
|
|
$index = $this->pos; |
1102
|
|
|
if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) { |
1103
|
|
|
return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo)); |
1104
|
|
|
} |
1105
|
|
|
} |
1106
|
|
|
|
1107
|
|
|
|
1108
|
|
|
// A variable entity useing the protective {} e.g. @{var} |
1109
|
|
|
private function parseEntitiesVariableCurly() { |
1110
|
|
|
$index = $this->pos; |
1111
|
|
|
|
1112
|
|
|
if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){ |
1113
|
|
|
return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo)); |
1114
|
|
|
} |
1115
|
|
|
} |
1116
|
|
|
|
1117
|
|
|
// |
1118
|
|
|
// A Hexadecimal color |
1119
|
|
|
// |
1120
|
|
|
// #4F3C2F |
1121
|
|
|
// |
1122
|
|
|
// `rgb` and `hsl` colors are parsed through the `entities.call` parser. |
1123
|
|
|
// |
1124
|
|
|
private function parseEntitiesColor(){ |
1125
|
|
|
if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) { |
1126
|
|
|
return $this->NewObj1('Less_Tree_Color',$rgb[1]); |
1127
|
|
|
} |
1128
|
|
|
} |
1129
|
|
|
|
1130
|
|
|
// |
1131
|
|
|
// A Dimension, that is, a number and a unit |
1132
|
|
|
// |
1133
|
|
|
// 0.5em 95% |
1134
|
|
|
// |
1135
|
|
|
private function parseEntitiesDimension(){ |
1136
|
|
|
|
1137
|
|
|
$c = @ord($this->input[$this->pos]); |
1138
|
|
|
|
1139
|
|
|
//Is the first char of the dimension 0-9, '.', '+' or '-' |
1140
|
|
|
if (($c > 57 || $c < 43) || $c === 47 || $c == 44){ |
1141
|
|
|
return; |
1142
|
|
|
} |
1143
|
|
|
|
1144
|
|
|
$value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/'); |
1145
|
|
|
if( $value ){ |
1146
|
|
|
|
1147
|
|
|
if( isset($value[2]) ){ |
1148
|
|
|
return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2])); |
1149
|
|
|
} |
1150
|
|
|
return $this->NewObj1('Less_Tree_Dimension',$value[1]); |
1151
|
|
|
} |
1152
|
|
|
} |
1153
|
|
|
|
1154
|
|
|
|
1155
|
|
|
// |
1156
|
|
|
// A unicode descriptor, as is used in unicode-range |
1157
|
|
|
// |
1158
|
|
|
// U+0?? or U+00A1-00A9 |
|
|
|
|
1159
|
|
|
// |
1160
|
|
|
function parseUnicodeDescriptor() { |
|
|
|
|
1161
|
|
|
$ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/'); |
1162
|
|
|
if( $ud ){ |
1163
|
|
|
return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]); |
1164
|
|
|
} |
1165
|
|
|
} |
1166
|
|
|
|
1167
|
|
|
|
1168
|
|
|
// |
1169
|
|
|
// JavaScript code to be evaluated |
1170
|
|
|
// |
1171
|
|
|
// `window.location.href` |
1172
|
|
|
// |
1173
|
|
|
private function parseEntitiesJavascript(){ |
1174
|
|
|
$e = false; |
1175
|
|
|
$j = $this->pos; |
1176
|
|
|
if( $this->input[$j] === '~' ){ |
1177
|
|
|
$j++; |
1178
|
|
|
$e = true; |
1179
|
|
|
} |
1180
|
|
|
if( $this->input[$j] !== '`' ){ |
1181
|
|
|
return; |
1182
|
|
|
} |
1183
|
|
|
if( $e ){ |
1184
|
|
|
$this->MatchChar('~'); |
1185
|
|
|
} |
1186
|
|
|
$str = $this->MatchReg('/\\G`([^`]*)`/'); |
1187
|
|
|
if( $str ){ |
1188
|
|
|
return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e)); |
1189
|
|
|
} |
1190
|
|
|
} |
1191
|
|
|
|
1192
|
|
|
|
1193
|
|
|
// |
1194
|
|
|
// The variable part of a variable definition. Used in the `rule` parser |
1195
|
|
|
// |
1196
|
|
|
// @fink: |
1197
|
|
|
// |
1198
|
|
|
private function parseVariable(){ |
1199
|
|
|
if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) { |
1200
|
|
|
return $name[1]; |
1201
|
|
|
} |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
|
|
|
1205
|
|
|
// |
1206
|
|
|
// The variable part of a variable definition. Used in the `rule` parser |
1207
|
|
|
// |
1208
|
|
|
// @fink(); |
1209
|
|
|
// |
1210
|
|
|
private function parseRulesetCall(){ |
1211
|
|
|
|
1212
|
|
|
if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){ |
1213
|
|
|
return $this->NewObj1('Less_Tree_RulesetCall', $name[1] ); |
1214
|
|
|
} |
1215
|
|
|
} |
1216
|
|
|
|
1217
|
|
|
|
1218
|
|
|
// |
1219
|
|
|
// extend syntax - used to extend selectors |
1220
|
|
|
// |
1221
|
|
|
function parseExtend($isRule = false){ |
|
|
|
|
1222
|
|
|
|
1223
|
|
|
$index = $this->pos; |
1224
|
|
|
$extendList = array(); |
1225
|
|
|
|
1226
|
|
|
|
1227
|
|
|
if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; } |
1228
|
|
|
|
1229
|
|
|
do{ |
1230
|
|
|
$option = null; |
1231
|
|
|
$elements = array(); |
1232
|
|
|
while( true ){ |
1233
|
|
|
$option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/'); |
1234
|
|
|
if( $option ){ break; } |
|
|
|
|
1235
|
|
|
$e = $this->parseElement(); |
1236
|
|
|
if( !$e ){ break; } |
1237
|
|
|
$elements[] = $e; |
1238
|
|
|
} |
1239
|
|
|
|
1240
|
|
|
if( $option ){ |
1241
|
|
|
$option = $option[1]; |
1242
|
|
|
} |
1243
|
|
|
|
1244
|
|
|
$extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index )); |
1245
|
|
|
|
1246
|
|
|
}while( $this->MatchChar(",") ); |
1247
|
|
|
|
1248
|
|
|
$this->expect('/\\G\)/'); |
1249
|
|
|
|
1250
|
|
|
if( $isRule ){ |
1251
|
|
|
$this->expect('/\\G;/'); |
1252
|
|
|
} |
1253
|
|
|
|
1254
|
|
|
return $extendList; |
1255
|
|
|
} |
1256
|
|
|
|
1257
|
|
|
|
1258
|
|
|
// |
1259
|
|
|
// A Mixin call, with an optional argument list |
1260
|
|
|
// |
1261
|
|
|
// #mixins > .square(#fff); |
1262
|
|
|
// .rounded(4px, black); |
|
|
|
|
1263
|
|
|
// .button; |
1264
|
|
|
// |
1265
|
|
|
// The `while` loop is there because mixins can be |
1266
|
|
|
// namespaced, but we only support the child and descendant |
1267
|
|
|
// selector for now. |
1268
|
|
|
// |
1269
|
|
|
private function parseMixinCall(){ |
1270
|
|
|
|
1271
|
|
|
$char = $this->input[$this->pos]; |
1272
|
|
|
if( $char !== '.' && $char !== '#' ){ |
1273
|
|
|
return; |
1274
|
|
|
} |
1275
|
|
|
|
1276
|
|
|
$index = $this->pos; |
1277
|
|
|
$this->save(); // stop us absorbing part of an invalid selector |
1278
|
|
|
|
1279
|
|
|
$elements = $this->parseMixinCallElements(); |
1280
|
|
|
|
1281
|
|
|
if( $elements ){ |
|
|
|
|
1282
|
|
|
|
1283
|
|
|
if( $this->MatchChar('(') ){ |
1284
|
|
|
$returned = $this->parseMixinArgs(true); |
1285
|
|
|
$args = $returned['args']; |
1286
|
|
|
$this->expectChar(')'); |
1287
|
|
|
}else{ |
1288
|
|
|
$args = array(); |
1289
|
|
|
} |
1290
|
|
|
|
1291
|
|
|
$important = $this->parseImportant(); |
1292
|
|
|
|
1293
|
|
|
if( $this->parseEnd() ){ |
1294
|
|
|
$this->forget(); |
1295
|
|
|
return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important)); |
1296
|
|
|
} |
1297
|
|
|
} |
1298
|
|
|
|
1299
|
|
|
$this->restore(); |
1300
|
|
|
} |
1301
|
|
|
|
1302
|
|
|
|
1303
|
|
|
private function parseMixinCallElements(){ |
1304
|
|
|
$elements = array(); |
1305
|
|
|
$c = null; |
1306
|
|
|
|
1307
|
|
|
while( true ){ |
1308
|
|
|
$elemIndex = $this->pos; |
1309
|
|
|
$e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/'); |
1310
|
|
|
if( !$e ){ |
|
|
|
|
1311
|
|
|
break; |
1312
|
|
|
} |
1313
|
|
|
$elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo)); |
1314
|
|
|
$c = $this->MatchChar('>'); |
1315
|
|
|
} |
1316
|
|
|
|
1317
|
|
|
return $elements; |
1318
|
|
|
} |
1319
|
|
|
|
1320
|
|
|
|
1321
|
|
|
|
1322
|
|
|
/** |
1323
|
|
|
* @param boolean $isCall |
1324
|
|
|
*/ |
1325
|
|
|
private function parseMixinArgs( $isCall ){ |
1326
|
|
|
$expressions = array(); |
1327
|
|
|
$argsSemiColon = array(); |
1328
|
|
|
$isSemiColonSeperated = null; |
1329
|
|
|
$argsComma = array(); |
1330
|
|
|
$expressionContainsNamed = null; |
1331
|
|
|
$name = null; |
1332
|
|
|
$returner = array('args'=>array(), 'variadic'=> false); |
1333
|
|
|
|
1334
|
|
|
$this->save(); |
1335
|
|
|
|
1336
|
|
|
while( true ){ |
1337
|
|
|
if( $isCall ){ |
1338
|
|
|
$arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) ); |
1339
|
|
|
} else { |
1340
|
|
|
$this->parseComments(); |
1341
|
|
|
if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){ |
1342
|
|
|
$returner['variadic'] = true; |
1343
|
|
|
if( $this->MatchChar(";") && !$isSemiColonSeperated ){ |
|
|
|
|
1344
|
|
|
$isSemiColonSeperated = true; |
1345
|
|
|
} |
1346
|
|
|
|
1347
|
|
|
if( $isSemiColonSeperated ){ |
1348
|
|
|
$argsSemiColon[] = array('variadic'=>true); |
1349
|
|
|
}else{ |
1350
|
|
|
$argsComma[] = array('variadic'=>true); |
1351
|
|
|
} |
1352
|
|
|
break; |
1353
|
|
|
} |
1354
|
|
|
$arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') ); |
1355
|
|
|
} |
1356
|
|
|
|
1357
|
|
|
if( !$arg ){ |
1358
|
|
|
break; |
1359
|
|
|
} |
1360
|
|
|
|
1361
|
|
|
|
1362
|
|
|
$nameLoop = null; |
1363
|
|
|
if( $arg instanceof Less_Tree_Expression ){ |
1364
|
|
|
$arg->throwAwayComments(); |
1365
|
|
|
} |
1366
|
|
|
$value = $arg; |
1367
|
|
|
$val = null; |
1368
|
|
|
|
1369
|
|
|
if( $isCall ){ |
1370
|
|
|
// Variable |
1371
|
|
|
if( property_exists($arg,'value') && count($arg->value) == 1 ){ |
1372
|
|
|
$val = $arg->value[0]; |
1373
|
|
|
} |
1374
|
|
|
} else { |
1375
|
|
|
$val = $arg; |
1376
|
|
|
} |
1377
|
|
|
|
1378
|
|
|
|
1379
|
|
|
if( $val instanceof Less_Tree_Variable ){ |
1380
|
|
|
|
1381
|
|
|
if( $this->MatchChar(':') ){ |
1382
|
|
|
if( $expressions ){ |
|
|
|
|
1383
|
|
|
if( $isSemiColonSeperated ){ |
1384
|
|
|
$this->Error('Cannot mix ; and , as delimiter types'); |
1385
|
|
|
} |
1386
|
|
|
$expressionContainsNamed = true; |
1387
|
|
|
} |
1388
|
|
|
|
1389
|
|
|
// we do not support setting a ruleset as a default variable - it doesn't make sense |
1390
|
|
|
// However if we do want to add it, there is nothing blocking it, just don't error |
1391
|
|
|
// and remove isCall dependency below |
1392
|
|
|
$value = null; |
1393
|
|
|
if( $isCall ){ |
1394
|
|
|
$value = $this->parseDetachedRuleset(); |
1395
|
|
|
} |
1396
|
|
|
if( !$value ){ |
1397
|
|
|
$value = $this->parseExpression(); |
1398
|
|
|
} |
1399
|
|
|
|
1400
|
|
|
if( !$value ){ |
1401
|
|
|
if( $isCall ){ |
1402
|
|
|
$this->Error('could not understand value for named argument'); |
1403
|
|
|
} else { |
1404
|
|
|
$this->restore(); |
1405
|
|
|
$returner['args'] = array(); |
1406
|
|
|
return $returner; |
1407
|
|
|
} |
1408
|
|
|
} |
1409
|
|
|
|
1410
|
|
|
$nameLoop = ($name = $val->name); |
1411
|
|
|
}elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){ |
1412
|
|
|
$returner['variadic'] = true; |
1413
|
|
|
if( $this->MatchChar(";") && !$isSemiColonSeperated ){ |
|
|
|
|
1414
|
|
|
$isSemiColonSeperated = true; |
1415
|
|
|
} |
1416
|
|
|
if( $isSemiColonSeperated ){ |
1417
|
|
|
$argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true); |
1418
|
|
|
}else{ |
1419
|
|
|
$argsComma[] = array('name'=> $arg->name, 'variadic' => true); |
1420
|
|
|
} |
1421
|
|
|
break; |
1422
|
|
|
}elseif( !$isCall ){ |
1423
|
|
|
$name = $nameLoop = $val->name; |
1424
|
|
|
$value = null; |
1425
|
|
|
} |
1426
|
|
|
} |
1427
|
|
|
|
1428
|
|
|
if( $value ){ |
1429
|
|
|
$expressions[] = $value; |
1430
|
|
|
} |
1431
|
|
|
|
1432
|
|
|
$argsComma[] = array('name'=>$nameLoop, 'value'=>$value ); |
1433
|
|
|
|
1434
|
|
|
if( $this->MatchChar(',') ){ |
1435
|
|
|
continue; |
1436
|
|
|
} |
1437
|
|
|
|
1438
|
|
|
if( $this->MatchChar(';') || $isSemiColonSeperated ){ |
1439
|
|
|
|
1440
|
|
|
if( $expressionContainsNamed ){ |
1441
|
|
|
$this->Error('Cannot mix ; and , as delimiter types'); |
1442
|
|
|
} |
1443
|
|
|
|
1444
|
|
|
$isSemiColonSeperated = true; |
1445
|
|
|
|
1446
|
|
|
if( count($expressions) > 1 ){ |
1447
|
|
|
$value = $this->NewObj1('Less_Tree_Value', $expressions); |
1448
|
|
|
} |
1449
|
|
|
$argsSemiColon[] = array('name'=>$name, 'value'=>$value ); |
1450
|
|
|
|
1451
|
|
|
$name = null; |
1452
|
|
|
$expressions = array(); |
1453
|
|
|
$expressionContainsNamed = false; |
1454
|
|
|
} |
1455
|
|
|
} |
1456
|
|
|
|
1457
|
|
|
$this->forget(); |
1458
|
|
|
$returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma); |
1459
|
|
|
return $returner; |
1460
|
|
|
} |
1461
|
|
|
|
1462
|
|
|
|
1463
|
|
|
|
1464
|
|
|
// |
1465
|
|
|
// A Mixin definition, with a list of parameters |
1466
|
|
|
// |
1467
|
|
|
// .rounded (@radius: 2px, @color) { |
|
|
|
|
1468
|
|
|
// ... |
1469
|
|
|
// } |
1470
|
|
|
// |
1471
|
|
|
// Until we have a finer grained state-machine, we have to |
1472
|
|
|
// do a look-ahead, to make sure we don't have a mixin call. |
1473
|
|
|
// See the `rule` function for more information. |
1474
|
|
|
// |
1475
|
|
|
// We start by matching `.rounded (`, and then proceed on to |
1476
|
|
|
// the argument list, which has optional default values. |
1477
|
|
|
// We store the parameters in `params`, with a `value` key, |
1478
|
|
|
// if there is a value, such as in the case of `@radius`. |
1479
|
|
|
// |
1480
|
|
|
// Once we've got our params list, and a closing `)`, we parse |
1481
|
|
|
// the `{...}` block. |
1482
|
|
|
// |
1483
|
|
|
private function parseMixinDefinition(){ |
1484
|
|
|
$cond = null; |
1485
|
|
|
|
1486
|
|
|
$char = $this->input[$this->pos]; |
1487
|
|
|
if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){ |
1488
|
|
|
return; |
1489
|
|
|
} |
1490
|
|
|
|
1491
|
|
|
$this->save(); |
1492
|
|
|
|
1493
|
|
|
$match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/'); |
1494
|
|
|
if( $match ){ |
1495
|
|
|
$name = $match[1]; |
1496
|
|
|
|
1497
|
|
|
$argInfo = $this->parseMixinArgs( false ); |
1498
|
|
|
$params = $argInfo['args']; |
1499
|
|
|
$variadic = $argInfo['variadic']; |
1500
|
|
|
|
1501
|
|
|
|
1502
|
|
|
// .mixincall("@{a}"); |
|
|
|
|
1503
|
|
|
// looks a bit like a mixin definition.. |
1504
|
|
|
// also |
1505
|
|
|
// .mixincall(@a: {rule: set;}); |
|
|
|
|
1506
|
|
|
// so we have to be nice and restore |
1507
|
|
|
if( !$this->MatchChar(')') ){ |
1508
|
|
|
$this->furthest = $this->pos; |
1509
|
|
|
$this->restore(); |
1510
|
|
|
return; |
1511
|
|
|
} |
1512
|
|
|
|
1513
|
|
|
|
1514
|
|
|
$this->parseComments(); |
1515
|
|
|
|
1516
|
|
|
if ($this->MatchReg('/\\Gwhen/')) { // Guard |
1517
|
|
|
$cond = $this->expect('parseConditions', 'Expected conditions'); |
1518
|
|
|
} |
1519
|
|
|
|
1520
|
|
|
$ruleset = $this->parseBlock(); |
1521
|
|
|
|
1522
|
|
|
if( is_array($ruleset) ){ |
1523
|
|
|
$this->forget(); |
1524
|
|
|
return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic)); |
1525
|
|
|
} |
1526
|
|
|
|
1527
|
|
|
$this->restore(); |
1528
|
|
|
}else{ |
1529
|
|
|
$this->forget(); |
1530
|
|
|
} |
1531
|
|
|
} |
1532
|
|
|
|
1533
|
|
|
// |
1534
|
|
|
// Entities are the smallest recognized token, |
1535
|
|
|
// and can be found inside a rule's value. |
1536
|
|
|
// |
1537
|
|
|
private function parseEntity(){ |
1538
|
|
|
|
1539
|
|
|
return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') ); |
1540
|
|
|
} |
1541
|
|
|
|
1542
|
|
|
// |
1543
|
|
|
// A Rule terminator. Note that we use `peek()` to check for '}', |
1544
|
|
|
// because the `block` rule will be expecting it, but we still need to make sure |
1545
|
|
|
// it's there, if ';' was ommitted. |
1546
|
|
|
// |
1547
|
|
|
private function parseEnd(){ |
1548
|
|
|
return $this->MatchChar(';') || $this->PeekChar('}'); |
1549
|
|
|
} |
1550
|
|
|
|
1551
|
|
|
// |
1552
|
|
|
// IE's alpha function |
1553
|
|
|
// |
1554
|
|
|
// alpha(opacity=88) |
|
|
|
|
1555
|
|
|
// |
1556
|
|
|
private function parseAlpha(){ |
1557
|
|
|
|
1558
|
|
|
if ( ! $this->MatchReg('/\\G\(opacity=/i')) { |
1559
|
|
|
return; |
1560
|
|
|
} |
1561
|
|
|
|
1562
|
|
|
$value = $this->MatchReg('/\\G[0-9]+/'); |
1563
|
|
|
if( $value ){ |
|
|
|
|
1564
|
|
|
$value = $value[0]; |
1565
|
|
|
}else{ |
1566
|
|
|
$value = $this->parseEntitiesVariable(); |
1567
|
|
|
if( !$value ){ |
1568
|
|
|
return; |
1569
|
|
|
} |
1570
|
|
|
} |
1571
|
|
|
|
1572
|
|
|
$this->expectChar(')'); |
1573
|
|
|
return $this->NewObj1('Less_Tree_Alpha',$value); |
1574
|
|
|
} |
1575
|
|
|
|
1576
|
|
|
|
1577
|
|
|
// |
1578
|
|
|
// A Selector Element |
1579
|
|
|
// |
1580
|
|
|
// div |
1581
|
|
|
// + h1 |
1582
|
|
|
// #socks |
1583
|
|
|
// input[type="text"] |
|
|
|
|
1584
|
|
|
// |
1585
|
|
|
// Elements are the building blocks for Selectors, |
1586
|
|
|
// they are made out of a `Combinator` (see combinator rule), |
1587
|
|
|
// and an element name, such as a tag a class, or `*`. |
1588
|
|
|
// |
1589
|
|
|
private function parseElement(){ |
1590
|
|
|
$c = $this->parseCombinator(); |
1591
|
|
|
$index = $this->pos; |
1592
|
|
|
|
1593
|
|
|
$e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/', |
1594
|
|
|
'#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') ); |
1595
|
|
|
|
1596
|
|
|
if( is_null($e) ){ |
1597
|
|
|
$this->save(); |
1598
|
|
|
if( $this->MatchChar('(') ){ |
1599
|
|
|
if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){ |
1600
|
|
|
$e = $this->NewObj1('Less_Tree_Paren',$v); |
1601
|
|
|
$this->forget(); |
1602
|
|
|
}else{ |
1603
|
|
|
$this->restore(); |
1604
|
|
|
} |
1605
|
|
|
}else{ |
1606
|
|
|
$this->forget(); |
1607
|
|
|
} |
1608
|
|
|
} |
1609
|
|
|
|
1610
|
|
|
if( !is_null($e) ){ |
1611
|
|
|
return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo)); |
1612
|
|
|
} |
1613
|
|
|
} |
1614
|
|
|
|
1615
|
|
|
// |
1616
|
|
|
// Combinators combine elements together, in a Selector. |
1617
|
|
|
// |
1618
|
|
|
// Because our parser isn't white-space sensitive, special care |
1619
|
|
|
// has to be taken, when parsing the descendant combinator, ` `, |
1620
|
|
|
// as it's an empty space. We have to check the previous character |
1621
|
|
|
// in the input, to see if it's a ` ` character. |
1622
|
|
|
// |
1623
|
|
|
private function parseCombinator(){ |
1624
|
|
|
if( $this->pos < $this->input_len ){ |
1625
|
|
|
$c = $this->input[$this->pos]; |
1626
|
|
|
if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){ |
1627
|
|
|
|
1628
|
|
|
$this->pos++; |
1629
|
|
|
if( $this->input[$this->pos] === '^' ){ |
1630
|
|
|
$c = '^^'; |
1631
|
|
|
$this->pos++; |
1632
|
|
|
} |
1633
|
|
|
|
1634
|
|
|
$this->skipWhitespace(0); |
1635
|
|
|
|
1636
|
|
|
return $c; |
1637
|
|
|
} |
1638
|
|
|
|
1639
|
|
|
if( $this->pos > 0 && $this->isWhitespace(-1) ){ |
1640
|
|
|
return ' '; |
1641
|
|
|
} |
1642
|
|
|
} |
1643
|
|
|
} |
1644
|
|
|
|
1645
|
|
|
// |
1646
|
|
|
// A CSS selector (see selector below) |
1647
|
|
|
// with less extensions e.g. the ability to extend and guard |
1648
|
|
|
// |
1649
|
|
|
private function parseLessSelector(){ |
1650
|
|
|
return $this->parseSelector(true); |
1651
|
|
|
} |
1652
|
|
|
|
1653
|
|
|
// |
1654
|
|
|
// A CSS Selector |
1655
|
|
|
// |
1656
|
|
|
// .class > div + h1 |
1657
|
|
|
// li a:hover |
1658
|
|
|
// |
1659
|
|
|
// Selectors are made out of one or more Elements, see above. |
1660
|
|
|
// |
1661
|
|
|
private function parseSelector( $isLess = false ){ |
1662
|
|
|
$elements = array(); |
1663
|
|
|
$extendList = array(); |
1664
|
|
|
$condition = null; |
1665
|
|
|
$when = false; |
1666
|
|
|
$extend = false; |
1667
|
|
|
$e = null; |
1668
|
|
|
$c = null; |
1669
|
|
|
$index = $this->pos; |
1670
|
|
|
|
1671
|
|
|
while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){ |
1672
|
|
|
if( $when ){ |
1673
|
|
|
$condition = $this->expect('parseConditions', 'expected condition'); |
1674
|
|
|
}elseif( $condition ){ |
|
|
|
|
1675
|
|
|
//error("CSS guard can only be used at the end of selector"); |
1676
|
|
|
}elseif( $extend ){ |
1677
|
|
|
$extendList = array_merge($extendList,$extend); |
1678
|
|
|
}else{ |
1679
|
|
|
//if( count($extendList) ){ |
|
|
|
|
1680
|
|
|
//error("Extend can only be used at the end of selector"); |
1681
|
|
|
//} |
1682
|
|
|
if( $this->pos < $this->input_len ){ |
1683
|
|
|
$c = $this->input[ $this->pos ]; |
1684
|
|
|
} |
1685
|
|
|
$elements[] = $e; |
1686
|
|
|
$e = null; |
1687
|
|
|
} |
1688
|
|
|
|
1689
|
|
|
if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; } |
1690
|
|
|
} |
1691
|
|
|
|
1692
|
|
|
if( $elements ){ |
|
|
|
|
1693
|
|
|
return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo)); |
1694
|
|
|
} |
1695
|
|
|
if( $extendList ) { |
|
|
|
|
1696
|
|
|
$this->Error('Extend must be used to extend a selector, it cannot be used on its own'); |
1697
|
|
|
} |
1698
|
|
|
} |
1699
|
|
|
|
1700
|
|
|
private function parseTag(){ |
1701
|
|
|
return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*'); |
1702
|
|
|
} |
1703
|
|
|
|
1704
|
|
|
private function parseAttribute(){ |
1705
|
|
|
|
1706
|
|
|
$val = null; |
1707
|
|
|
|
1708
|
|
|
if( !$this->MatchChar('[') ){ |
1709
|
|
|
return; |
1710
|
|
|
} |
1711
|
|
|
|
1712
|
|
|
$key = $this->parseEntitiesVariableCurly(); |
1713
|
|
|
if( !$key ){ |
1714
|
|
|
$key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/'); |
1715
|
|
|
} |
1716
|
|
|
|
1717
|
|
|
$op = $this->MatchReg('/\\G[|~*$^]?=/'); |
1718
|
|
|
if( $op ){ |
1719
|
|
|
$val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') ); |
1720
|
|
|
} |
1721
|
|
|
|
1722
|
|
|
$this->expectChar(']'); |
1723
|
|
|
|
1724
|
|
|
return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val)); |
1725
|
|
|
} |
1726
|
|
|
|
1727
|
|
|
// |
1728
|
|
|
// The `block` rule is used by `ruleset` and `mixin.definition`. |
1729
|
|
|
// It's a wrapper around the `primary` rule, with added `{}`. |
1730
|
|
|
// |
1731
|
|
|
private function parseBlock(){ |
1732
|
|
|
if( $this->MatchChar('{') ){ |
1733
|
|
|
$content = $this->parsePrimary(); |
1734
|
|
|
if( $this->MatchChar('}') ){ |
1735
|
|
|
return $content; |
1736
|
|
|
} |
1737
|
|
|
} |
1738
|
|
|
} |
1739
|
|
|
|
1740
|
|
|
private function parseBlockRuleset(){ |
1741
|
|
|
$block = $this->parseBlock(); |
1742
|
|
|
|
1743
|
|
|
if( $block ){ |
1744
|
|
|
$block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block)); |
1745
|
|
|
} |
1746
|
|
|
|
1747
|
|
|
return $block; |
1748
|
|
|
} |
1749
|
|
|
|
1750
|
|
|
private function parseDetachedRuleset(){ |
1751
|
|
|
$blockRuleset = $this->parseBlockRuleset(); |
1752
|
|
|
if( $blockRuleset ){ |
1753
|
|
|
return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset); |
1754
|
|
|
} |
1755
|
|
|
} |
1756
|
|
|
|
1757
|
|
|
// |
1758
|
|
|
// div, .class, body > p {...} |
|
|
|
|
1759
|
|
|
// |
1760
|
|
|
private function parseRuleset(){ |
1761
|
|
|
$selectors = array(); |
1762
|
|
|
|
1763
|
|
|
$this->save(); |
1764
|
|
|
|
1765
|
|
|
while( true ){ |
1766
|
|
|
$s = $this->parseLessSelector(); |
1767
|
|
|
if( !$s ){ |
1768
|
|
|
break; |
1769
|
|
|
} |
1770
|
|
|
$selectors[] = $s; |
1771
|
|
|
$this->parseComments(); |
1772
|
|
|
|
1773
|
|
|
if( $s->condition && count($selectors) > 1 ){ |
1774
|
|
|
$this->Error('Guards are only currently allowed on a single selector.'); |
1775
|
|
|
} |
1776
|
|
|
|
1777
|
|
|
if( !$this->MatchChar(',') ){ |
1778
|
|
|
break; |
1779
|
|
|
} |
1780
|
|
|
if( $s->condition ){ |
1781
|
|
|
$this->Error('Guards are only currently allowed on a single selector.'); |
1782
|
|
|
} |
1783
|
|
|
$this->parseComments(); |
1784
|
|
|
} |
1785
|
|
|
|
1786
|
|
|
|
1787
|
|
|
if( $selectors ){ |
|
|
|
|
1788
|
|
|
$rules = $this->parseBlock(); |
1789
|
|
|
if( is_array($rules) ){ |
1790
|
|
|
$this->forget(); |
1791
|
|
|
return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports |
1792
|
|
|
} |
1793
|
|
|
} |
1794
|
|
|
|
1795
|
|
|
// Backtrack |
1796
|
|
|
$this->furthest = $this->pos; |
1797
|
|
|
$this->restore(); |
1798
|
|
|
} |
1799
|
|
|
|
1800
|
|
|
/** |
1801
|
|
|
* Custom less.php parse function for finding simple name-value css pairs |
1802
|
|
|
* ex: width:100px; |
1803
|
|
|
* |
1804
|
|
|
*/ |
1805
|
|
|
private function parseNameValue(){ |
1806
|
|
|
|
1807
|
|
|
$index = $this->pos; |
1808
|
|
|
$this->save(); |
1809
|
|
|
|
1810
|
|
|
|
1811
|
|
|
//$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/'); |
1812
|
|
|
$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/'); |
1813
|
|
|
if( $match ){ |
1814
|
|
|
|
1815
|
|
|
if( $match[4] == '}' ){ |
1816
|
|
|
$this->pos = $index + strlen($match[0])-1; |
1817
|
|
|
} |
1818
|
|
|
|
1819
|
|
|
if( $match[3] ){ |
1820
|
|
|
$match[2] .= ' !important'; |
1821
|
|
|
} |
1822
|
|
|
|
1823
|
|
|
return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo)); |
1824
|
|
|
} |
1825
|
|
|
|
1826
|
|
|
$this->restore(); |
1827
|
|
|
} |
1828
|
|
|
|
1829
|
|
|
|
1830
|
|
|
private function parseRule( $tryAnonymous = null ){ |
1831
|
|
|
|
1832
|
|
|
$merge = false; |
1833
|
|
|
$startOfRule = $this->pos; |
1834
|
|
|
|
1835
|
|
|
$c = $this->input[$this->pos]; |
1836
|
|
|
if( $c === '.' || $c === '#' || $c === '&' ){ |
1837
|
|
|
return; |
1838
|
|
|
} |
1839
|
|
|
|
1840
|
|
|
$this->save(); |
1841
|
|
|
$name = $this->MatchFuncs( array('parseVariable','parseRuleProperty')); |
1842
|
|
|
|
1843
|
|
|
if( $name ){ |
1844
|
|
|
|
1845
|
|
|
$isVariable = is_string($name); |
1846
|
|
|
|
1847
|
|
|
$value = null; |
1848
|
|
|
if( $isVariable ){ |
1849
|
|
|
$value = $this->parseDetachedRuleset(); |
1850
|
|
|
} |
1851
|
|
|
|
1852
|
|
|
$important = null; |
1853
|
|
|
if( !$value ){ |
1854
|
|
|
|
1855
|
|
|
// prefer to try to parse first if its a variable or we are compressing |
1856
|
|
|
// but always fallback on the other one |
1857
|
|
|
//if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){ |
|
|
|
|
1858
|
|
|
if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){ |
1859
|
|
|
$value = $this->MatchFuncs( array('parseValue','parseAnonymousValue')); |
1860
|
|
|
}else{ |
1861
|
|
|
$value = $this->MatchFuncs( array('parseAnonymousValue','parseValue')); |
1862
|
|
|
} |
1863
|
|
|
|
1864
|
|
|
$important = $this->parseImportant(); |
1865
|
|
|
|
1866
|
|
|
// a name returned by this.ruleProperty() is always an array of the form: |
1867
|
|
|
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] |
|
|
|
|
1868
|
|
|
// where each item is a tree.Keyword or tree.Variable |
1869
|
|
|
if( !$isVariable && is_array($name) ){ |
1870
|
|
|
$nm = array_pop($name); |
1871
|
|
|
if( $nm->value ){ |
1872
|
|
|
$merge = $nm->value; |
1873
|
|
|
} |
1874
|
|
|
} |
1875
|
|
|
} |
1876
|
|
|
|
1877
|
|
|
|
1878
|
|
|
if( $value && $this->parseEnd() ){ |
1879
|
|
|
$this->forget(); |
1880
|
|
|
return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo)); |
1881
|
|
|
}else{ |
1882
|
|
|
$this->furthest = $this->pos; |
1883
|
|
|
$this->restore(); |
1884
|
|
|
if( $value && !$tryAnonymous ){ |
1885
|
|
|
return $this->parseRule(true); |
1886
|
|
|
} |
1887
|
|
|
} |
1888
|
|
|
}else{ |
1889
|
|
|
$this->forget(); |
1890
|
|
|
} |
1891
|
|
|
} |
1892
|
|
|
|
1893
|
|
|
function parseAnonymousValue(){ |
|
|
|
|
1894
|
|
|
|
1895
|
|
|
if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){ |
1896
|
|
|
$this->pos += strlen($match[1]); |
1897
|
|
|
return $this->NewObj1('Less_Tree_Anonymous',$match[1]); |
1898
|
|
|
} |
1899
|
|
|
} |
1900
|
|
|
|
1901
|
|
|
// |
1902
|
|
|
// An @import directive |
1903
|
|
|
// |
1904
|
|
|
// @import "lib"; |
1905
|
|
|
// |
1906
|
|
|
// Depending on our environment, importing is done differently: |
1907
|
|
|
// In the browser, it's an XHR request, in Node, it would be a |
1908
|
|
|
// file-system operation. The function used for importing is |
1909
|
|
|
// stored in `import`, which we pass to the Import constructor. |
1910
|
|
|
// |
1911
|
|
|
private function parseImport(){ |
1912
|
|
|
|
1913
|
|
|
$this->save(); |
1914
|
|
|
|
1915
|
|
|
$dir = $this->MatchReg('/\\G@import?\s+/'); |
1916
|
|
|
|
1917
|
|
|
if( $dir ){ |
1918
|
|
|
$options = $this->parseImportOptions(); |
1919
|
|
|
$path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl')); |
1920
|
|
|
|
1921
|
|
|
if( $path ){ |
1922
|
|
|
$features = $this->parseMediaFeatures(); |
1923
|
|
|
if( $this->MatchChar(';') ){ |
1924
|
|
|
if( $features ){ |
1925
|
|
|
$features = $this->NewObj1('Less_Tree_Value',$features); |
1926
|
|
|
} |
1927
|
|
|
|
1928
|
|
|
$this->forget(); |
1929
|
|
|
return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo)); |
1930
|
|
|
} |
1931
|
|
|
} |
1932
|
|
|
} |
1933
|
|
|
|
1934
|
|
|
$this->restore(); |
1935
|
|
|
} |
1936
|
|
|
|
1937
|
|
|
private function parseImportOptions(){ |
1938
|
|
|
|
1939
|
|
|
$options = array(); |
1940
|
|
|
|
1941
|
|
|
// list of options, surrounded by parens |
1942
|
|
|
if( !$this->MatchChar('(') ){ |
1943
|
|
|
return $options; |
1944
|
|
|
} |
1945
|
|
|
do{ |
1946
|
|
|
$optionName = $this->parseImportOption(); |
1947
|
|
|
if( $optionName ){ |
1948
|
|
|
$value = true; |
1949
|
|
|
switch( $optionName ){ |
1950
|
|
|
case "css": |
1951
|
|
|
$optionName = "less"; |
1952
|
|
|
$value = false; |
1953
|
|
|
break; |
1954
|
|
|
case "once": |
1955
|
|
|
$optionName = "multiple"; |
1956
|
|
|
$value = false; |
1957
|
|
|
break; |
1958
|
|
|
} |
1959
|
|
|
$options[$optionName] = $value; |
1960
|
|
|
if( !$this->MatchChar(',') ){ break; } |
1961
|
|
|
} |
1962
|
|
|
}while( $optionName ); |
1963
|
|
|
$this->expectChar(')'); |
1964
|
|
|
return $options; |
1965
|
|
|
} |
1966
|
|
|
|
1967
|
|
|
private function parseImportOption(){ |
1968
|
|
|
$opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/'); |
1969
|
|
|
if( $opt ){ |
1970
|
|
|
return $opt[1]; |
1971
|
|
|
} |
1972
|
|
|
} |
1973
|
|
|
|
1974
|
|
|
private function parseMediaFeature() { |
1975
|
|
|
$nodes = array(); |
1976
|
|
|
|
1977
|
|
|
do{ |
1978
|
|
|
$e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable')); |
1979
|
|
|
if( $e ){ |
1980
|
|
|
$nodes[] = $e; |
1981
|
|
|
} elseif ($this->MatchChar('(')) { |
1982
|
|
|
$p = $this->parseProperty(); |
1983
|
|
|
$e = $this->parseValue(); |
1984
|
|
|
if ($this->MatchChar(')')) { |
1985
|
|
|
if ($p && $e) { |
1986
|
|
|
$r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true)); |
1987
|
|
|
$nodes[] = $this->NewObj1('Less_Tree_Paren',$r); |
1988
|
|
|
} elseif ($e) { |
1989
|
|
|
$nodes[] = $this->NewObj1('Less_Tree_Paren',$e); |
1990
|
|
|
} else { |
1991
|
|
|
return null; |
1992
|
|
|
} |
1993
|
|
|
} else |
1994
|
|
|
return null; |
1995
|
|
|
} |
1996
|
|
|
} while ($e); |
1997
|
|
|
|
1998
|
|
|
if ($nodes) { |
|
|
|
|
1999
|
|
|
return $this->NewObj1('Less_Tree_Expression',$nodes); |
2000
|
|
|
} |
2001
|
|
|
} |
2002
|
|
|
|
2003
|
|
|
private function parseMediaFeatures() { |
2004
|
|
|
$features = array(); |
2005
|
|
|
|
2006
|
|
|
do{ |
2007
|
|
|
$e = $this->parseMediaFeature(); |
2008
|
|
|
if( $e ){ |
2009
|
|
|
$features[] = $e; |
2010
|
|
|
if (!$this->MatchChar(',')) break; |
2011
|
|
|
}else{ |
2012
|
|
|
$e = $this->parseEntitiesVariable(); |
|
|
|
|
2013
|
|
|
if( $e ){ |
2014
|
|
|
$features[] = $e; |
2015
|
|
|
if (!$this->MatchChar(',')) break; |
2016
|
|
|
} |
2017
|
|
|
} |
2018
|
|
|
} while ($e); |
2019
|
|
|
|
2020
|
|
|
return $features ? $features : null; |
2021
|
|
|
} |
2022
|
|
|
|
2023
|
|
|
private function parseMedia() { |
2024
|
|
|
if( $this->MatchReg('/\\G@media/') ){ |
2025
|
|
|
$features = $this->parseMediaFeatures(); |
2026
|
|
|
$rules = $this->parseBlock(); |
2027
|
|
|
|
2028
|
|
|
if( is_array($rules) ){ |
2029
|
|
|
return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo)); |
2030
|
|
|
} |
2031
|
|
|
} |
2032
|
|
|
} |
2033
|
|
|
|
2034
|
|
|
|
2035
|
|
|
// |
2036
|
|
|
// A CSS Directive |
2037
|
|
|
// |
2038
|
|
|
// @charset "utf-8"; |
2039
|
|
|
// |
2040
|
|
|
private function parseDirective(){ |
2041
|
|
|
|
2042
|
|
|
if( !$this->PeekChar('@') ){ |
2043
|
|
|
return; |
2044
|
|
|
} |
2045
|
|
|
|
2046
|
|
|
$rules = null; |
2047
|
|
|
$index = $this->pos; |
2048
|
|
|
$hasBlock = true; |
2049
|
|
|
$hasIdentifier = false; |
2050
|
|
|
$hasExpression = false; |
2051
|
|
|
$hasUnknown = false; |
2052
|
|
|
|
2053
|
|
|
|
2054
|
|
|
$value = $this->MatchFuncs(array('parseImport','parseMedia')); |
2055
|
|
|
if( $value ){ |
2056
|
|
|
return $value; |
2057
|
|
|
} |
2058
|
|
|
|
2059
|
|
|
$this->save(); |
2060
|
|
|
|
2061
|
|
|
$name = $this->MatchReg('/\\G@[a-z-]+/'); |
2062
|
|
|
|
2063
|
|
|
if( !$name ) return; |
2064
|
|
|
$name = $name[0]; |
2065
|
|
|
|
2066
|
|
|
|
2067
|
|
|
$nonVendorSpecificName = $name; |
2068
|
|
|
$pos = strpos($name,'-', 2); |
2069
|
|
|
if( $name[1] == '-' && $pos > 0 ){ |
2070
|
|
|
$nonVendorSpecificName = "@" . substr($name, $pos + 1); |
2071
|
|
|
} |
2072
|
|
|
|
2073
|
|
|
|
2074
|
|
|
switch( $nonVendorSpecificName ){ |
2075
|
|
|
/* |
|
|
|
|
2076
|
|
|
case "@font-face": |
2077
|
|
|
case "@viewport": |
2078
|
|
|
case "@top-left": |
2079
|
|
|
case "@top-left-corner": |
2080
|
|
|
case "@top-center": |
2081
|
|
|
case "@top-right": |
2082
|
|
|
case "@top-right-corner": |
2083
|
|
|
case "@bottom-left": |
2084
|
|
|
case "@bottom-left-corner": |
2085
|
|
|
case "@bottom-center": |
2086
|
|
|
case "@bottom-right": |
2087
|
|
|
case "@bottom-right-corner": |
2088
|
|
|
case "@left-top": |
2089
|
|
|
case "@left-middle": |
2090
|
|
|
case "@left-bottom": |
2091
|
|
|
case "@right-top": |
2092
|
|
|
case "@right-middle": |
2093
|
|
|
case "@right-bottom": |
2094
|
|
|
hasBlock = true; |
2095
|
|
|
break; |
2096
|
|
|
*/ |
2097
|
|
|
case "@charset": |
2098
|
|
|
$hasIdentifier = true; |
2099
|
|
|
$hasBlock = false; |
2100
|
|
|
break; |
2101
|
|
|
case "@namespace": |
2102
|
|
|
$hasExpression = true; |
2103
|
|
|
$hasBlock = false; |
2104
|
|
|
break; |
2105
|
|
|
case "@keyframes": |
2106
|
|
|
$hasIdentifier = true; |
2107
|
|
|
break; |
2108
|
|
|
case "@host": |
2109
|
|
|
case "@page": |
2110
|
|
|
case "@document": |
2111
|
|
|
case "@supports": |
2112
|
|
|
$hasUnknown = true; |
2113
|
|
|
break; |
2114
|
|
|
} |
2115
|
|
|
|
2116
|
|
|
if( $hasIdentifier ){ |
2117
|
|
|
$value = $this->parseEntity(); |
2118
|
|
|
if( !$value ){ |
2119
|
|
|
$this->error("expected " . $name . " identifier"); |
2120
|
|
|
} |
2121
|
|
|
} else if( $hasExpression ){ |
2122
|
|
|
$value = $this->parseExpression(); |
2123
|
|
|
if( !$value ){ |
2124
|
|
|
$this->error("expected " . $name. " expression"); |
2125
|
|
|
} |
2126
|
|
|
} else if ($hasUnknown) { |
2127
|
|
|
|
2128
|
|
|
$value = $this->MatchReg('/\\G[^{;]+/'); |
2129
|
|
|
if( $value ){ |
|
|
|
|
2130
|
|
|
$value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0])); |
2131
|
|
|
} |
2132
|
|
|
} |
2133
|
|
|
|
2134
|
|
|
if( $hasBlock ){ |
2135
|
|
|
$rules = $this->parseBlockRuleset(); |
2136
|
|
|
} |
2137
|
|
|
|
2138
|
|
|
if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) { |
2139
|
|
|
$this->forget(); |
2140
|
|
|
return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo)); |
2141
|
|
|
} |
2142
|
|
|
|
2143
|
|
|
$this->restore(); |
2144
|
|
|
} |
2145
|
|
|
|
2146
|
|
|
|
2147
|
|
|
// |
2148
|
|
|
// A Value is a comma-delimited list of Expressions |
2149
|
|
|
// |
2150
|
|
|
// font-family: Baskerville, Georgia, serif; |
2151
|
|
|
// |
2152
|
|
|
// In a Rule, a Value represents everything after the `:`, |
2153
|
|
|
// and before the `;`. |
2154
|
|
|
// |
2155
|
|
|
private function parseValue(){ |
2156
|
|
|
$expressions = array(); |
2157
|
|
|
|
2158
|
|
|
do{ |
2159
|
|
|
$e = $this->parseExpression(); |
2160
|
|
|
if( $e ){ |
2161
|
|
|
$expressions[] = $e; |
2162
|
|
|
if (! $this->MatchChar(',')) { |
2163
|
|
|
break; |
2164
|
|
|
} |
2165
|
|
|
} |
2166
|
|
|
}while($e); |
2167
|
|
|
|
2168
|
|
|
if( $expressions ){ |
|
|
|
|
2169
|
|
|
return $this->NewObj1('Less_Tree_Value',$expressions); |
2170
|
|
|
} |
2171
|
|
|
} |
2172
|
|
|
|
2173
|
|
|
private function parseImportant (){ |
2174
|
|
|
if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){ |
2175
|
|
|
return ' !important'; |
2176
|
|
|
} |
2177
|
|
|
} |
2178
|
|
|
|
2179
|
|
|
private function parseSub (){ |
2180
|
|
|
|
2181
|
|
|
if( $this->MatchChar('(') ){ |
2182
|
|
|
$a = $this->parseAddition(); |
2183
|
|
|
if( $a ){ |
2184
|
|
|
$this->expectChar(')'); |
2185
|
|
|
return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached |
2186
|
|
|
} |
2187
|
|
|
} |
2188
|
|
|
} |
2189
|
|
|
|
2190
|
|
|
|
2191
|
|
|
/** |
2192
|
|
|
* Parses multiplication operation |
2193
|
|
|
* |
2194
|
|
|
* @return Less_Tree_Operation|null |
2195
|
|
|
*/ |
2196
|
|
|
function parseMultiplication(){ |
|
|
|
|
2197
|
|
|
|
2198
|
|
|
$return = $m = $this->parseOperand(); |
2199
|
|
|
if( $return ){ |
2200
|
|
|
while( true ){ |
2201
|
|
|
|
2202
|
|
|
$isSpaced = $this->isWhitespace( -1 ); |
2203
|
|
|
|
2204
|
|
|
if( $this->PeekReg('/\\G\/[*\/]/') ){ |
2205
|
|
|
break; |
2206
|
|
|
} |
2207
|
|
|
|
2208
|
|
|
$op = $this->MatchChar('/'); |
2209
|
|
|
if( !$op ){ |
2210
|
|
|
$op = $this->MatchChar('*'); |
2211
|
|
|
if( !$op ){ |
2212
|
|
|
break; |
2213
|
|
|
} |
2214
|
|
|
} |
2215
|
|
|
|
2216
|
|
|
$a = $this->parseOperand(); |
2217
|
|
|
|
2218
|
|
|
if(!$a) { break; } |
2219
|
|
|
|
2220
|
|
|
$m->parensInOp = true; |
2221
|
|
|
$a->parensInOp = true; |
2222
|
|
|
$return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) ); |
2223
|
|
|
} |
2224
|
|
|
} |
2225
|
|
|
return $return; |
2226
|
|
|
|
2227
|
|
|
} |
2228
|
|
|
|
2229
|
|
|
|
2230
|
|
|
/** |
2231
|
|
|
* Parses an addition operation |
2232
|
|
|
* |
2233
|
|
|
* @return Less_Tree_Operation|null |
2234
|
|
|
*/ |
2235
|
|
|
private function parseAddition (){ |
2236
|
|
|
|
2237
|
|
|
$return = $m = $this->parseMultiplication(); |
2238
|
|
|
if( $return ){ |
2239
|
|
|
while( true ){ |
2240
|
|
|
|
2241
|
|
|
$isSpaced = $this->isWhitespace( -1 ); |
2242
|
|
|
|
2243
|
|
|
$op = $this->MatchReg('/\\G[-+]\s+/'); |
2244
|
|
|
if( $op ){ |
2245
|
|
|
$op = $op[0]; |
2246
|
|
|
}else{ |
2247
|
|
|
if( !$isSpaced ){ |
2248
|
|
|
$op = $this->match(array('#+','#-')); |
2249
|
|
|
} |
2250
|
|
|
if( !$op ){ |
2251
|
|
|
break; |
2252
|
|
|
} |
2253
|
|
|
} |
2254
|
|
|
|
2255
|
|
|
$a = $this->parseMultiplication(); |
2256
|
|
|
if( !$a ){ |
2257
|
|
|
break; |
2258
|
|
|
} |
2259
|
|
|
|
2260
|
|
|
$m->parensInOp = true; |
2261
|
|
|
$a->parensInOp = true; |
2262
|
|
|
$return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced)); |
2263
|
|
|
} |
2264
|
|
|
} |
2265
|
|
|
|
2266
|
|
|
return $return; |
2267
|
|
|
} |
2268
|
|
|
|
2269
|
|
|
|
2270
|
|
|
/** |
2271
|
|
|
* Parses the conditions |
2272
|
|
|
* |
2273
|
|
|
* @return Less_Tree_Condition|null |
2274
|
|
|
*/ |
2275
|
|
|
private function parseConditions() { |
2276
|
|
|
$index = $this->pos; |
2277
|
|
|
$return = $a = $this->parseCondition(); |
2278
|
|
|
if( $a ){ |
2279
|
|
|
while( true ){ |
2280
|
|
|
if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') || !$this->MatchChar(',') ){ |
2281
|
|
|
break; |
2282
|
|
|
} |
2283
|
|
|
$b = $this->parseCondition(); |
2284
|
|
|
if( !$b ){ |
2285
|
|
|
break; |
2286
|
|
|
} |
2287
|
|
|
|
2288
|
|
|
$return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index)); |
2289
|
|
|
} |
2290
|
|
|
return $return; |
2291
|
|
|
} |
2292
|
|
|
} |
2293
|
|
|
|
2294
|
|
|
private function parseCondition() { |
2295
|
|
|
$index = $this->pos; |
2296
|
|
|
$negate = false; |
2297
|
|
|
$c = null; |
2298
|
|
|
|
2299
|
|
|
if ($this->MatchReg('/\\Gnot/')) $negate = true; |
2300
|
|
|
$this->expectChar('('); |
2301
|
|
|
$a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted')); |
2302
|
|
|
|
2303
|
|
|
if( $a ){ |
2304
|
|
|
$op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/'); |
2305
|
|
|
if( $op ){ |
2306
|
|
|
$b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted')); |
2307
|
|
View Code Duplication |
if( $b ){ |
|
|
|
|
2308
|
|
|
$c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate)); |
2309
|
|
|
} else { |
2310
|
|
|
$this->Error('Unexpected expression'); |
2311
|
|
|
} |
2312
|
|
View Code Duplication |
} else { |
|
|
|
|
2313
|
|
|
$k = $this->NewObj1('Less_Tree_Keyword','true'); |
2314
|
|
|
$c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate)); |
2315
|
|
|
} |
2316
|
|
|
$this->expectChar(')'); |
2317
|
|
|
return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c; |
2318
|
|
|
} |
2319
|
|
|
} |
2320
|
|
|
|
2321
|
|
|
/** |
2322
|
|
|
* An operand is anything that can be part of an operation, |
2323
|
|
|
* such as a Color, or a Variable |
2324
|
|
|
* |
2325
|
|
|
*/ |
2326
|
|
|
private function parseOperand (){ |
2327
|
|
|
|
2328
|
|
|
$negate = false; |
2329
|
|
|
$offset = $this->pos+1; |
2330
|
|
|
if( $offset >= $this->input_len ){ |
2331
|
|
|
return; |
2332
|
|
|
} |
2333
|
|
|
$char = $this->input[$offset]; |
2334
|
|
|
if( $char === '@' || $char === '(' ){ |
2335
|
|
|
$negate = $this->MatchChar('-'); |
2336
|
|
|
} |
2337
|
|
|
|
2338
|
|
|
$o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall')); |
2339
|
|
|
|
2340
|
|
|
if( $negate ){ |
2341
|
|
|
$o->parensInOp = true; |
2342
|
|
|
$o = $this->NewObj1('Less_Tree_Negative',$o); |
2343
|
|
|
} |
2344
|
|
|
|
2345
|
|
|
return $o; |
2346
|
|
|
} |
2347
|
|
|
|
2348
|
|
|
|
2349
|
|
|
/** |
2350
|
|
|
* Expressions either represent mathematical operations, |
2351
|
|
|
* or white-space delimited Entities. |
2352
|
|
|
* |
2353
|
|
|
* 1px solid black |
2354
|
|
|
* @var * 2 |
2355
|
|
|
* |
2356
|
|
|
* @return Less_Tree_Expression|null |
2357
|
|
|
*/ |
2358
|
|
|
private function parseExpression (){ |
2359
|
|
|
$entities = array(); |
2360
|
|
|
|
2361
|
|
|
do{ |
2362
|
|
|
$e = $this->MatchFuncs(array('parseAddition','parseEntity')); |
2363
|
|
|
if( $e ){ |
2364
|
|
|
$entities[] = $e; |
2365
|
|
|
// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here |
2366
|
|
|
if( !$this->PeekReg('/\\G\/[\/*]/') ){ |
2367
|
|
|
$delim = $this->MatchChar('/'); |
2368
|
|
|
if( $delim ){ |
2369
|
|
|
$entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim); |
2370
|
|
|
} |
2371
|
|
|
} |
2372
|
|
|
} |
2373
|
|
|
}while($e); |
2374
|
|
|
|
2375
|
|
|
if( $entities ){ |
|
|
|
|
2376
|
|
|
return $this->NewObj1('Less_Tree_Expression',$entities); |
2377
|
|
|
} |
2378
|
|
|
} |
2379
|
|
|
|
2380
|
|
|
|
2381
|
|
|
/** |
2382
|
|
|
* Parse a property |
2383
|
|
|
* eg: 'min-width', 'orientation', etc |
2384
|
|
|
* |
2385
|
|
|
* @return string |
2386
|
|
|
*/ |
2387
|
|
|
private function parseProperty (){ |
2388
|
|
|
$name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/'); |
2389
|
|
|
if( $name ){ |
2390
|
|
|
return $name[1]; |
2391
|
|
|
} |
2392
|
|
|
} |
2393
|
|
|
|
2394
|
|
|
|
2395
|
|
|
/** |
2396
|
|
|
* Parse a rule property |
2397
|
|
|
* eg: 'color', 'width', 'height', etc |
2398
|
|
|
* |
2399
|
|
|
* @return string |
2400
|
|
|
*/ |
2401
|
|
|
private function parseRuleProperty(){ |
2402
|
|
|
$offset = $this->pos; |
2403
|
|
|
$name = array(); |
2404
|
|
|
$index = array(); |
2405
|
|
|
$length = 0; |
2406
|
|
|
|
2407
|
|
|
|
2408
|
|
|
$this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name ); |
2409
|
|
|
while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // ! |
2410
|
|
|
|
2411
|
|
|
if( (count($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){ |
2412
|
|
|
// at last, we have the complete match now. move forward, |
2413
|
|
|
// convert name particles to tree objects and return: |
2414
|
|
|
$this->skipWhitespace($length); |
2415
|
|
|
|
2416
|
|
|
if( $name[0] === '' ){ |
2417
|
|
|
array_shift($name); |
2418
|
|
|
array_shift($index); |
2419
|
|
|
} |
2420
|
|
|
foreach($name as $k => $s ){ |
2421
|
|
|
if( !$s || $s[0] !== '@' ){ |
2422
|
|
|
$name[$k] = $this->NewObj1('Less_Tree_Keyword',$s); |
2423
|
|
|
}else{ |
2424
|
|
|
$name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo)); |
2425
|
|
|
} |
2426
|
|
|
} |
2427
|
|
|
return $name; |
2428
|
|
|
} |
2429
|
|
|
|
2430
|
|
|
|
2431
|
|
|
} |
2432
|
|
|
|
2433
|
|
|
private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ){ |
2434
|
|
|
preg_match($re, $this->input, $a, 0, $offset); |
2435
|
|
|
if( $a ){ |
|
|
|
|
2436
|
|
|
$index[] = $this->pos + $length; |
2437
|
|
|
$length += strlen($a[0]); |
2438
|
|
|
$offset += strlen($a[0]); |
2439
|
|
|
$name[] = $a[1]; |
2440
|
|
|
return true; |
2441
|
|
|
} |
2442
|
|
|
} |
2443
|
|
|
|
2444
|
|
|
public static function serializeVars( $vars ){ |
2445
|
|
|
$s = ''; |
2446
|
|
|
|
2447
|
|
|
foreach($vars as $name => $value){ |
2448
|
|
|
$s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';'); |
2449
|
|
|
} |
2450
|
|
|
|
2451
|
|
|
return $s; |
2452
|
|
|
} |
2453
|
|
|
|
2454
|
|
|
|
2455
|
|
|
/** |
2456
|
|
|
* Some versions of php have trouble with method_exists($a,$b) if $a is not an object |
2457
|
|
|
* |
2458
|
|
|
* @param string $b |
2459
|
|
|
*/ |
2460
|
|
|
public static function is_method($a,$b){ |
2461
|
|
|
return is_object($a) && method_exists($a,$b); |
2462
|
|
|
} |
2463
|
|
|
|
2464
|
|
|
|
2465
|
|
|
/** |
2466
|
|
|
* Round numbers similarly to javascript |
2467
|
|
|
* eg: 1.499999 to 1 instead of 2 |
2468
|
|
|
* |
2469
|
|
|
*/ |
2470
|
|
|
public static function round($i, $precision = 0){ |
2471
|
|
|
|
2472
|
|
|
$precision = pow(10,$precision); |
2473
|
|
|
$i = $i*$precision; |
2474
|
|
|
|
2475
|
|
|
$ceil = ceil($i); |
2476
|
|
|
$floor = floor($i); |
2477
|
|
|
if( ($ceil - $i) <= ($i - $floor) ){ |
2478
|
|
|
return $ceil/$precision; |
2479
|
|
|
}else{ |
2480
|
|
|
return $floor/$precision; |
2481
|
|
|
} |
2482
|
|
|
} |
2483
|
|
|
|
2484
|
|
|
|
2485
|
|
|
/** |
2486
|
|
|
* Create Less_Tree_* objects and optionally generate a cache string |
2487
|
|
|
* |
2488
|
|
|
* @return mixed |
2489
|
|
|
*/ |
2490
|
|
|
public function NewObj0($class){ |
2491
|
|
|
$obj = new $class(); |
2492
|
|
|
if( $this->CacheEnabled() ){ |
2493
|
|
|
$obj->cache_string = ' new '.$class.'()'; |
2494
|
|
|
} |
2495
|
|
|
return $obj; |
2496
|
|
|
} |
2497
|
|
|
|
2498
|
|
|
public function NewObj1($class, $arg){ |
2499
|
|
|
$obj = new $class( $arg ); |
2500
|
|
|
if( $this->CacheEnabled() ){ |
2501
|
|
|
$obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')'; |
2502
|
|
|
} |
2503
|
|
|
return $obj; |
2504
|
|
|
} |
2505
|
|
|
|
2506
|
|
|
public function NewObj2($class, $args){ |
2507
|
|
|
$obj = new $class( $args[0], $args[1] ); |
2508
|
|
|
if( $this->CacheEnabled() ){ |
2509
|
|
|
$this->ObjCache( $obj, $class, $args); |
2510
|
|
|
} |
2511
|
|
|
return $obj; |
2512
|
|
|
} |
2513
|
|
|
|
2514
|
|
|
public function NewObj3($class, $args){ |
2515
|
|
|
$obj = new $class( $args[0], $args[1], $args[2] ); |
2516
|
|
|
if( $this->CacheEnabled() ){ |
2517
|
|
|
$this->ObjCache( $obj, $class, $args); |
2518
|
|
|
} |
2519
|
|
|
return $obj; |
2520
|
|
|
} |
2521
|
|
|
|
2522
|
|
|
public function NewObj4($class, $args){ |
2523
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3] ); |
2524
|
|
|
if( $this->CacheEnabled() ){ |
2525
|
|
|
$this->ObjCache( $obj, $class, $args); |
2526
|
|
|
} |
2527
|
|
|
return $obj; |
2528
|
|
|
} |
2529
|
|
|
|
2530
|
|
|
public function NewObj5($class, $args){ |
2531
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] ); |
2532
|
|
|
if( $this->CacheEnabled() ){ |
2533
|
|
|
$this->ObjCache( $obj, $class, $args); |
2534
|
|
|
} |
2535
|
|
|
return $obj; |
2536
|
|
|
} |
2537
|
|
|
|
2538
|
|
View Code Duplication |
public function NewObj6($class, $args){ |
|
|
|
|
2539
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] ); |
2540
|
|
|
if( $this->CacheEnabled() ){ |
2541
|
|
|
$this->ObjCache( $obj, $class, $args); |
2542
|
|
|
} |
2543
|
|
|
return $obj; |
2544
|
|
|
} |
2545
|
|
|
|
2546
|
|
View Code Duplication |
public function NewObj7($class, $args){ |
|
|
|
|
2547
|
|
|
$obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] ); |
2548
|
|
|
if( $this->CacheEnabled() ){ |
2549
|
|
|
$this->ObjCache( $obj, $class, $args); |
2550
|
|
|
} |
2551
|
|
|
return $obj; |
2552
|
|
|
} |
2553
|
|
|
|
2554
|
|
|
//caching |
2555
|
|
|
public function ObjCache($obj, $class, $args=array()){ |
2556
|
|
|
$obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')'; |
2557
|
|
|
} |
2558
|
|
|
|
2559
|
|
|
public function ArgCache($args){ |
2560
|
|
|
return implode(',',array_map( array('Less_Parser','ArgString'),$args)); |
2561
|
|
|
} |
2562
|
|
|
|
2563
|
|
|
|
2564
|
|
|
/** |
2565
|
|
|
* Convert an argument to a string for use in the parser cache |
2566
|
|
|
* |
2567
|
|
|
* @return string |
2568
|
|
|
*/ |
2569
|
|
|
public static function ArgString($arg){ |
2570
|
|
|
|
2571
|
|
|
$type = gettype($arg); |
2572
|
|
|
|
2573
|
|
|
if( $type === 'object'){ |
2574
|
|
|
$string = $arg->cache_string; |
2575
|
|
|
unset($arg->cache_string); |
2576
|
|
|
return $string; |
2577
|
|
|
|
2578
|
|
|
}elseif( $type === 'array' ){ |
2579
|
|
|
$string = ' Array('; |
2580
|
|
|
foreach($arg as $k => $a){ |
2581
|
|
|
$string .= var_export($k,true).' => '.self::ArgString($a).','; |
2582
|
|
|
} |
2583
|
|
|
return $string . ')'; |
2584
|
|
|
} |
2585
|
|
|
|
2586
|
|
|
return var_export($arg,true); |
2587
|
|
|
} |
2588
|
|
|
|
2589
|
|
|
public function Error($msg){ |
2590
|
|
|
throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo); |
|
|
|
|
2591
|
|
|
} |
2592
|
|
|
|
2593
|
|
|
public static function WinPath($path){ |
2594
|
|
|
return str_replace('\\', '/', $path); |
2595
|
|
|
} |
2596
|
|
|
|
2597
|
|
|
public function CacheEnabled(){ |
2598
|
|
|
return (Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback'))); |
2599
|
|
|
} |
2600
|
|
|
|
2601
|
|
|
} |
2602
|
|
|
|
2603
|
|
|
|
2604
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.