less-js-source 1.1.1.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. data/Gemfile +1 -0
  2. data/less-js-source.gemspec +1 -1
  3. data/lib/less_js/less.js +579 -572
  4. metadata +4 -5
data/Gemfile CHANGED
@@ -1,4 +1,5 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem 'rake'
3
4
  # Specify your gem's dependencies in less-js-source.gemspec
4
5
  gemspec
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "less-js-source"
6
- s.version = '1.1.1.1'
6
+ s.version = '1.1.2'
7
7
  s.authors = ["Alexis Sellier"]
8
8
  s.email = ["alexis@cloudhead.io"]
9
9
  s.homepage = "http://lesscss.org"
@@ -1,5 +1,5 @@
1
1
  //
2
- // LESS - Leaner CSS v1.1.1
2
+ // LESS - Leaner CSS v1.1.2
3
3
  // http://lesscss.org
4
4
  //
5
5
  // Copyright (c) 2009-2011, Alexis Sellier
@@ -625,7 +625,7 @@ less.Parser = function Parser(env) {
625
625
  // The arguments are parsed with the `entities.arguments` parser.
626
626
  //
627
627
  call: function () {
628
- var name, args;
628
+ var name, args, index = i;
629
629
 
630
630
  if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return;
631
631
 
@@ -642,7 +642,7 @@ less.Parser = function Parser(env) {
642
642
 
643
643
  if (! $(')')) return;
644
644
 
645
- if (name) { return new(tree.Call)(name, args) }
645
+ if (name) { return new(tree.Call)(name, args, index) }
646
646
  },
647
647
  arguments: function () {
648
648
  var args = [], arg;
@@ -1231,6 +1231,78 @@ if (typeof(window) !== 'undefined') {
1231
1231
  };
1232
1232
  }
1233
1233
 
1234
+ (function (tree) {
1235
+
1236
+ tree.Alpha = function (val) {
1237
+ this.value = val;
1238
+ };
1239
+ tree.Alpha.prototype = {
1240
+ toCSS: function () {
1241
+ return "alpha(opacity=" +
1242
+ (this.value.toCSS ? this.value.toCSS() : this.value) + ")";
1243
+ },
1244
+ eval: function () { return this }
1245
+ };
1246
+
1247
+ })(require('less/tree'));
1248
+ (function (tree) {
1249
+
1250
+ tree.Anonymous = function (string) {
1251
+ this.value = string.value || string;
1252
+ };
1253
+ tree.Anonymous.prototype = {
1254
+ toCSS: function () {
1255
+ return this.value;
1256
+ },
1257
+ eval: function () { return this }
1258
+ };
1259
+
1260
+ })(require('less/tree'));
1261
+ (function (tree) {
1262
+
1263
+ //
1264
+ // A function call node.
1265
+ //
1266
+ tree.Call = function (name, args, index) {
1267
+ this.name = name;
1268
+ this.args = args;
1269
+ this.index = index;
1270
+ };
1271
+ tree.Call.prototype = {
1272
+ //
1273
+ // When evaluating a function call,
1274
+ // we either find the function in `tree.functions` [1],
1275
+ // in which case we call it, passing the evaluated arguments,
1276
+ // or we simply print it out as it appeared originally [2].
1277
+ //
1278
+ // The *functions.js* file contains the built-in functions.
1279
+ //
1280
+ // The reason why we evaluate the arguments, is in the case where
1281
+ // we try to pass a variable to a function, like: `saturate(@color)`.
1282
+ // The function should receive the value, not the variable.
1283
+ //
1284
+ eval: function (env) {
1285
+ var args = this.args.map(function (a) { return a.eval(env) });
1286
+
1287
+ if (this.name in tree.functions) { // 1.
1288
+ try {
1289
+ return tree.functions[this.name].apply(tree.functions, args);
1290
+ } catch (e) {
1291
+ throw { message: "error evaluating function `" + this.name + "`",
1292
+ index: this.index };
1293
+ }
1294
+ } else { // 2.
1295
+ return new(tree.Anonymous)(this.name +
1296
+ "(" + args.map(function (a) { return a.toCSS() }).join(', ') + ")");
1297
+ }
1298
+ },
1299
+
1300
+ toCSS: function (env) {
1301
+ return this.eval(env).toCSS();
1302
+ }
1303
+ };
1304
+
1305
+ })(require('less/tree'));
1234
1306
  (function (tree) {
1235
1307
  //
1236
1308
  // RGB Colors - #ff0014, #eee
@@ -1331,66 +1403,15 @@ tree.Color.prototype = {
1331
1403
  })(require('less/tree'));
1332
1404
  (function (tree) {
1333
1405
 
1334
- tree.Directive = function (name, value) {
1335
- this.name = name;
1336
- if (Array.isArray(value)) {
1337
- this.ruleset = new(tree.Ruleset)([], value);
1338
- } else {
1339
- this.value = value;
1340
- }
1406
+ tree.Comment = function (value, silent) {
1407
+ this.value = value;
1408
+ this.silent = !!silent;
1341
1409
  };
1342
- tree.Directive.prototype = {
1343
- toCSS: function (ctx, env) {
1344
- if (this.ruleset) {
1345
- this.ruleset.root = true;
1346
- return this.name + (env.compress ? '{' : ' {\n ') +
1347
- this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') +
1348
- (env.compress ? '}': '\n}\n');
1349
- } else {
1350
- return this.name + ' ' + this.value.toCSS() + ';\n';
1351
- }
1352
- },
1353
- eval: function (env) {
1354
- env.frames.unshift(this);
1355
- this.ruleset = this.ruleset && this.ruleset.eval(env);
1356
- env.frames.shift();
1357
- return this;
1410
+ tree.Comment.prototype = {
1411
+ toCSS: function (env) {
1412
+ return env.compress ? '' : this.value;
1358
1413
  },
1359
- variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
1360
- find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
1361
- rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }
1362
- };
1363
-
1364
- })(require('less/tree'));
1365
- (function (tree) {
1366
-
1367
- tree.Operation = function (op, operands) {
1368
- this.op = op.trim();
1369
- this.operands = operands;
1370
- };
1371
- tree.Operation.prototype.eval = function (env) {
1372
- var a = this.operands[0].eval(env),
1373
- b = this.operands[1].eval(env),
1374
- temp;
1375
-
1376
- if (a instanceof tree.Dimension && b instanceof tree.Color) {
1377
- if (this.op === '*' || this.op === '+') {
1378
- temp = b, b = a, a = temp;
1379
- } else {
1380
- throw { name: "OperationError",
1381
- message: "Can't substract or divide a color from a number" };
1382
- }
1383
- }
1384
- return a.operate(this.op, b);
1385
- };
1386
-
1387
- tree.operate = function (op, a, b) {
1388
- switch (op) {
1389
- case '+': return a + b;
1390
- case '-': return a - b;
1391
- case '*': return a * b;
1392
- case '/': return a / b;
1393
- }
1414
+ eval: function () { return this }
1394
1415
  };
1395
1416
 
1396
1417
  })(require('less/tree'));
@@ -1430,273 +1451,364 @@ tree.Dimension.prototype = {
1430
1451
  })(require('less/tree'));
1431
1452
  (function (tree) {
1432
1453
 
1433
- tree.Keyword = function (value) { this.value = value };
1434
- tree.Keyword.prototype = {
1435
- eval: function () { return this },
1436
- toCSS: function () { return this.value }
1454
+ tree.Directive = function (name, value) {
1455
+ this.name = name;
1456
+ if (Array.isArray(value)) {
1457
+ this.ruleset = new(tree.Ruleset)([], value);
1458
+ } else {
1459
+ this.value = value;
1460
+ }
1461
+ };
1462
+ tree.Directive.prototype = {
1463
+ toCSS: function (ctx, env) {
1464
+ if (this.ruleset) {
1465
+ this.ruleset.root = true;
1466
+ return this.name + (env.compress ? '{' : ' {\n ') +
1467
+ this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') +
1468
+ (env.compress ? '}': '\n}\n');
1469
+ } else {
1470
+ return this.name + ' ' + this.value.toCSS() + ';\n';
1471
+ }
1472
+ },
1473
+ eval: function (env) {
1474
+ env.frames.unshift(this);
1475
+ this.ruleset = this.ruleset && this.ruleset.eval(env);
1476
+ env.frames.shift();
1477
+ return this;
1478
+ },
1479
+ variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
1480
+ find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
1481
+ rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }
1437
1482
  };
1438
1483
 
1439
1484
  })(require('less/tree'));
1440
1485
  (function (tree) {
1441
1486
 
1442
- tree.Variable = function (name, index) { this.name = name, this.index = index };
1443
- tree.Variable.prototype = {
1444
- eval: function (env) {
1445
- var variable, v, name = this.name;
1446
-
1447
- if (name.indexOf('@@') == 0) {
1448
- name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value;
1449
- }
1487
+ tree.Element = function (combinator, value) {
1488
+ this.combinator = combinator instanceof tree.Combinator ?
1489
+ combinator : new(tree.Combinator)(combinator);
1490
+ this.value = value.trim();
1491
+ };
1492
+ tree.Element.prototype.toCSS = function (env) {
1493
+ return this.combinator.toCSS(env || {}) + this.value;
1494
+ };
1450
1495
 
1451
- if (variable = tree.find(env.frames, function (frame) {
1452
- if (v = frame.variable(name)) {
1453
- return v.value.eval(env);
1454
- }
1455
- })) { return variable }
1456
- else {
1457
- throw { message: "variable " + name + " is undefined",
1458
- index: this.index };
1459
- }
1496
+ tree.Combinator = function (value) {
1497
+ if (value === ' ') {
1498
+ this.value = ' ';
1499
+ } else {
1500
+ this.value = value ? value.trim() : "";
1460
1501
  }
1461
1502
  };
1503
+ tree.Combinator.prototype.toCSS = function (env) {
1504
+ return {
1505
+ '' : '',
1506
+ ' ' : ' ',
1507
+ '&' : '',
1508
+ ':' : ' :',
1509
+ '::': '::',
1510
+ '+' : env.compress ? '+' : ' + ',
1511
+ '~' : env.compress ? '~' : ' ~ ',
1512
+ '>' : env.compress ? '>' : ' > '
1513
+ }[this.value];
1514
+ };
1462
1515
 
1463
1516
  })(require('less/tree'));
1464
1517
  (function (tree) {
1465
1518
 
1466
- tree.Ruleset = function (selectors, rules) {
1467
- this.selectors = selectors;
1468
- this.rules = rules;
1469
- this._lookups = {};
1470
- };
1471
- tree.Ruleset.prototype = {
1519
+ tree.Expression = function (value) { this.value = value };
1520
+ tree.Expression.prototype = {
1472
1521
  eval: function (env) {
1473
- var ruleset = new(tree.Ruleset)(this.selectors, this.rules.slice(0));
1522
+ if (this.value.length > 1) {
1523
+ return new(tree.Expression)(this.value.map(function (e) {
1524
+ return e.eval(env);
1525
+ }));
1526
+ } else if (this.value.length === 1) {
1527
+ return this.value[0].eval(env);
1528
+ } else {
1529
+ return this;
1530
+ }
1531
+ },
1532
+ toCSS: function (env) {
1533
+ return this.value.map(function (e) {
1534
+ return e.toCSS(env);
1535
+ }).join(' ');
1536
+ }
1537
+ };
1474
1538
 
1475
- ruleset.root = this.root;
1539
+ })(require('less/tree'));
1540
+ (function (tree) {
1541
+ //
1542
+ // CSS @import node
1543
+ //
1544
+ // The general strategy here is that we don't want to wait
1545
+ // for the parsing to be completed, before we start importing
1546
+ // the file. That's because in the context of a browser,
1547
+ // most of the time will be spent waiting for the server to respond.
1548
+ //
1549
+ // On creation, we push the import path to our import queue, though
1550
+ // `import,push`, we also pass it a callback, which it'll call once
1551
+ // the file has been fetched, and parsed.
1552
+ //
1553
+ tree.Import = function (path, imports) {
1554
+ var that = this;
1476
1555
 
1477
- // push the current ruleset to the frames stack
1478
- env.frames.unshift(ruleset);
1556
+ this._path = path;
1479
1557
 
1480
- // Evaluate imports
1481
- if (ruleset.root) {
1482
- for (var i = 0; i < ruleset.rules.length; i++) {
1483
- if (ruleset.rules[i] instanceof tree.Import) {
1484
- Array.prototype.splice
1485
- .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
1486
- }
1487
- }
1488
- }
1558
+ // The '.less' extension is optional
1559
+ if (path instanceof tree.Quoted) {
1560
+ this.path = /\.(le?|c)ss$/.test(path.value) ? path.value : path.value + '.less';
1561
+ } else {
1562
+ this.path = path.value.value || path.value;
1563
+ }
1489
1564
 
1490
- // Store the frames around mixin definitions,
1491
- // so they can be evaluated like closures when the time comes.
1492
- for (var i = 0; i < ruleset.rules.length; i++) {
1493
- if (ruleset.rules[i] instanceof tree.mixin.Definition) {
1494
- ruleset.rules[i].frames = env.frames.slice(0);
1495
- }
1496
- }
1565
+ this.css = /css$/.test(this.path);
1497
1566
 
1498
- // Evaluate mixin calls.
1499
- for (var i = 0; i < ruleset.rules.length; i++) {
1500
- if (ruleset.rules[i] instanceof tree.mixin.Call) {
1501
- Array.prototype.splice
1502
- .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
1567
+ // Only pre-compile .less files
1568
+ if (! this.css) {
1569
+ imports.push(this.path, function (root) {
1570
+ if (! root) {
1571
+ throw new(Error)("Error parsing " + that.path);
1503
1572
  }
1573
+ that.root = root;
1574
+ });
1575
+ }
1576
+ };
1577
+
1578
+ //
1579
+ // The actual import node doesn't return anything, when converted to CSS.
1580
+ // The reason is that it's used at the evaluation stage, so that the rules
1581
+ // it imports can be treated like any other rules.
1582
+ //
1583
+ // In `eval`, we make sure all Import nodes get evaluated, recursively, so
1584
+ // we end up with a flat structure, which can easily be imported in the parent
1585
+ // ruleset.
1586
+ //
1587
+ tree.Import.prototype = {
1588
+ toCSS: function () {
1589
+ if (this.css) {
1590
+ return "@import " + this._path.toCSS() + ';\n';
1591
+ } else {
1592
+ return "";
1504
1593
  }
1594
+ },
1595
+ eval: function (env) {
1596
+ var ruleset;
1505
1597
 
1506
- // Evaluate everything else
1507
- for (var i = 0, rule; i < ruleset.rules.length; i++) {
1508
- rule = ruleset.rules[i];
1598
+ if (this.css) {
1599
+ return this;
1600
+ } else {
1601
+ ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0));
1509
1602
 
1510
- if (! (rule instanceof tree.mixin.Definition)) {
1511
- ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
1603
+ for (var i = 0; i < ruleset.rules.length; i++) {
1604
+ if (ruleset.rules[i] instanceof tree.Import) {
1605
+ Array.prototype
1606
+ .splice
1607
+ .apply(ruleset.rules,
1608
+ [i, 1].concat(ruleset.rules[i].eval(env)));
1609
+ }
1512
1610
  }
1611
+ return ruleset.rules;
1513
1612
  }
1613
+ }
1614
+ };
1514
1615
 
1515
- // Pop the stack
1516
- env.frames.shift();
1616
+ })(require('less/tree'));
1617
+ (function (tree) {
1517
1618
 
1518
- return ruleset;
1519
- },
1520
- match: function (args) {
1521
- return !args || args.length === 0;
1522
- },
1523
- variables: function () {
1524
- if (this._variables) { return this._variables }
1525
- else {
1526
- return this._variables = this.rules.reduce(function (hash, r) {
1527
- if (r instanceof tree.Rule && r.variable === true) {
1528
- hash[r.name] = r;
1619
+ tree.JavaScript = function (string, index, escaped) {
1620
+ this.escaped = escaped;
1621
+ this.expression = string;
1622
+ this.index = index;
1623
+ };
1624
+ tree.JavaScript.prototype = {
1625
+ eval: function (env) {
1626
+ var result,
1627
+ that = this,
1628
+ context = {};
1629
+
1630
+ var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
1631
+ return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env));
1632
+ });
1633
+
1634
+ try {
1635
+ expression = new(Function)('return (' + expression + ')');
1636
+ } catch (e) {
1637
+ throw { message: "JavaScript evaluation error: `" + expression + "`" ,
1638
+ index: this.index };
1639
+ }
1640
+
1641
+ for (var k in env.frames[0].variables()) {
1642
+ context[k.slice(1)] = {
1643
+ value: env.frames[0].variables()[k].value,
1644
+ toJS: function () {
1645
+ return this.value.eval(env).toCSS();
1529
1646
  }
1530
- return hash;
1531
- }, {});
1647
+ };
1532
1648
  }
1533
- },
1534
- variable: function (name) {
1535
- return this.variables()[name];
1536
- },
1537
- rulesets: function () {
1538
- if (this._rulesets) { return this._rulesets }
1539
- else {
1540
- return this._rulesets = this.rules.filter(function (r) {
1541
- return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition);
1542
- });
1649
+
1650
+ try {
1651
+ result = expression.call(context);
1652
+ } catch (e) {
1653
+ throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" ,
1654
+ index: this.index };
1543
1655
  }
1544
- },
1545
- find: function (selector, self) {
1546
- self = self || this;
1547
- var rules = [], rule, match,
1548
- key = selector.toCSS();
1656
+ if (typeof(result) === 'string') {
1657
+ return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index);
1658
+ } else if (Array.isArray(result)) {
1659
+ return new(tree.Anonymous)(result.join(', '));
1660
+ } else {
1661
+ return new(tree.Anonymous)(result);
1662
+ }
1663
+ }
1664
+ };
1549
1665
 
1550
- if (key in this._lookups) { return this._lookups[key] }
1666
+ })(require('less/tree'));
1551
1667
 
1552
- this.rulesets().forEach(function (rule) {
1553
- if (rule !== self) {
1554
- for (var j = 0; j < rule.selectors.length; j++) {
1555
- if (match = selector.match(rule.selectors[j])) {
1556
- if (selector.elements.length > 1) {
1557
- Array.prototype.push.apply(rules, rule.find(
1558
- new(tree.Selector)(selector.elements.slice(1)), self));
1559
- } else {
1560
- rules.push(rule);
1561
- }
1562
- break;
1563
- }
1564
- }
1565
- }
1566
- });
1567
- return this._lookups[key] = rules;
1568
- },
1569
- //
1570
- // Entry point for code generation
1571
- //
1572
- // `context` holds an array of arrays.
1573
- //
1574
- toCSS: function (context, env) {
1575
- var css = [], // The CSS output
1576
- rules = [], // node.Rule instances
1577
- rulesets = [], // node.Ruleset instances
1578
- paths = [], // Current selectors
1579
- selector, // The fully rendered selector
1580
- rule;
1668
+ (function (tree) {
1581
1669
 
1582
- if (! this.root) {
1583
- if (context.length === 0) {
1584
- paths = this.selectors.map(function (s) { return [s] });
1585
- } else {
1586
- for (var s = 0; s < this.selectors.length; s++) {
1587
- for (var c = 0; c < context.length; c++) {
1588
- paths.push(context[c].concat([this.selectors[s]]));
1589
- }
1590
- }
1591
- }
1592
- }
1670
+ tree.Keyword = function (value) { this.value = value };
1671
+ tree.Keyword.prototype = {
1672
+ eval: function () { return this },
1673
+ toCSS: function () { return this.value }
1674
+ };
1593
1675
 
1594
- // Compile rules and rulesets
1595
- for (var i = 0; i < this.rules.length; i++) {
1596
- rule = this.rules[i];
1676
+ })(require('less/tree'));
1677
+ (function (tree) {
1597
1678
 
1598
- if (rule.rules || (rule instanceof tree.Directive)) {
1599
- rulesets.push(rule.toCSS(paths, env));
1600
- } else if (rule instanceof tree.Comment) {
1601
- if (!rule.silent) {
1602
- if (this.root) {
1603
- rulesets.push(rule.toCSS(env));
1604
- } else {
1605
- rules.push(rule.toCSS(env));
1679
+ tree.mixin = {};
1680
+ tree.mixin.Call = function (elements, args, index) {
1681
+ this.selector = new(tree.Selector)(elements);
1682
+ this.arguments = args;
1683
+ this.index = index;
1684
+ };
1685
+ tree.mixin.Call.prototype = {
1686
+ eval: function (env) {
1687
+ var mixins, args, rules = [], match = false;
1688
+
1689
+ for (var i = 0; i < env.frames.length; i++) {
1690
+ if ((mixins = env.frames[i].find(this.selector)).length > 0) {
1691
+ args = this.arguments && this.arguments.map(function (a) { return a.eval(env) });
1692
+ for (var m = 0; m < mixins.length; m++) {
1693
+ if (mixins[m].match(args, env)) {
1694
+ try {
1695
+ Array.prototype.push.apply(
1696
+ rules, mixins[m].eval(env, this.arguments).rules);
1697
+ match = true;
1698
+ } catch (e) {
1699
+ throw { message: e.message, index: e.index, stack: e.stack, call: this.index };
1700
+ }
1606
1701
  }
1607
1702
  }
1608
- } else {
1609
- if (rule.toCSS && !rule.variable) {
1610
- rules.push(rule.toCSS(env));
1611
- } else if (rule.value && !rule.variable) {
1612
- rules.push(rule.value.toString());
1703
+ if (match) {
1704
+ return rules;
1705
+ } else {
1706
+ throw { message: 'No matching definition was found for `' +
1707
+ this.selector.toCSS().trim() + '(' +
1708
+ this.arguments.map(function (a) {
1709
+ return a.toCSS();
1710
+ }).join(', ') + ")`",
1711
+ index: this.index };
1613
1712
  }
1614
1713
  }
1615
- }
1714
+ }
1715
+ throw { message: this.selector.toCSS().trim() + " is undefined",
1716
+ index: this.index };
1717
+ }
1718
+ };
1616
1719
 
1617
- rulesets = rulesets.join('');
1720
+ tree.mixin.Definition = function (name, params, rules) {
1721
+ this.name = name;
1722
+ this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])];
1723
+ this.params = params;
1724
+ this.arity = params.length;
1725
+ this.rules = rules;
1726
+ this._lookups = {};
1727
+ this.required = params.reduce(function (count, p) {
1728
+ if (!p.name || (p.name && !p.value)) { return count + 1 }
1729
+ else { return count }
1730
+ }, 0);
1731
+ this.parent = tree.Ruleset.prototype;
1732
+ this.frames = [];
1733
+ };
1734
+ tree.mixin.Definition.prototype = {
1735
+ toCSS: function () { return "" },
1736
+ variable: function (name) { return this.parent.variable.call(this, name) },
1737
+ variables: function () { return this.parent.variables.call(this) },
1738
+ find: function () { return this.parent.find.apply(this, arguments) },
1739
+ rulesets: function () { return this.parent.rulesets.apply(this) },
1618
1740
 
1619
- // If this is the root node, we don't render
1620
- // a selector, or {}.
1621
- // Otherwise, only output if this ruleset has rules.
1622
- if (this.root) {
1623
- css.push(rules.join(env.compress ? '' : '\n'));
1624
- } else {
1625
- if (rules.length > 0) {
1626
- selector = paths.map(function (p) {
1627
- return p.map(function (s) {
1628
- return s.toCSS(env);
1629
- }).join('').trim();
1630
- }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', '));
1631
- css.push(selector,
1632
- (env.compress ? '{' : ' {\n ') +
1633
- rules.join(env.compress ? '' : '\n ') +
1634
- (env.compress ? '}' : '\n}\n'));
1741
+ eval: function (env, args) {
1742
+ var frame = new(tree.Ruleset)(null, []), context, _arguments = [];
1743
+
1744
+ for (var i = 0, val; i < this.params.length; i++) {
1745
+ if (this.params[i].name) {
1746
+ if (val = (args && args[i]) || this.params[i].value) {
1747
+ frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env)));
1748
+ } else {
1749
+ throw { message: "wrong number of arguments for " + this.name +
1750
+ ' (' + args.length + ' for ' + this.arity + ')' };
1751
+ }
1635
1752
  }
1636
1753
  }
1637
- css.push(rulesets);
1754
+ for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) {
1755
+ _arguments.push(args[i] || this.params[i].value);
1756
+ }
1757
+ frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
1638
1758
 
1639
- return css.join('') + (env.compress ? '\n' : '');
1640
- }
1641
- };
1642
- })(require('less/tree'));
1643
- (function (tree) {
1759
+ return new(tree.Ruleset)(null, this.rules.slice(0)).eval({
1760
+ frames: [this, frame].concat(this.frames, env.frames)
1761
+ });
1762
+ },
1763
+ match: function (args, env) {
1764
+ var argsLength = (args && args.length) || 0, len;
1644
1765
 
1645
- tree.Element = function (combinator, value) {
1646
- this.combinator = combinator instanceof tree.Combinator ?
1647
- combinator : new(tree.Combinator)(combinator);
1648
- this.value = value.trim();
1649
- };
1650
- tree.Element.prototype.toCSS = function (env) {
1651
- return this.combinator.toCSS(env || {}) + this.value;
1652
- };
1766
+ if (argsLength < this.required) { return false }
1767
+ if ((this.required > 0) && (argsLength > this.params.length)) { return false }
1653
1768
 
1654
- tree.Combinator = function (value) {
1655
- if (value === ' ') {
1656
- this.value = ' ';
1657
- } else {
1658
- this.value = value ? value.trim() : "";
1769
+ len = Math.min(argsLength, this.arity);
1770
+
1771
+ for (var i = 0; i < len; i++) {
1772
+ if (!this.params[i].name) {
1773
+ if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
1774
+ return false;
1775
+ }
1776
+ }
1777
+ }
1778
+ return true;
1659
1779
  }
1660
1780
  };
1661
- tree.Combinator.prototype.toCSS = function (env) {
1662
- return {
1663
- '' : '',
1664
- ' ' : ' ',
1665
- '&' : '',
1666
- ':' : ' :',
1667
- '::': '::',
1668
- '+' : env.compress ? '+' : ' + ',
1669
- '~' : env.compress ? '~' : ' ~ ',
1670
- '>' : env.compress ? '>' : ' > '
1671
- }[this.value];
1672
- };
1673
1781
 
1674
1782
  })(require('less/tree'));
1675
1783
  (function (tree) {
1676
1784
 
1677
- tree.Selector = function (elements) {
1678
- this.elements = elements;
1679
- if (this.elements[0].combinator.value === "") {
1680
- this.elements[0].combinator.value = ' ';
1681
- }
1682
- };
1683
- tree.Selector.prototype.match = function (other) {
1684
- if (this.elements[0].value === other.elements[0].value) {
1685
- return true;
1686
- } else {
1687
- return false;
1688
- }
1785
+ tree.Operation = function (op, operands) {
1786
+ this.op = op.trim();
1787
+ this.operands = operands;
1689
1788
  };
1690
- tree.Selector.prototype.toCSS = function (env) {
1691
- if (this._css) { return this._css }
1789
+ tree.Operation.prototype.eval = function (env) {
1790
+ var a = this.operands[0].eval(env),
1791
+ b = this.operands[1].eval(env),
1792
+ temp;
1692
1793
 
1693
- return this._css = this.elements.map(function (e) {
1694
- if (typeof(e) === 'string') {
1695
- return ' ' + e.trim();
1794
+ if (a instanceof tree.Dimension && b instanceof tree.Color) {
1795
+ if (this.op === '*' || this.op === '+') {
1796
+ temp = b, b = a, a = temp;
1696
1797
  } else {
1697
- return e.toCSS(env);
1798
+ throw { name: "OperationError",
1799
+ message: "Can't substract or divide a color from a number" };
1698
1800
  }
1699
- }).join('');
1801
+ }
1802
+ return a.operate(this.op, b);
1803
+ };
1804
+
1805
+ tree.operate = function (op, a, b) {
1806
+ switch (op) {
1807
+ case '+': return a + b;
1808
+ case '-': return a - b;
1809
+ case '*': return a * b;
1810
+ case '/': return a / b;
1811
+ }
1700
1812
  };
1701
1813
 
1702
1814
  })(require('less/tree'));
@@ -1731,29 +1843,6 @@ tree.Quoted.prototype = {
1731
1843
  })(require('less/tree'));
1732
1844
  (function (tree) {
1733
1845
 
1734
- tree.Expression = function (value) { this.value = value };
1735
- tree.Expression.prototype = {
1736
- eval: function (env) {
1737
- if (this.value.length > 1) {
1738
- return new(tree.Expression)(this.value.map(function (e) {
1739
- return e.eval(env);
1740
- }));
1741
- } else if (this.value.length === 1) {
1742
- return this.value[0].eval(env);
1743
- } else {
1744
- return this;
1745
- }
1746
- },
1747
- toCSS: function (env) {
1748
- return this.value.map(function (e) {
1749
- return e.toCSS(env);
1750
- }).join(' ');
1751
- }
1752
- };
1753
-
1754
- })(require('less/tree'));
1755
- (function (tree) {
1756
-
1757
1846
  tree.Rule = function (name, value, important, index) {
1758
1847
  this.name = name;
1759
1848
  this.value = (value instanceof tree.Value) ? value : new(tree.Value)([value]);
@@ -1792,288 +1881,233 @@ tree.Shorthand.prototype = {
1792
1881
  })(require('less/tree'));
1793
1882
  (function (tree) {
1794
1883
 
1795
- //
1796
- // A function call node.
1797
- //
1798
- tree.Call = function (name, args) {
1799
- this.name = name;
1800
- this.args = args;
1884
+ tree.Ruleset = function (selectors, rules) {
1885
+ this.selectors = selectors;
1886
+ this.rules = rules;
1887
+ this._lookups = {};
1801
1888
  };
1802
- tree.Call.prototype = {
1803
- //
1804
- // When evaluating a function call,
1805
- // we either find the function in `tree.functions` [1],
1806
- // in which case we call it, passing the evaluated arguments,
1807
- // or we simply print it out as it appeared originally [2].
1808
- //
1809
- // The *functions.js* file contains the built-in functions.
1810
- //
1811
- // The reason why we evaluate the arguments, is in the case where
1812
- // we try to pass a variable to a function, like: `saturate(@color)`.
1813
- // The function should receive the value, not the variable.
1814
- //
1889
+ tree.Ruleset.prototype = {
1815
1890
  eval: function (env) {
1816
- var args = this.args.map(function (a) { return a.eval(env) });
1817
-
1818
- if (this.name in tree.functions) { // 1.
1819
- return tree.functions[this.name].apply(tree.functions, args);
1820
- } else { // 2.
1821
- return new(tree.Anonymous)(this.name +
1822
- "(" + args.map(function (a) { return a.toCSS() }).join(', ') + ")");
1823
- }
1824
- },
1891
+ var ruleset = new(tree.Ruleset)(this.selectors, this.rules.slice(0));
1825
1892
 
1826
- toCSS: function (env) {
1827
- return this.eval(env).toCSS();
1828
- }
1829
- };
1893
+ ruleset.root = this.root;
1830
1894
 
1831
- })(require('less/tree'));
1832
- (function (tree) {
1895
+ // push the current ruleset to the frames stack
1896
+ env.frames.unshift(ruleset);
1833
1897
 
1834
- tree.URL = function (val, paths) {
1835
- if (val.data) {
1836
- this.attrs = val;
1837
- } else {
1838
- // Add the base path if the URL is relative and we are in the browser
1839
- if (!/^(?:https?:\/|file:\/|data:\/)?\//.test(val.value) && paths.length > 0 && typeof(window) !== 'undefined') {
1840
- val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value);
1898
+ // Evaluate imports
1899
+ if (ruleset.root) {
1900
+ for (var i = 0; i < ruleset.rules.length; i++) {
1901
+ if (ruleset.rules[i] instanceof tree.Import) {
1902
+ Array.prototype.splice
1903
+ .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
1904
+ }
1905
+ }
1841
1906
  }
1842
- this.value = val;
1843
- this.paths = paths;
1844
- }
1845
- };
1846
- tree.URL.prototype = {
1847
- toCSS: function () {
1848
- return "url(" + (this.attrs ? 'data:' + this.attrs.mime + this.attrs.charset + this.attrs.base64 + this.attrs.data
1849
- : this.value.toCSS()) + ")";
1850
- },
1851
- eval: function (ctx) {
1852
- return this.attrs ? this : new(tree.URL)(this.value.eval(ctx), this.paths);
1853
- }
1854
- };
1855
-
1856
- })(require('less/tree'));
1857
- (function (tree) {
1858
-
1859
- tree.Alpha = function (val) {
1860
- this.value = val;
1861
- };
1862
- tree.Alpha.prototype = {
1863
- toCSS: function () {
1864
- return "alpha(opacity=" +
1865
- (this.value.toCSS ? this.value.toCSS() : this.value) + ")";
1866
- },
1867
- eval: function () { return this }
1868
- };
1869
-
1870
- })(require('less/tree'));
1871
- (function (tree) {
1872
- //
1873
- // CSS @import node
1874
- //
1875
- // The general strategy here is that we don't want to wait
1876
- // for the parsing to be completed, before we start importing
1877
- // the file. That's because in the context of a browser,
1878
- // most of the time will be spent waiting for the server to respond.
1879
- //
1880
- // On creation, we push the import path to our import queue, though
1881
- // `import,push`, we also pass it a callback, which it'll call once
1882
- // the file has been fetched, and parsed.
1883
- //
1884
- tree.Import = function (path, imports) {
1885
- var that = this;
1886
-
1887
- this._path = path;
1888
-
1889
- // The '.less' extension is optional
1890
- if (path instanceof tree.Quoted) {
1891
- this.path = /\.(le?|c)ss$/.test(path.value) ? path.value : path.value + '.less';
1892
- } else {
1893
- this.path = path.value.value || path.value;
1894
- }
1895
-
1896
- this.css = /css$/.test(this.path);
1897
1907
 
1898
- // Only pre-compile .less files
1899
- if (! this.css) {
1900
- imports.push(this.path, function (root) {
1901
- if (! root) {
1902
- throw new(Error)("Error parsing " + that.path);
1908
+ // Store the frames around mixin definitions,
1909
+ // so they can be evaluated like closures when the time comes.
1910
+ for (var i = 0; i < ruleset.rules.length; i++) {
1911
+ if (ruleset.rules[i] instanceof tree.mixin.Definition) {
1912
+ ruleset.rules[i].frames = env.frames.slice(0);
1903
1913
  }
1904
- that.root = root;
1905
- });
1906
- }
1907
- };
1914
+ }
1908
1915
 
1909
- //
1910
- // The actual import node doesn't return anything, when converted to CSS.
1911
- // The reason is that it's used at the evaluation stage, so that the rules
1912
- // it imports can be treated like any other rules.
1913
- //
1914
- // In `eval`, we make sure all Import nodes get evaluated, recursively, so
1915
- // we end up with a flat structure, which can easily be imported in the parent
1916
- // ruleset.
1917
- //
1918
- tree.Import.prototype = {
1919
- toCSS: function () {
1920
- if (this.css) {
1921
- return "@import " + this._path.toCSS() + ';\n';
1922
- } else {
1923
- return "";
1916
+ // Evaluate mixin calls.
1917
+ for (var i = 0; i < ruleset.rules.length; i++) {
1918
+ if (ruleset.rules[i] instanceof tree.mixin.Call) {
1919
+ Array.prototype.splice
1920
+ .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
1921
+ }
1924
1922
  }
1925
- },
1926
- eval: function (env) {
1927
- var ruleset;
1928
1923
 
1929
- if (this.css) {
1930
- return this;
1931
- } else {
1932
- ruleset = new(tree.Ruleset)(null, this.root.rules.slice(0));
1924
+ // Evaluate everything else
1925
+ for (var i = 0, rule; i < ruleset.rules.length; i++) {
1926
+ rule = ruleset.rules[i];
1933
1927
 
1934
- for (var i = 0; i < ruleset.rules.length; i++) {
1935
- if (ruleset.rules[i] instanceof tree.Import) {
1936
- Array.prototype
1937
- .splice
1938
- .apply(ruleset.rules,
1939
- [i, 1].concat(ruleset.rules[i].eval(env)));
1940
- }
1928
+ if (! (rule instanceof tree.mixin.Definition)) {
1929
+ ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
1941
1930
  }
1942
- return ruleset.rules;
1943
1931
  }
1944
- }
1945
- };
1946
1932
 
1947
- })(require('less/tree'));
1948
- (function (tree) {
1933
+ // Pop the stack
1934
+ env.frames.shift();
1949
1935
 
1950
- tree.mixin = {};
1951
- tree.mixin.Call = function (elements, args, index) {
1952
- this.selector = new(tree.Selector)(elements);
1953
- this.arguments = args;
1954
- this.index = index;
1955
- };
1956
- tree.mixin.Call.prototype = {
1957
- eval: function (env) {
1958
- var mixins, rules = [], match = false;
1936
+ return ruleset;
1937
+ },
1938
+ match: function (args) {
1939
+ return !args || args.length === 0;
1940
+ },
1941
+ variables: function () {
1942
+ if (this._variables) { return this._variables }
1943
+ else {
1944
+ return this._variables = this.rules.reduce(function (hash, r) {
1945
+ if (r instanceof tree.Rule && r.variable === true) {
1946
+ hash[r.name] = r;
1947
+ }
1948
+ return hash;
1949
+ }, {});
1950
+ }
1951
+ },
1952
+ variable: function (name) {
1953
+ return this.variables()[name];
1954
+ },
1955
+ rulesets: function () {
1956
+ if (this._rulesets) { return this._rulesets }
1957
+ else {
1958
+ return this._rulesets = this.rules.filter(function (r) {
1959
+ return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition);
1960
+ });
1961
+ }
1962
+ },
1963
+ find: function (selector, self) {
1964
+ self = self || this;
1965
+ var rules = [], rule, match,
1966
+ key = selector.toCSS();
1959
1967
 
1960
- for (var i = 0; i < env.frames.length; i++) {
1961
- if ((mixins = env.frames[i].find(this.selector)).length > 0) {
1962
- for (var m = 0; m < mixins.length; m++) {
1963
- if (mixins[m].match(this.arguments, env)) {
1964
- try {
1965
- Array.prototype.push.apply(
1966
- rules, mixins[m].eval(env, this.arguments).rules);
1967
- match = true;
1968
- } catch (e) {
1969
- throw { message: e.message, index: e.index, stack: e.stack, call: this.index };
1968
+ if (key in this._lookups) { return this._lookups[key] }
1969
+
1970
+ this.rulesets().forEach(function (rule) {
1971
+ if (rule !== self) {
1972
+ for (var j = 0; j < rule.selectors.length; j++) {
1973
+ if (match = selector.match(rule.selectors[j])) {
1974
+ if (selector.elements.length > 1) {
1975
+ Array.prototype.push.apply(rules, rule.find(
1976
+ new(tree.Selector)(selector.elements.slice(1)), self));
1977
+ } else {
1978
+ rules.push(rule);
1970
1979
  }
1980
+ break;
1971
1981
  }
1972
1982
  }
1973
- if (match) {
1974
- return rules;
1975
- } else {
1976
- throw { message: 'No matching definition was found for `' +
1977
- this.selector.toCSS().trim() + '(' +
1978
- this.arguments.map(function (a) {
1979
- return a.toCSS();
1980
- }).join(', ') + ")`",
1981
- index: this.index };
1982
- }
1983
1983
  }
1984
- }
1985
- throw { message: this.selector.toCSS().trim() + " is undefined",
1986
- index: this.index };
1987
- }
1988
- };
1989
-
1990
- tree.mixin.Definition = function (name, params, rules) {
1991
- this.name = name;
1992
- this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])];
1993
- this.params = params;
1994
- this.arity = params.length;
1995
- this.rules = rules;
1996
- this._lookups = {};
1997
- this.required = params.reduce(function (count, p) {
1998
- if (!p.name || (p.name && !p.value)) { return count + 1 }
1999
- else { return count }
2000
- }, 0);
2001
- this.parent = tree.Ruleset.prototype;
2002
- this.frames = [];
2003
- };
2004
- tree.mixin.Definition.prototype = {
2005
- toCSS: function () { return "" },
2006
- variable: function (name) { return this.parent.variable.call(this, name) },
2007
- variables: function () { return this.parent.variables.call(this) },
2008
- find: function () { return this.parent.find.apply(this, arguments) },
2009
- rulesets: function () { return this.parent.rulesets.apply(this) },
2010
-
2011
- eval: function (env, args) {
2012
- var frame = new(tree.Ruleset)(null, []), context, _arguments = [];
1984
+ });
1985
+ return this._lookups[key] = rules;
1986
+ },
1987
+ //
1988
+ // Entry point for code generation
1989
+ //
1990
+ // `context` holds an array of arrays.
1991
+ //
1992
+ toCSS: function (context, env) {
1993
+ var css = [], // The CSS output
1994
+ rules = [], // node.Rule instances
1995
+ rulesets = [], // node.Ruleset instances
1996
+ paths = [], // Current selectors
1997
+ selector, // The fully rendered selector
1998
+ rule;
2013
1999
 
2014
- for (var i = 0, val; i < this.params.length; i++) {
2015
- if (this.params[i].name) {
2016
- if (val = (args && args[i]) || this.params[i].value) {
2017
- frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env)));
2018
- } else {
2019
- throw { message: "wrong number of arguments for " + this.name +
2020
- ' (' + args.length + ' for ' + this.arity + ')' };
2000
+ if (! this.root) {
2001
+ if (context.length === 0) {
2002
+ paths = this.selectors.map(function (s) { return [s] });
2003
+ } else {
2004
+ for (var s = 0; s < this.selectors.length; s++) {
2005
+ for (var c = 0; c < context.length; c++) {
2006
+ paths.push(context[c].concat([this.selectors[s]]));
2007
+ }
2021
2008
  }
2022
2009
  }
2023
2010
  }
2024
- for (var i = 0; i < Math.max(this.params.length, args && args.length); i++) {
2025
- _arguments.push(args[i] || this.params[i].value);
2026
- }
2027
- frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(_arguments).eval(env)));
2028
2011
 
2029
- return new(tree.Ruleset)(null, this.rules.slice(0)).eval({
2030
- frames: [this, frame].concat(this.frames, env.frames)
2031
- });
2032
- },
2033
- match: function (args, env) {
2034
- var argsLength = (args && args.length) || 0, len;
2012
+ // Compile rules and rulesets
2013
+ for (var i = 0; i < this.rules.length; i++) {
2014
+ rule = this.rules[i];
2035
2015
 
2036
- if (argsLength < this.required) { return false }
2037
- if ((this.required > 0) && (argsLength > this.params.length)) { return false }
2016
+ if (rule.rules || (rule instanceof tree.Directive)) {
2017
+ rulesets.push(rule.toCSS(paths, env));
2018
+ } else if (rule instanceof tree.Comment) {
2019
+ if (!rule.silent) {
2020
+ if (this.root) {
2021
+ rulesets.push(rule.toCSS(env));
2022
+ } else {
2023
+ rules.push(rule.toCSS(env));
2024
+ }
2025
+ }
2026
+ } else {
2027
+ if (rule.toCSS && !rule.variable) {
2028
+ rules.push(rule.toCSS(env));
2029
+ } else if (rule.value && !rule.variable) {
2030
+ rules.push(rule.value.toString());
2031
+ }
2032
+ }
2033
+ }
2038
2034
 
2039
- len = Math.min(argsLength, this.arity);
2035
+ rulesets = rulesets.join('');
2040
2036
 
2041
- for (var i = 0; i < len; i++) {
2042
- if (!this.params[i].name) {
2043
- if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
2044
- return false;
2045
- }
2037
+ // If this is the root node, we don't render
2038
+ // a selector, or {}.
2039
+ // Otherwise, only output if this ruleset has rules.
2040
+ if (this.root) {
2041
+ css.push(rules.join(env.compress ? '' : '\n'));
2042
+ } else {
2043
+ if (rules.length > 0) {
2044
+ selector = paths.map(function (p) {
2045
+ return p.map(function (s) {
2046
+ return s.toCSS(env);
2047
+ }).join('').trim();
2048
+ }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', '));
2049
+ css.push(selector,
2050
+ (env.compress ? '{' : ' {\n ') +
2051
+ rules.join(env.compress ? '' : '\n ') +
2052
+ (env.compress ? '}' : '\n}\n'));
2046
2053
  }
2047
2054
  }
2048
- return true;
2055
+ css.push(rulesets);
2056
+
2057
+ return css.join('') + (env.compress ? '\n' : '');
2049
2058
  }
2050
2059
  };
2051
-
2052
2060
  })(require('less/tree'));
2053
2061
  (function (tree) {
2054
2062
 
2055
- tree.Comment = function (value, silent) {
2056
- this.value = value;
2057
- this.silent = !!silent;
2063
+ tree.Selector = function (elements) {
2064
+ this.elements = elements;
2065
+ if (this.elements[0].combinator.value === "") {
2066
+ this.elements[0].combinator.value = ' ';
2067
+ }
2058
2068
  };
2059
- tree.Comment.prototype = {
2060
- toCSS: function (env) {
2061
- return env.compress ? '' : this.value;
2062
- },
2063
- eval: function () { return this }
2069
+ tree.Selector.prototype.match = function (other) {
2070
+ if (this.elements[0].value === other.elements[0].value) {
2071
+ return true;
2072
+ } else {
2073
+ return false;
2074
+ }
2075
+ };
2076
+ tree.Selector.prototype.toCSS = function (env) {
2077
+ if (this._css) { return this._css }
2078
+
2079
+ return this._css = this.elements.map(function (e) {
2080
+ if (typeof(e) === 'string') {
2081
+ return ' ' + e.trim();
2082
+ } else {
2083
+ return e.toCSS(env);
2084
+ }
2085
+ }).join('');
2064
2086
  };
2065
2087
 
2066
2088
  })(require('less/tree'));
2067
2089
  (function (tree) {
2068
2090
 
2069
- tree.Anonymous = function (string) {
2070
- this.value = string.value || string;
2091
+ tree.URL = function (val, paths) {
2092
+ if (val.data) {
2093
+ this.attrs = val;
2094
+ } else {
2095
+ // Add the base path if the URL is relative and we are in the browser
2096
+ if (!/^(?:https?:\/|file:\/|data:\/)?\//.test(val.value) && paths.length > 0 && typeof(window) !== 'undefined') {
2097
+ val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value);
2098
+ }
2099
+ this.value = val;
2100
+ this.paths = paths;
2101
+ }
2071
2102
  };
2072
- tree.Anonymous.prototype = {
2103
+ tree.URL.prototype = {
2073
2104
  toCSS: function () {
2074
- return this.value;
2105
+ return "url(" + (this.attrs ? 'data:' + this.attrs.mime + this.attrs.charset + this.attrs.base64 + this.attrs.data
2106
+ : this.value.toCSS()) + ")";
2075
2107
  },
2076
- eval: function () { return this }
2108
+ eval: function (ctx) {
2109
+ return this.attrs ? this : new(tree.URL)(this.value.eval(ctx), this.paths);
2110
+ }
2077
2111
  };
2078
2112
 
2079
2113
  })(require('less/tree'));
@@ -2103,55 +2137,28 @@ tree.Value.prototype = {
2103
2137
  })(require('less/tree'));
2104
2138
  (function (tree) {
2105
2139
 
2106
- tree.JavaScript = function (string, index, escaped) {
2107
- this.escaped = escaped;
2108
- this.expression = string;
2109
- this.index = index;
2110
- };
2111
- tree.JavaScript.prototype = {
2140
+ tree.Variable = function (name, index) { this.name = name, this.index = index };
2141
+ tree.Variable.prototype = {
2112
2142
  eval: function (env) {
2113
- var result,
2114
- that = this,
2115
- context = {};
2116
-
2117
- var expression = this.expression.replace(/@\{([\w-]+)\}/g, function (_, name) {
2118
- return tree.jsify(new(tree.Variable)('@' + name, that.index).eval(env));
2119
- });
2120
-
2121
- try {
2122
- expression = new(Function)('return (' + expression + ')');
2123
- } catch (e) {
2124
- throw { message: "JavaScript evaluation error: `" + expression + "`" ,
2125
- index: this.index };
2126
- }
2143
+ var variable, v, name = this.name;
2127
2144
 
2128
- for (var k in env.frames[0].variables()) {
2129
- context[k.slice(1)] = {
2130
- value: env.frames[0].variables()[k].value,
2131
- toJS: function () {
2132
- return this.value.eval(env).toCSS();
2133
- }
2134
- };
2145
+ if (name.indexOf('@@') == 0) {
2146
+ name = '@' + new(tree.Variable)(name.slice(1)).eval(env).value;
2135
2147
  }
2136
2148
 
2137
- try {
2138
- result = expression.call(context);
2139
- } catch (e) {
2140
- throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" ,
2149
+ if (variable = tree.find(env.frames, function (frame) {
2150
+ if (v = frame.variable(name)) {
2151
+ return v.value.eval(env);
2152
+ }
2153
+ })) { return variable }
2154
+ else {
2155
+ throw { message: "variable " + name + " is undefined",
2141
2156
  index: this.index };
2142
2157
  }
2143
- if (typeof(result) === 'string') {
2144
- return new(tree.Quoted)('"' + result + '"', result, this.escaped, this.index);
2145
- } else if (Array.isArray(result)) {
2146
- return new(tree.Anonymous)(result.join(', '));
2147
- } else {
2148
- return new(tree.Anonymous)(result);
2149
- }
2150
2158
  }
2151
2159
  };
2152
2160
 
2153
2161
  })(require('less/tree'));
2154
-
2155
2162
  require('less/tree').find = function (obj, fun) {
2156
2163
  for (var i = 0, r; i < obj.length; i++) {
2157
2164
  if (r = fun.call(obj, obj[i])) { return r }