Subversion Repositories PHPX

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
37 PointedEar 1
<?php
2
 
3
namespace de\pointedears\css\least;
4
 
5
require_once __DIR__ . '/../../string/parser/Lexer.php';
6
require_once __DIR__ . '/../../string/parser/Parser.php';
7
 
8
/**
9
 * Parses a LEAST stylesheet into a CSS stylesheet
10
 *
11
 * @author Thomas 'PointedEars' Lahn
12
 * @property-read string $compiled
13
 */
14
class Parser extends \de\pointedears\string\parser\Parser
15
{
16
  /**
17
   * CSS stylesheet code compiled from the LEAST stylesheet
18
   *
19
   * @var string
20
   */
21
  protected $_compiled;
22
 
23
  /**
24
   * Array of arrays of template variables, one inner array for
25
   * each scope level.
26
   * @var array[array]
27
   */
28
  protected $_vars = array(array());
29
 
30
  /**
31
   * The nesting level of the current variable scope.
32
   *
33
   * Used to hold variable definitions per nesting level.
34
   *
35
   * @var int
36
   */
37
  protected $_scope_level = 0;
38
 
39
  /**
40
   * Cache for lookup results.
41
   *
42
   * The lookup cache improves parser performance with
43
   * nested scopes.  By contrast to <code>$_vars</code>, it is
44
   * a _shallow_ storage for template variable definitions and
45
   * represents the current _certain_ variable knowledge of the
46
   * parser, i.e. variables that have been defined in outer scopes
47
   * and the same scope, and whose values have been looked up in
48
   * <code>$_vars</code> before.
49
   *
50
   * The lookup cache is replaced by the variables of the outer
51
   * scope when a scope is exited because definitions in the
52
   * outer scope may have been shadowed by the inner scope.
53
   * Note that definitions from the second-next, third-next
54
   * aso. scopes need to be looked up again then.
55
   *
56
   * @var array
57
   */
58
  protected $_lookup_cache = array();
59
 
60
  /**
61
   * Mixins
62
   * @var array
63
   */
64
  protected $_mixins = array();
65
 
66
  /**
67
   * <code>true</code> if the parser is parsing a mix-in
68
   * @var boolean
69
   */
70
  protected $_in_mixin = false;
71
 
72
  /**
73
   * The currently parsed mix-in
74
   * @var Mixin
75
   */
76
  protected $_current_mixin;
77
 
78
  /**
79
   * The nesting level in the currently parsed mix-in, 0 again when
80
   * it just ended
81
   * @var int
82
   */
83
  protected $_mixin_level = 0;
84
 
85
  public function __construct ($code)
86
  {
87
    $lexer = new \de\pointedears\string\parser\Lexer($code);
88
 
89
    $nl = 'n';
90
    $escape = '\\[0-9a-f]{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f]';
91
    $lexer->addTokens(array(
92
      '(?P<PHP><\?php.*?\?>)',
93
      "/\*[^*]*\*+([^/*][^*]*\*+)*/",
94
      "\"([^\n\r\f\\\"]|\\{$nl}|{$escape})*\"",
95
      "'([^\n\r\f\\']|\\{$nl}|{$escape})*'",
96
//       '\.(?P<mixin_def>[\w-]+)\s*\((?P<params>.*?)\)\s*(?!;)',
97
//       '\.(?P<mixin_call>[\w-]+)\s*\((?P<args>.*)?\)\s*;\\s*',
98
      '[{}]',
99
//       '\$(?P<name>\w+)\s*=\s*(?P<value>\'.+?\'|".+"|.+?)\s*;\s*',
100
//       '\$(?P<ref>\w+)'
101
    ));
102
 
103
    $lexer->ignoreCase = true;
104
    $lexer->dotAll = true;
105
 
106
    parent::__construct($lexer);
107
    $this->_compiled = $code;
108
  }
109
 
110
  protected function defineVar($name, $value)
111
  {
112
    $scope_level = $this->_scope_level;
113
 
114
    if (defined('DEBUG') && DEBUG > 0)
115
    {
116
      echo "<?php // Least.php: Found definition \${$name} = `{$value}'"
117
      . " at scope level {$scope_level} ?>\n";
118
    }
119
 
120
    $this->_vars[$scope_level][$name] = $value;
121
    $this->_lookup_cache[$name] = $value;
122
  }
123
 
124
  /**
125
   * Resolves a template variable reference against the template scope chain
126
   * @param string $ref
127
   * @return mixed
128
   */
129
  protected function resolveVar ($name)
130
  {
131
    $scope_level = $this->_scope_level + 1;
132
    $orig_level = $scope_level - 1;
133
 
134
    if (defined('DEBUG') && DEBUG > 0)
135
    {
136
      echo "<?php // Least.php: Found reference \${$name} at scope level "
137
      . ($scope_level - 1) . " ?>\n";
138
    }
139
 
140
    /* Try the lookup cache for this scope level first */
141
    if (array_key_exists($name, $this->_lookup_cache))
142
    {
143
      $value = $this->_lookup_cache[$name];
144
 
145
      if (defined('DEBUG') && DEBUG > 1)
146
      {
147
        echo "<?php // Least.php: Resolved reference \${$name}"
148
        . " at scope level {$orig_level}"
149
        . " from lookup cache, value `{$value}' ?>\n";
150
      }
151
 
152
      return $value;
153
    }
154
 
155
    while ($scope_level--)
156
    {
157
      if (array_key_exists($name, $this->_vars[$scope_level]))
158
      {
159
        $value = $this->_vars[$scope_level][$name];
160
        $this->_lookup_cache[$name] = $value;
161
 
162
        if (defined('DEBUG') && DEBUG > 1)
163
        {
164
          echo "<?php // Least.php: Resolved reference \${$name}"
165
          . " from scope level {$orig_level}"
166
          . " at scope level {$scope_level}, value `{$value}' ?>\n";
167
        }
168
 
169
        return $value;
170
      }
171
    }
172
 
173
    if (defined('DEBUG') && DEBUG > 0)
174
    {
175
      echo "<?php // Least.php: WARNING: Unresolved reference \${$name}"
176
      . " at scope level {$orig_level}. Variable not in scope? ?>\n";
177
    }
178
  }
179
 
180
  protected function defineMixin($name, $params)
181
  {
182
    $scope_level = $this->_scope_level;
183
 
184
    if (defined('DEBUG') && DEBUG > 0)
185
    {
186
      echo "<?php // Least.php: Found mixin definition {$name}($params)"
187
      . " at scope level {$scope_level}. ?>\n";
188
    }
189
 
190
    $mixin = new Mixin($params);
191
    $this->_mixins[$name] = $mixin;
192
 
193
    return $mixin;
194
  }
195
 
196
  protected function callMixin($name, $args)
197
  {
198
    return $this->$_mixins[$name]->apply($args);
199
  }
200
 
201
  public function parseToken ($matches)
202
  {
203
    ini_set('html_errors', 0);
204
//     var_dump($matches);
205
//     /* Get match offset and match length */
206
//     $match = $matches[0];
207
//     $match_start = $match[1];
208
//     $match_length = mb_strlen($match[0]);
209
 
210
//     /* Transform $matches to the format it is usually set as (without PREG_OFFSET_CAPTURE set) */
211
//     $matches = array_map(function ($m) {
212
//       return $m[0];
213
//     }, $matches);
214
//     $match = $matches[0];
215
 
216
//     if (defined('DEBUG') && DEBUG > 1)
217
//     {
218
//       echo print_r(array(
219
//         'match_start' => $match_start,
220
//         'match_length' => $match_length,
221
//         'matches' => $matches,
222
//         'in_mixin' => $in_mixin,
223
//         'scope_level' => $this->_scope_level,
224
//       ), true) . "\n";
225
//     }
226
 
227
//     if (isset($matches['mixin_def']) && $matches['mixin_def'])
228
//     {
229
//       $this->_in_mixin = true;
230
//       $this->_current_mixin = $this->defineMixin($matches['mixin_def'], $matches['params']);
231
//       // $code = self::replace(mb_strlen($match), '', $code, $match_start);
232
//       // $match_length = 0;
233
//     }
234
//     else if (isset($matches['mixin_call']) && $matches['mixin_call'])
235
//     {
236
//       //return
237
//       $this->callMixin($matches['mixin_call'], $matches['args']);
238
//       //$code = self::replace(mb_strlen($match), '', $code, $match_start);
239
//       //$match_length = 0;
240
//       $mixin_start = $match_start;
241
//     }
242
//     else if ($match === '{')
243
//     {
244
//       ++$this->_scope_level;
245
//       $this->_vars[$this->_scope_level] = array();
246
 
247
//       if ($this->_in_mixin)
248
//       {
249
//         if ($mixin_body_start === 0)
250
//         {
251
//           $mixin_body_start = $match_start;
252
//         }
253
 
254
//         ++$this->_mixin_level;
255
//       }
256
//     }
257
//     else if ($match === '}')
258
//     {
259
//       --$this->_scope_level;
260
//       $this->_lookup_cache = $this->_vars[$this->_scope_level];
261
 
262
//       if ($this->_in_mixin)
263
//       {
264
//         --$this->_mixin_level;
265
//         if ($this->_mixin_level === 0)
266
//         {
267
//           $this->_in_mixin = false;
268
//           $this->_current_mixin->setBody(substr($code, $mixin_start + 1, $match_start - $mixin_start - 1));
269
//           echo '"'.print_r(substr($code, $mixin_start, $match_start - $mixin_start + 1), true) . "\"\n";
270
//           //             $code = self::replace($match_start - $mixin_start + 1, '', $code, $mixin_start);
271
//           //             $match_length = 0;
272
//         }
273
//       }
274
//     }
275
//     else if (isset($matches['name']) && $matches['name'])
276
//     {
277
//       $this->defineVar($matches['name'], $matches['value']);
278
//       $code = self::replace(mb_strlen($match), '', $code, $match_start);
279
//       $match_length = 0;
280
//     }
281
//     else if (isset($matches['ref']) && $matches['ref'])
282
//     {
283
//       $name = $matches['ref'];
284
 
285
//       if (!$in_mixin || !in_array($name, $mixin->params, true))
286
//       {
287
//         $value = $this->resolveVar($matches['ref']);
288
//         $this->_lexer->text = self::replace(mb_strlen($match), $value, $code, $match_start);
289
//         $this->_lexer->offset -= $match_length + mb_strlen($value);
290
//       }
291
//     }
292
  }
293
 
294
  public function getCompiled ()
295
  {
296
    return $this->_compiled;
297
  }
298
}
299
 
300
class Mixin extends \de\pointedears\Base
301
{
302
  /**
303
   * Parameters of the mixin
304
   * @var array
305
   */
306
  protected $_params;
307
 
308
  /**
309
   * Body of the mixin
310
   * @var string
311
   */
312
  protected $_body = '';
313
 
314
  /**
315
   * @param string $params
316
   */
317
  public function __construct ($params, $body = '')
318
  {
319
    $this->_params = is_array($params)
320
    ? $params
321
    : array_map(
322
      function ($e) {
323
      return preg_replace('/^\$/', '', $e);
324
    },
325
    preg_split('/\s*,\s*/', $params));
326
 
327
    if ($body !== '')
328
    {
329
      $this->setBody($body);
330
    }
331
  }
332
 
333
  public function setBody ($body)
334
  {
335
    $this->body = (string) $body;
336
  }
337
 
338
  /**
339
   * @param string $arguments
340
   */
341
  public function apply ($arguments)
342
  {
343
    return '';
344
  }
345
}