less 2.4.0 → 2.5.0

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 (265) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +4 -0
  3. data/Gemfile +1 -1
  4. data/less.gemspec +1 -1
  5. data/lib/less/defaults.rb +3 -3
  6. data/lib/less/java_script/v8_context.rb +1 -1
  7. data/lib/less/js/.gitattributes +3 -2
  8. data/lib/less/js/.gitignore +18 -2
  9. data/lib/less/js/.jshintrc +11 -0
  10. data/lib/less/js/CHANGELOG.md +87 -2
  11. data/lib/less/js/CONTRIBUTING.md +4 -3
  12. data/lib/less/js/Gruntfile.js +290 -0
  13. data/lib/less/js/README.md +342 -12
  14. data/lib/less/js/benchmark/benchmark.less +194 -194
  15. data/lib/less/js/benchmark/less-benchmark.js +9 -10
  16. data/lib/less/js/bin/lessc +154 -12
  17. data/lib/less/js/bower.json +18 -0
  18. data/lib/less/js/build.gradle +347 -0
  19. data/lib/less/js/build/README.md +350 -0
  20. data/lib/less/js/build/browser-header.js +4 -0
  21. data/lib/less/js/build/build.yml +160 -0
  22. data/lib/less/js/build/require-rhino.js +7 -2
  23. data/lib/less/js/build/rhino-header.js +4 -0
  24. data/lib/less/js/build/rhino-modules.js +131 -0
  25. data/lib/less/js/build/tasks/.gitkeep +1 -0
  26. data/lib/less/js/dist/less-1.5.0.js +6914 -0
  27. data/lib/less/js/dist/less-1.5.0.min.js +13 -0
  28. data/lib/less/js/dist/less-1.5.1.js +6941 -0
  29. data/lib/less/js/dist/less-1.5.1.min.js +13 -0
  30. data/lib/less/js/dist/less-1.6.0.js +7485 -0
  31. data/lib/less/js/dist/less-1.6.0.min.js +16 -0
  32. data/lib/less/js/dist/less-1.6.1.js +7513 -0
  33. data/lib/less/js/dist/less-1.6.1.min.js +16 -0
  34. data/lib/less/js/dist/less-1.6.2.js +7624 -0
  35. data/lib/less/js/dist/less-1.6.2.min.js +16 -0
  36. data/lib/less/js/dist/less-rhino-1.5.1.js +6831 -0
  37. data/lib/less/js/dist/less-rhino-1.6.2.js +9017 -0
  38. data/lib/less/js/dist/lessc-rhino-1.6.2.js +449 -0
  39. data/lib/less/js/gradle/wrapper/gradle-wrapper.jar +0 -0
  40. data/lib/less/js/gradle/wrapper/gradle-wrapper.properties +6 -0
  41. data/lib/less/js/gradlew +164 -0
  42. data/lib/less/js/gradlew.bat +90 -0
  43. data/lib/less/js/lib/less/browser.js +482 -367
  44. data/lib/less/js/lib/less/colors.js +0 -1
  45. data/lib/less/js/lib/less/encoder.js +4 -0
  46. data/lib/less/js/lib/less/env.js +50 -19
  47. data/lib/less/js/lib/less/extend-visitor.js +66 -41
  48. data/lib/less/js/lib/less/functions.js +309 -104
  49. data/lib/less/js/lib/less/import-visitor.js +21 -10
  50. data/lib/less/js/lib/less/index.js +90 -68
  51. data/lib/less/js/lib/less/join-selector-visitor.js +11 -4
  52. data/lib/less/js/lib/less/lessc_helper.js +56 -45
  53. data/lib/less/js/lib/less/parser.js +830 -460
  54. data/lib/less/js/lib/less/rhino.js +380 -58
  55. data/lib/less/js/lib/less/source-map-output.js +141 -0
  56. data/lib/less/js/lib/less/to-css-visitor.js +215 -0
  57. data/lib/less/js/lib/less/tree.js +57 -5
  58. data/lib/less/js/lib/less/tree/alpha.js +13 -5
  59. data/lib/less/js/lib/less/tree/anonymous.js +11 -5
  60. data/lib/less/js/lib/less/tree/assignment.js +11 -5
  61. data/lib/less/js/lib/less/tree/call.js +19 -8
  62. data/lib/less/js/lib/less/tree/color.js +59 -36
  63. data/lib/less/js/lib/less/tree/comment.js +17 -4
  64. data/lib/less/js/lib/less/tree/condition.js +3 -3
  65. data/lib/less/js/lib/less/tree/dimension.js +161 -153
  66. data/lib/less/js/lib/less/tree/directive.js +39 -18
  67. data/lib/less/js/lib/less/tree/element.js +41 -18
  68. data/lib/less/js/lib/less/tree/expression.js +11 -5
  69. data/lib/less/js/lib/less/tree/extend.js +11 -1
  70. data/lib/less/js/lib/less/tree/import.js +34 -20
  71. data/lib/less/js/lib/less/tree/javascript.js +16 -10
  72. data/lib/less/js/lib/less/tree/keyword.js +5 -2
  73. data/lib/less/js/lib/less/tree/media.js +39 -22
  74. data/lib/less/js/lib/less/tree/mixin.js +135 -56
  75. data/lib/less/js/lib/less/tree/negative.js +4 -2
  76. data/lib/less/js/lib/less/tree/operation.js +17 -12
  77. data/lib/less/js/lib/less/tree/paren.js +5 -2
  78. data/lib/less/js/lib/less/tree/quoted.js +9 -6
  79. data/lib/less/js/lib/less/tree/rule.js +39 -21
  80. data/lib/less/js/lib/less/tree/ruleset.js +229 -145
  81. data/lib/less/js/lib/less/tree/selector.js +101 -34
  82. data/lib/less/js/lib/less/tree/unicode-descriptor.js +4 -3
  83. data/lib/less/js/lib/less/tree/url.js +33 -11
  84. data/lib/less/js/lib/less/tree/value.js +13 -6
  85. data/lib/less/js/lib/less/tree/variable.js +13 -8
  86. data/lib/less/js/lib/less/visitor.js +117 -25
  87. data/lib/less/js/lib/source-map/source-map-0.1.31.js +1933 -0
  88. data/lib/less/js/lib/source-map/source-map-footer.js +4 -0
  89. data/lib/less/js/lib/source-map/source-map-header.js +3 -0
  90. data/lib/less/js/package.json +30 -15
  91. data/lib/less/js/test/browser/common.js +131 -56
  92. data/lib/less/js/test/browser/css/global-vars/simple.css +3 -0
  93. data/lib/less/js/test/browser/css/modify-vars/simple.css +8 -0
  94. data/lib/less/js/test/browser/css/relative-urls/urls.css +8 -9
  95. data/lib/less/js/test/browser/css/rootpath-relative/urls.css +0 -1
  96. data/lib/less/js/test/browser/css/rootpath/urls.css +0 -1
  97. data/lib/less/js/test/browser/css/urls.css +18 -14
  98. data/lib/less/js/test/browser/es5.js +27 -0
  99. data/lib/less/js/test/{less/errors/color-operation-error.less → browser/less/console-errors/test-error.less} +0 -0
  100. data/lib/less/js/test/browser/less/console-errors/test-error.txt +2 -0
  101. data/lib/less/js/test/browser/less/global-vars/simple.less +3 -0
  102. data/lib/less/js/test/browser/less/modify-vars/imports/simple2.less +4 -0
  103. data/lib/less/js/test/browser/less/modify-vars/simple.less +8 -0
  104. data/lib/less/js/test/browser/less/relative-urls/urls.less +1 -1
  105. data/lib/less/js/test/browser/less/rootpath-relative/urls.less +1 -1
  106. data/lib/less/js/test/browser/less/rootpath/urls.less +1 -1
  107. data/lib/less/js/test/browser/less/urls.less +9 -1
  108. data/lib/less/js/test/browser/phantom-runner.js +112 -103
  109. data/lib/less/js/test/browser/runner-browser-options.js +42 -0
  110. data/lib/less/js/test/browser/{runner-browser.js → runner-browser-spec.js} +7 -2
  111. data/lib/less/js/test/browser/runner-console-errors.js +5 -0
  112. data/lib/less/js/test/browser/runner-errors-options.js +5 -0
  113. data/lib/less/js/test/browser/runner-errors-spec.js +4 -0
  114. data/lib/less/js/test/browser/runner-global-vars-options.js +4 -0
  115. data/lib/less/js/test/browser/runner-global-vars-spec.js +3 -0
  116. data/lib/less/js/test/browser/runner-legacy-options.js +4 -0
  117. data/lib/less/js/test/browser/{runner-legacy.js → runner-legacy-spec.js} +1 -4
  118. data/lib/less/js/test/browser/runner-main-options.js +15 -0
  119. data/lib/less/js/test/browser/runner-main-spec.js +3 -0
  120. data/lib/less/js/test/browser/runner-modify-vars-options.js +2 -0
  121. data/lib/less/js/test/browser/runner-modify-vars-spec.js +43 -0
  122. data/lib/less/js/test/browser/runner-no-js-errors-options.js +4 -0
  123. data/lib/less/js/test/browser/runner-no-js-errors-spec.js +4 -0
  124. data/lib/less/js/test/browser/runner-production-options.js +3 -0
  125. data/lib/less/js/test/browser/{runner-production.js → runner-production-spec.js} +1 -3
  126. data/lib/less/js/test/browser/runner-relative-urls-options.js +3 -0
  127. data/lib/less/js/test/browser/{runner-relative-urls.js → runner-relative-urls-spec.js} +1 -2
  128. data/lib/less/js/test/browser/runner-rootpath-options.js +3 -0
  129. data/lib/less/js/test/browser/runner-rootpath-relative-options.js +4 -0
  130. data/lib/less/js/test/browser/{runner-rootpath-relative.js → runner-rootpath-relative-spec.js} +1 -3
  131. data/lib/less/js/test/browser/{runner-rootpath.js → runner-rootpath-spec.js} +1 -2
  132. data/lib/less/js/test/browser/test-runner-template.tmpl +47 -0
  133. data/lib/less/js/test/css/colors.css +7 -0
  134. data/lib/less/js/test/css/comments.css +9 -4
  135. data/lib/less/js/test/css/compression/compression.css +3 -2
  136. data/lib/less/js/test/css/css-3.css +17 -5
  137. data/lib/less/js/test/css/css-guards.css +37 -0
  138. data/lib/less/js/test/css/debug/linenumbers-all.css +6 -0
  139. data/lib/less/js/test/css/debug/linenumbers-comments.css +5 -0
  140. data/lib/less/js/test/css/debug/linenumbers-mediaquery.css +5 -0
  141. data/lib/less/js/test/css/empty.css +0 -0
  142. data/lib/less/js/test/css/extend-chaining.css +9 -0
  143. data/lib/less/js/test/css/extend-selector.css +10 -2
  144. data/lib/less/js/test/css/extract-and-length.css +133 -0
  145. data/lib/less/js/test/css/functions.css +23 -10
  146. data/lib/less/js/test/css/globalVars/extended.css +12 -0
  147. data/lib/less/js/test/css/globalVars/simple.css +6 -0
  148. data/lib/less/js/test/css/import-inline.css +5 -0
  149. data/lib/less/js/test/css/import-once.css +12 -0
  150. data/lib/less/js/test/css/import-reference.css +49 -0
  151. data/lib/less/js/test/css/import.css +0 -2
  152. data/lib/less/js/test/css/media.css +21 -5
  153. data/lib/less/js/test/css/merge.css +26 -0
  154. data/lib/less/js/test/css/mixins-guards-default-func.css +129 -0
  155. data/lib/less/js/test/css/mixins-guards.css +6 -0
  156. data/lib/less/js/test/css/mixins-important.css +7 -0
  157. data/lib/less/js/test/css/mixins-interpolated.css +39 -0
  158. data/lib/less/js/test/css/mixins.css +20 -0
  159. data/lib/less/js/test/css/modifyVars/extended.css +9 -0
  160. data/lib/less/js/test/css/no-output.css +0 -0
  161. data/lib/less/js/test/css/parens.css +3 -0
  162. data/lib/less/js/test/css/property-name-interp.css +20 -0
  163. data/lib/less/js/test/css/selectors.css +12 -0
  164. data/lib/less/js/test/css/static-urls/urls.css +7 -4
  165. data/lib/less/js/test/css/strings.css +3 -0
  166. data/lib/less/js/test/css/url-args/urls.css +56 -0
  167. data/lib/less/js/test/css/urls.css +21 -9
  168. data/lib/less/js/test/index.js +45 -0
  169. data/lib/less/js/test/less-test.js +234 -191
  170. data/lib/less/js/test/less/colors.less +6 -0
  171. data/lib/less/js/test/less/comments.less +7 -1
  172. data/lib/less/js/test/less/compression/compression.less +21 -1
  173. data/lib/less/js/test/less/css-3.less +12 -0
  174. data/lib/less/js/test/less/css-guards.less +99 -0
  175. data/lib/less/js/test/less/debug/linenumbers.less +11 -1
  176. data/lib/less/js/test/less/empty.less +0 -0
  177. data/lib/less/js/test/less/errors/add-mixed-units.txt +4 -2
  178. data/lib/less/js/test/less/errors/add-mixed-units2.txt +4 -2
  179. data/lib/less/js/test/less/errors/color-func-invalid-color.less +3 -0
  180. data/lib/less/js/test/less/errors/color-func-invalid-color.txt +4 -0
  181. data/lib/less/js/test/less/errors/css-guard-default-func.less +4 -0
  182. data/lib/less/js/test/less/errors/css-guard-default-func.txt +4 -0
  183. data/lib/less/js/test/less/errors/import-subfolder2.txt +4 -2
  184. data/lib/less/js/test/less/errors/javascript-undefined-var.less +3 -0
  185. data/lib/less/js/test/less/errors/javascript-undefined-var.txt +4 -0
  186. data/lib/less/js/test/less/errors/mixins-guards-default-func-1.less +9 -0
  187. data/lib/less/js/test/less/errors/mixins-guards-default-func-1.txt +4 -0
  188. data/lib/less/js/test/less/errors/mixins-guards-default-func-2.less +9 -0
  189. data/lib/less/js/test/less/errors/mixins-guards-default-func-2.txt +4 -0
  190. data/lib/less/js/test/less/errors/mixins-guards-default-func-3.less +9 -0
  191. data/lib/less/js/test/less/errors/mixins-guards-default-func-3.txt +4 -0
  192. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors.less +4 -0
  193. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors.txt +4 -0
  194. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors2.less +4 -0
  195. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors2.txt +4 -0
  196. data/lib/less/js/test/less/errors/parse-error-curly-bracket.less +4 -1
  197. data/lib/less/js/test/less/errors/parse-error-curly-bracket.txt +4 -2
  198. data/lib/less/js/test/less/errors/parse-error-extra-parens.less +5 -0
  199. data/lib/less/js/test/less/errors/parse-error-extra-parens.txt +3 -0
  200. data/lib/less/js/test/less/errors/parse-error-missing-bracket.txt +2 -2
  201. data/lib/less/js/test/less/errors/parse-error-missing-parens.less +5 -0
  202. data/lib/less/js/test/less/errors/parse-error-missing-parens.txt +3 -0
  203. data/lib/less/js/test/less/errors/property-asterisk-only-name.less +3 -0
  204. data/lib/less/js/test/less/errors/property-asterisk-only-name.txt +4 -0
  205. data/lib/less/js/test/less/errors/property-interp-not-defined.less +1 -0
  206. data/lib/less/js/test/less/errors/property-interp-not-defined.txt +2 -0
  207. data/lib/less/js/test/less/errors/svg-gradient1.less +3 -0
  208. data/lib/less/js/test/less/errors/svg-gradient1.txt +4 -0
  209. data/lib/less/js/test/less/errors/svg-gradient2.less +3 -0
  210. data/lib/less/js/test/less/errors/svg-gradient2.txt +4 -0
  211. data/lib/less/js/test/less/errors/svg-gradient3.less +3 -0
  212. data/lib/less/js/test/less/errors/svg-gradient3.txt +4 -0
  213. data/lib/less/js/test/less/errors/unit-function.less +3 -0
  214. data/lib/less/js/test/less/errors/unit-function.txt +4 -0
  215. data/lib/less/js/test/less/extend-chaining.less +12 -0
  216. data/lib/less/js/test/less/extend-selector.less +15 -0
  217. data/lib/less/js/test/less/extract-and-length.less +133 -0
  218. data/lib/less/js/test/less/functions.less +15 -2
  219. data/lib/less/js/test/less/globalVars/extended.json +5 -0
  220. data/lib/less/js/test/less/globalVars/extended.less +10 -0
  221. data/lib/less/js/test/less/globalVars/simple.json +3 -0
  222. data/lib/less/js/test/less/globalVars/simple.less +3 -0
  223. data/lib/less/js/test/less/import-inline.less +2 -0
  224. data/lib/less/js/test/less/import-once.less +2 -0
  225. data/lib/less/js/test/less/import-reference.less +18 -0
  226. data/lib/less/js/test/less/import/import-and-relative-paths-test.less +11 -0
  227. data/lib/less/js/test/less/import/import-reference.less +43 -0
  228. data/lib/less/js/test/less/import/import-test-f.less +5 -0
  229. data/lib/less/js/test/less/import/invalid-css.less +1 -0
  230. data/lib/less/js/test/less/media.less +25 -1
  231. data/lib/less/js/test/less/merge.less +59 -0
  232. data/lib/less/js/test/less/mixins-args.less +10 -0
  233. data/lib/less/js/test/less/mixins-guards-default-func.less +195 -0
  234. data/lib/less/js/test/less/mixins-guards.less +16 -0
  235. data/lib/less/js/test/less/mixins-important.less +4 -1
  236. data/lib/less/js/test/less/mixins-interpolated.less +69 -0
  237. data/lib/less/js/test/less/mixins.less +27 -0
  238. data/lib/less/js/test/less/modifyVars/extended.json +5 -0
  239. data/lib/less/js/test/less/modifyVars/extended.less +11 -0
  240. data/lib/less/js/test/less/no-js-errors/no-js-errors.less +3 -0
  241. data/lib/less/js/test/less/no-js-errors/no-js-errors.txt +4 -0
  242. data/lib/less/js/test/less/no-output.less +2 -0
  243. data/lib/less/js/test/less/parens.less +4 -0
  244. data/lib/less/js/test/less/property-name-interp.less +53 -0
  245. data/lib/less/js/test/less/selectors.less +13 -0
  246. data/lib/less/js/test/less/sourcemaps/basic.json +3 -0
  247. data/lib/less/js/test/less/sourcemaps/basic.less +27 -0
  248. data/lib/less/js/test/less/sourcemaps/imported.css +7 -0
  249. data/lib/less/js/test/less/strings.less +6 -0
  250. data/lib/less/js/test/less/url-args/urls.less +63 -0
  251. data/lib/less/js/test/less/urls.less +15 -0
  252. data/lib/less/js/test/rhino/test-header.js +15 -0
  253. data/lib/less/js/test/sourcemaps/basic.json +1 -0
  254. data/lib/less/js/test/sourcemaps/index.html +17 -0
  255. data/lib/less/loader.rb +48 -40
  256. data/lib/less/version.rb +1 -1
  257. data/spec/less/parser_spec.rb +15 -15
  258. metadata +146 -40
  259. data/lib/less/js/Makefile +0 -102
  260. data/lib/less/js/build/header.js +0 -9
  261. data/lib/less/js/test/browser-test-prepare.js +0 -46
  262. data/lib/less/js/test/browser/runner-errors.js +0 -5
  263. data/lib/less/js/test/browser/runner-main.js +0 -15
  264. data/lib/less/js/test/browser/template.htm +0 -10
  265. data/lib/less/js/test/less/errors/color-operation-error.txt +0 -2
@@ -1,23 +1,10 @@
1
- var less, tree, charset;
2
-
3
- if (typeof environment === "object" && ({}).toString.call(environment) === "[object Environment]") {
4
- // Rhino
5
- // Details on how to detect Rhino: https://github.com/ringo/ringojs/issues/88
6
- if (typeof(window) === 'undefined') { less = {} }
7
- else { less = window.less = {} }
8
- tree = less.tree = {};
9
- less.mode = 'rhino';
10
- } else if (typeof(window) === 'undefined') {
11
- // Node.js
12
- less = exports,
1
+ var less, tree;
2
+
3
+ // Node.js does not have a header file added which defines less
4
+ if (less === undefined) {
5
+ less = exports;
13
6
  tree = require('./tree');
14
7
  less.mode = 'node';
15
- } else {
16
- // Browser
17
- if (typeof(window.less) === 'undefined') { window.less = {} }
18
- less = window.less,
19
- tree = window.less.tree = {};
20
- less.mode = 'browser';
21
8
  }
22
9
  //
23
10
  // less.js - parser
@@ -32,7 +19,7 @@ if (typeof environment === "object" && ({}).toString.call(environment) === "[obj
32
19
  // - Matching and slicing on a huge input is often cause of slowdowns.
33
20
  // The solution is to chunkify the input into smaller strings.
34
21
  // The chunks are stored in the `chunks` var,
35
- // `j` holds the current chunk index, and `current` holds
22
+ // `j` holds the current chunk index, and `currentPos` holds
36
23
  // the index of the current chunk in relation to `input`.
37
24
  // This gives us an almost 4x speed-up.
38
25
  //
@@ -60,10 +47,11 @@ less.Parser = function Parser(env) {
60
47
  memo, // temporarily holds `i`, when backtracking
61
48
  furthest, // furthest index the parser has gone to
62
49
  chunks, // chunkified input
63
- current, // index of current chunk, in `input`
64
- parser;
65
-
66
- var that = this;
50
+ current, // current chunk
51
+ currentPos, // index of current chunk, in `input`
52
+ parser,
53
+ parsers,
54
+ rootFilename = env && env.filename;
67
55
 
68
56
  // Top parser on an import tree must be sure there is one "env"
69
57
  // which will then be passed around by reference.
@@ -76,115 +64,184 @@ less.Parser = function Parser(env) {
76
64
  queue: [], // Files which haven't been imported yet
77
65
  files: env.files, // Holds the imported parse trees
78
66
  contents: env.contents, // Holds the imported file contents
67
+ contentsIgnoredChars: env.contentsIgnoredChars, // lines inserted, not in the original less
79
68
  mime: env.mime, // MIME type of .less files
80
69
  error: null, // Error in parsing/evaluating an import
81
- push: function (path, currentFileInfo, callback) {
82
- var parserImporter = this;
70
+ push: function (path, currentFileInfo, importOptions, callback) {
71
+ var parserImports = this;
83
72
  this.queue.push(path);
84
73
 
85
- //
86
- // Import a file asynchronously
87
- //
88
- less.Parser.importer(path, currentFileInfo, function (e, root, fullPath) {
89
- parserImporter.queue.splice(parserImporter.queue.indexOf(path), 1); // Remove the path from the queue
74
+ var fileParsedFunc = function (e, root, fullPath) {
75
+ parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
90
76
 
91
- var imported = fullPath in parserImporter.files;
77
+ var importedPreviously = fullPath in parserImports.files || fullPath === rootFilename;
92
78
 
93
- parserImporter.files[fullPath] = root; // Store the root
79
+ parserImports.files[fullPath] = root; // Store the root
94
80
 
95
- if (e && !parserImporter.error) { parserImporter.error = e; }
96
-
97
- callback(e, root, imported);
98
- }, env);
81
+ if (e && !parserImports.error) { parserImports.error = e; }
82
+
83
+ callback(e, root, importedPreviously, fullPath);
84
+ };
85
+
86
+ if (less.Parser.importer) {
87
+ less.Parser.importer(path, currentFileInfo, fileParsedFunc, env);
88
+ } else {
89
+ less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) {
90
+ if (e) {fileParsedFunc(e); return;}
91
+
92
+ var newEnv = new tree.parseEnv(env);
93
+
94
+ newEnv.currentFileInfo = newFileInfo;
95
+ newEnv.processImports = false;
96
+ newEnv.contents[fullPath] = contents;
97
+
98
+ if (currentFileInfo.reference || importOptions.reference) {
99
+ newFileInfo.reference = true;
100
+ }
101
+
102
+ if (importOptions.inline) {
103
+ fileParsedFunc(null, contents, fullPath);
104
+ } else {
105
+ new(less.Parser)(newEnv).parse(contents, function (e, root) {
106
+ fileParsedFunc(e, root, fullPath);
107
+ });
108
+ }
109
+ }, env);
110
+ }
99
111
  }
100
112
  };
101
113
 
102
- function save() { temp = chunks[j], memo = i, current = i; }
103
- function restore() { chunks[j] = temp, i = memo, current = i; }
114
+ function save() { temp = current; memo = currentPos = i; }
115
+ function restore() { current = temp; currentPos = i = memo; }
104
116
 
105
117
  function sync() {
106
- if (i > current) {
107
- chunks[j] = chunks[j].slice(i - current);
108
- current = i;
118
+ if (i > currentPos) {
119
+ current = current.slice(i - currentPos);
120
+ currentPos = i;
109
121
  }
110
122
  }
111
- function isWhitespace(c) {
112
- // Could change to \s?
113
- var code = c.charCodeAt(0);
114
- return code === 32 || code === 10 || code === 9;
123
+ function isWhitespace(str, pos) {
124
+ var code = str.charCodeAt(pos | 0);
125
+ return (code <= 32) && (code === 32 || code === 10 || code === 9);
115
126
  }
116
127
  //
117
128
  // Parse from a token, regexp or string, and move forward if match
118
129
  //
119
130
  function $(tok) {
120
- var match, args, length, index, k;
131
+ var tokType = typeof tok,
132
+ match, length;
121
133
 
134
+ // Either match a single character in the input,
135
+ // or match a regexp in the current chunk (`current`).
122
136
  //
123
- // Non-terminal
124
- //
125
- if (tok instanceof Function) {
126
- return tok.call(parser.parsers);
127
- //
128
- // Terminal
129
- //
130
- // Either match a single character in the input,
131
- // or match a regexp in the current chunk (chunk[j]).
132
- //
133
- } else if (typeof(tok) === 'string') {
134
- match = input.charAt(i) === tok ? tok : null;
135
- length = 1;
136
- sync ();
137
- } else {
138
- sync ();
139
-
140
- if (match = tok.exec(chunks[j])) {
141
- length = match[0].length;
142
- } else {
137
+ if (tokType === "string") {
138
+ if (input.charAt(i) !== tok) {
143
139
  return null;
144
140
  }
141
+ skipWhitespace(1);
142
+ return tok;
143
+ }
144
+
145
+ // regexp
146
+ sync ();
147
+ if (! (match = tok.exec(current))) {
148
+ return null;
145
149
  }
146
150
 
151
+ length = match[0].length;
152
+
147
153
  // The match is confirmed, add the match length to `i`,
148
154
  // and consume any extra white-space characters (' ' || '\n')
149
155
  // which come after that. The reason for this is that LeSS's
150
156
  // grammar is mostly white-space insensitive.
151
157
  //
152
- if (match) {
153
- skipWhitespace(length);
158
+ skipWhitespace(length);
154
159
 
155
- if(typeof(match) === 'string') {
156
- return match;
157
- } else {
158
- return match.length === 1 ? match[0] : match;
159
- }
160
+ if(typeof(match) === 'string') {
161
+ return match;
162
+ } else {
163
+ return match.length === 1 ? match[0] : match;
160
164
  }
161
165
  }
162
166
 
167
+ // Specialization of $(tok)
168
+ function $re(tok) {
169
+ if (i > currentPos) {
170
+ current = current.slice(i - currentPos);
171
+ currentPos = i;
172
+ }
173
+ var m = tok.exec(current);
174
+ if (!m) {
175
+ return null;
176
+ }
177
+
178
+ skipWhitespace(m[0].length);
179
+ if(typeof m === "string") {
180
+ return m;
181
+ }
182
+
183
+ return m.length === 1 ? m[0] : m;
184
+ }
185
+
186
+ var _$re = $re;
187
+
188
+ // Specialization of $(tok)
189
+ function $char(tok) {
190
+ if (input.charAt(i) !== tok) {
191
+ return null;
192
+ }
193
+ skipWhitespace(1);
194
+ return tok;
195
+ }
196
+
163
197
  function skipWhitespace(length) {
164
198
  var oldi = i, oldj = j,
165
- endIndex = i + chunks[j].length,
166
- mem = i += length;
199
+ curr = i - currentPos,
200
+ endIndex = i + current.length - curr,
201
+ mem = (i += length),
202
+ inp = input,
203
+ c;
204
+
205
+ for (; i < endIndex; i++) {
206
+ c = inp.charCodeAt(i);
207
+ if (c > 32) {
208
+ break;
209
+ }
167
210
 
168
- while (i < endIndex) {
169
- if (! isWhitespace(input.charAt(i))) { break }
170
- i++;
171
- }
172
- chunks[j] = chunks[j].slice(length + (i - mem));
173
- current = i;
211
+ if ((c !== 32) && (c !== 10) && (c !== 9) && (c !== 13)) {
212
+ break;
213
+ }
214
+ }
215
+
216
+ current = current.slice(length + i - mem + curr);
217
+ currentPos = i;
174
218
 
175
- if (chunks[j].length === 0 && j < chunks.length - 1) { j++ }
219
+ if (!current.length && (j < chunks.length - 1)) {
220
+ current = chunks[++j];
221
+ skipWhitespace(0); // skip space at the beginning of a chunk
222
+ return true; // things changed
223
+ }
176
224
 
177
225
  return oldi !== i || oldj !== j;
178
226
  }
179
227
 
180
228
  function expect(arg, msg) {
181
- var result = $(arg);
182
- if (! result) {
183
- error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
184
- : "unexpected token"));
185
- } else {
229
+ // some older browsers return typeof 'function' for RegExp
230
+ var result = (Object.prototype.toString.call(arg) === '[object Function]') ? arg.call(parsers) : $(arg);
231
+ if (result) {
186
232
  return result;
187
233
  }
234
+ error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
235
+ : "unexpected token"));
236
+ }
237
+
238
+ // Specialization of expect()
239
+ function expectChar(arg, msg) {
240
+ if (input.charAt(i) === arg) {
241
+ skipWhitespace(1);
242
+ return arg;
243
+ }
244
+ error(msg || "expected '" + arg + "' got '" + input.charAt(i) + "'");
188
245
  }
189
246
 
190
247
  function error(msg, type) {
@@ -200,14 +257,16 @@ less.Parser = function Parser(env) {
200
257
  if (typeof(tok) === 'string') {
201
258
  return input.charAt(i) === tok;
202
259
  } else {
203
- if (tok.test(chunks[j])) {
204
- return true;
205
- } else {
206
- return false;
207
- }
260
+ return tok.test(current);
208
261
  }
209
262
  }
210
263
 
264
+ // Specialization of peek()
265
+ function peekChar(tok) {
266
+ return input.charAt(i) === tok;
267
+ }
268
+
269
+
211
270
  function getInput(e, env) {
212
271
  if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) {
213
272
  return parser.imports.contents[e.filename];
@@ -216,13 +275,23 @@ less.Parser = function Parser(env) {
216
275
  }
217
276
  }
218
277
 
219
- function getLocation(index, input) {
220
- for (var n = index, column = -1;
221
- n >= 0 && input.charAt(n) !== '\n';
222
- n--) { column++ }
278
+ function getLocation(index, inputStream) {
279
+ var n = index + 1,
280
+ line = null,
281
+ column = -1;
282
+
283
+ while (--n >= 0 && inputStream.charAt(n) !== '\n') {
284
+ column++;
285
+ }
223
286
 
224
- return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null,
225
- column: column };
287
+ if (typeof index === 'number') {
288
+ line = (inputStream.slice(0, index).match(/\n/g) || "").length;
289
+ }
290
+
291
+ return {
292
+ line: line,
293
+ column: column
294
+ };
226
295
  }
227
296
 
228
297
  function getDebugInfo(index, inputStream, env) {
@@ -242,6 +311,7 @@ less.Parser = function Parser(env) {
242
311
  loc = getLocation(e.index, input),
243
312
  line = loc.line,
244
313
  col = loc.column,
314
+ callLine = e.call && getLocation(e.call, input).line,
245
315
  lines = input.split('\n');
246
316
 
247
317
  this.type = e.type || 'Syntax';
@@ -249,8 +319,8 @@ less.Parser = function Parser(env) {
249
319
  this.filename = e.filename || env.currentFileInfo.filename;
250
320
  this.index = e.index;
251
321
  this.line = typeof(line) === 'number' ? line + 1 : null;
252
- this.callLine = e.call && (getLocation(e.call, input).line + 1);
253
- this.callExtract = lines[getLocation(e.call, input).line];
322
+ this.callLine = callLine + 1;
323
+ this.callExtract = lines[callLine];
254
324
  this.stack = e.stack;
255
325
  this.column = col;
256
326
  this.extract = [
@@ -274,97 +344,167 @@ less.Parser = function Parser(env) {
274
344
  //
275
345
  // The Parser
276
346
  //
277
- return parser = {
347
+ parser = {
278
348
 
279
349
  imports: imports,
280
350
  //
281
351
  // Parse an input string into an abstract syntax tree,
282
- // call `callback` when done.
352
+ // @param str A string containing 'less' markup
353
+ // @param callback call `callback` when done.
354
+ // @param [additionalData] An optional map which can contains vars - a map (key, value) of variables to apply
283
355
  //
284
- parse: function (str, callback) {
285
- var root, start, end, zone, line, lines, buff = [], c, error = null;
356
+ parse: function (str, callback, additionalData) {
357
+ var root, line, lines, error = null, globalVars, modifyVars, preText = "";
358
+
359
+ i = j = currentPos = furthest = 0;
286
360
 
287
- i = j = current = furthest = 0;
288
- input = str.replace(/\r\n/g, '\n');
361
+ globalVars = (additionalData && additionalData.globalVars) ? less.Parser.serializeVars(additionalData.globalVars) + '\n' : '';
362
+ modifyVars = (additionalData && additionalData.modifyVars) ? '\n' + less.Parser.serializeVars(additionalData.modifyVars) : '';
363
+
364
+ if (globalVars || (additionalData && additionalData.banner)) {
365
+ preText = ((additionalData && additionalData.banner) ? additionalData.banner : "") + globalVars;
366
+ parser.imports.contentsIgnoredChars[env.currentFileInfo.filename] = preText.length;
367
+ }
289
368
 
369
+ str = str.replace(/\r\n/g, '\n');
290
370
  // Remove potential UTF Byte Order Mark
291
- input = input.replace(/^\uFEFF/, '');
371
+ input = str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
372
+ parser.imports.contents[env.currentFileInfo.filename] = str;
292
373
 
293
374
  // Split the input into chunks.
294
- chunks = (function (chunks) {
295
- var j = 0,
296
- skip = /(?:@\{[\w-]+\}|[^"'`\{\}\/\(\)\\])+/g,
297
- comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
298
- string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`]|\\.)*)`/g,
299
- level = 0,
300
- match,
301
- chunk = chunks[0],
302
- inParam;
303
-
304
- for (var i = 0, c, cc; i < input.length;) {
305
- skip.lastIndex = i;
306
- if (match = skip.exec(input)) {
307
- if (match.index === i) {
308
- i += match[0].length;
309
- chunk.push(match[0]);
310
- }
375
+ chunks = (function (input) {
376
+ var len = input.length, level = 0, parenLevel = 0,
377
+ lastOpening, lastOpeningParen, lastMultiComment, lastMultiCommentEndBrace,
378
+ chunks = [], emitFrom = 0,
379
+ parserCurrentIndex, currentChunkStartIndex, cc, cc2, matched;
380
+
381
+ function fail(msg, index) {
382
+ error = new(LessError)({
383
+ index: index || parserCurrentIndex,
384
+ type: 'Parse',
385
+ message: msg,
386
+ filename: env.currentFileInfo.filename
387
+ }, env);
388
+ }
389
+
390
+ function emitChunk(force) {
391
+ var len = parserCurrentIndex - emitFrom;
392
+ if (((len < 512) && !force) || !len) {
393
+ return;
311
394
  }
312
- c = input.charAt(i);
313
- comment.lastIndex = string.lastIndex = i;
395
+ chunks.push(input.slice(emitFrom, parserCurrentIndex + 1));
396
+ emitFrom = parserCurrentIndex + 1;
397
+ }
314
398
 
315
- if (match = string.exec(input)) {
316
- if (match.index === i) {
317
- i += match[0].length;
318
- chunk.push(match[0]);
319
- continue;
320
- }
399
+ for (parserCurrentIndex = 0; parserCurrentIndex < len; parserCurrentIndex++) {
400
+ cc = input.charCodeAt(parserCurrentIndex);
401
+ if (((cc >= 97) && (cc <= 122)) || (cc < 34)) {
402
+ // a-z or whitespace
403
+ continue;
321
404
  }
322
405
 
323
- if (!inParam && c === '/') {
324
- cc = input.charAt(i + 1);
325
- if (cc === '/' || cc === '*') {
326
- if (match = comment.exec(input)) {
327
- if (match.index === i) {
328
- i += match[0].length;
329
- chunk.push(match[0]);
330
- continue;
406
+ switch (cc) {
407
+ case 40: // (
408
+ parenLevel++;
409
+ lastOpeningParen = parserCurrentIndex;
410
+ continue;
411
+ case 41: // )
412
+ if (--parenLevel < 0) {
413
+ return fail("missing opening `(`");
414
+ }
415
+ continue;
416
+ case 59: // ;
417
+ if (!parenLevel) { emitChunk(); }
418
+ continue;
419
+ case 123: // {
420
+ level++;
421
+ lastOpening = parserCurrentIndex;
422
+ continue;
423
+ case 125: // }
424
+ if (--level < 0) {
425
+ return fail("missing opening `{`");
426
+ }
427
+ if (!level) { emitChunk(); }
428
+ continue;
429
+ case 92: // \
430
+ if (parserCurrentIndex < len - 1) { parserCurrentIndex++; continue; }
431
+ return fail("unescaped `\\`");
432
+ case 34:
433
+ case 39:
434
+ case 96: // ", ' and `
435
+ matched = 0;
436
+ currentChunkStartIndex = parserCurrentIndex;
437
+ for (parserCurrentIndex = parserCurrentIndex + 1; parserCurrentIndex < len; parserCurrentIndex++) {
438
+ cc2 = input.charCodeAt(parserCurrentIndex);
439
+ if (cc2 > 96) { continue; }
440
+ if (cc2 == cc) { matched = 1; break; }
441
+ if (cc2 == 92) { // \
442
+ if (parserCurrentIndex == len - 1) {
443
+ return fail("unescaped `\\`");
444
+ }
445
+ parserCurrentIndex++;
331
446
  }
332
447
  }
333
- }
334
- }
335
-
336
- switch (c) {
337
- case '{': if (! inParam) { level ++; chunk.push(c); break }
338
- case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break }
339
- case '(': if (! inParam) { inParam = true; chunk.push(c); break }
340
- case ')': if ( inParam) { inParam = false; chunk.push(c); break }
341
- default: chunk.push(c);
448
+ if (matched) { continue; }
449
+ return fail("unmatched `" + String.fromCharCode(cc) + "`", currentChunkStartIndex);
450
+ case 47: // /, check for comment
451
+ if (parenLevel || (parserCurrentIndex == len - 1)) { continue; }
452
+ cc2 = input.charCodeAt(parserCurrentIndex + 1);
453
+ if (cc2 == 47) {
454
+ // //, find lnfeed
455
+ for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len; parserCurrentIndex++) {
456
+ cc2 = input.charCodeAt(parserCurrentIndex);
457
+ if ((cc2 <= 13) && ((cc2 == 10) || (cc2 == 13))) { break; }
458
+ }
459
+ } else if (cc2 == 42) {
460
+ // /*, find */
461
+ lastMultiComment = currentChunkStartIndex = parserCurrentIndex;
462
+ for (parserCurrentIndex = parserCurrentIndex + 2; parserCurrentIndex < len - 1; parserCurrentIndex++) {
463
+ cc2 = input.charCodeAt(parserCurrentIndex);
464
+ if (cc2 == 125) { lastMultiCommentEndBrace = parserCurrentIndex; }
465
+ if (cc2 != 42) { continue; }
466
+ if (input.charCodeAt(parserCurrentIndex + 1) == 47) { break; }
467
+ }
468
+ if (parserCurrentIndex == len - 1) {
469
+ return fail("missing closing `*/`", currentChunkStartIndex);
470
+ }
471
+ parserCurrentIndex++;
472
+ }
473
+ continue;
474
+ case 42: // *, check for unmatched */
475
+ if ((parserCurrentIndex < len - 1) && (input.charCodeAt(parserCurrentIndex + 1) == 47)) {
476
+ return fail("unmatched `/*`");
477
+ }
478
+ continue;
342
479
  }
343
-
344
- i++;
345
480
  }
346
- if (level != 0) {
347
- error = new(LessError)({
348
- index: i-1,
349
- type: 'Parse',
350
- message: (level > 0) ? "missing closing `}`" : "missing opening `{`",
351
- filename: env.currentFileInfo.filename
352
- }, env);
481
+
482
+ if (level !== 0) {
483
+ if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) {
484
+ return fail("missing closing `}` or `*/`", lastOpening);
485
+ } else {
486
+ return fail("missing closing `}`", lastOpening);
487
+ }
488
+ } else if (parenLevel !== 0) {
489
+ return fail("missing closing `)`", lastOpeningParen);
353
490
  }
354
491
 
355
- return chunks.map(function (c) { return c.join('') });;
356
- })([[]]);
492
+ emitChunk(true);
493
+ return chunks;
494
+ })(str);
357
495
 
358
496
  if (error) {
359
497
  return callback(new(LessError)(error, env));
360
498
  }
361
499
 
500
+ current = chunks[0];
501
+
362
502
  // Start with the primary rule.
363
503
  // The whole syntax tree is held under a Ruleset node,
364
504
  // with the `root` property set to true, so no `{}` are
365
505
  // output. The callback is called when the input is parsed.
366
506
  try {
367
- root = new(tree.Ruleset)([], $(this.parsers.primary));
507
+ root = new(tree.Ruleset)(null, this.parsers.primary());
368
508
  root.root = true;
369
509
  root.firstRoot = true;
370
510
  } catch (e) {
@@ -372,11 +512,10 @@ less.Parser = function Parser(env) {
372
512
  }
373
513
 
374
514
  root.toCSS = (function (evaluate) {
375
- var line, lines, column;
376
-
377
515
  return function (options, variables) {
378
516
  options = options || {};
379
- var importError,
517
+ var evaldRoot,
518
+ css,
380
519
  evalEnv = new tree.evalEnv(options);
381
520
 
382
521
  //
@@ -402,32 +541,85 @@ less.Parser = function Parser(env) {
402
541
  }
403
542
  value = new(tree.Value)([value]);
404
543
  }
405
- return new(tree.Rule)('@' + k, value, false, 0);
544
+ return new(tree.Rule)('@' + k, value, false, null, 0);
406
545
  });
407
546
  evalEnv.frames = [new(tree.Ruleset)(null, variables)];
408
547
  }
409
548
 
410
549
  try {
411
- var evaldRoot = evaluate.call(this, evalEnv);
550
+ var preEvalVisitors = [],
551
+ visitors = [
552
+ new(tree.joinSelectorVisitor)(),
553
+ new(tree.processExtendsVisitor)(),
554
+ new(tree.toCSSVisitor)({compress: Boolean(options.compress)})
555
+ ], i, root = this;
556
+
557
+ if (options.plugins) {
558
+ for(i =0; i < options.plugins.length; i++) {
559
+ if (options.plugins[i].isPreEvalVisitor) {
560
+ preEvalVisitors.push(options.plugins[i]);
561
+ } else {
562
+ if (options.plugins[i].isPreVisitor) {
563
+ visitors.splice(0, 0, options.plugins[i]);
564
+ } else {
565
+ visitors.push(options.plugins[i]);
566
+ }
567
+ }
568
+ }
569
+ }
412
570
 
413
- new(tree.joinSelectorVisitor)()
414
- .run(evaldRoot);
571
+ for(i = 0; i < preEvalVisitors.length; i++) {
572
+ preEvalVisitors[i].run(root);
573
+ }
574
+
575
+ evaldRoot = evaluate.call(root, evalEnv);
415
576
 
416
- new(tree.processExtendsVisitor)()
417
- .run(evaldRoot);
577
+ for(i = 0; i < visitors.length; i++) {
578
+ visitors[i].run(evaldRoot);
579
+ }
418
580
 
419
- var css = evaldRoot.toCSS({
581
+ if (options.sourceMap) {
582
+ evaldRoot = new tree.sourceMapOutput(
583
+ {
584
+ contentsIgnoredCharsMap: parser.imports.contentsIgnoredChars,
585
+ writeSourceMap: options.writeSourceMap,
586
+ rootNode: evaldRoot,
587
+ contentsMap: parser.imports.contents,
588
+ sourceMapFilename: options.sourceMapFilename,
589
+ sourceMapURL: options.sourceMapURL,
590
+ outputFilename: options.sourceMapOutputFilename,
591
+ sourceMapBasepath: options.sourceMapBasepath,
592
+ sourceMapRootpath: options.sourceMapRootpath,
593
+ outputSourceFiles: options.outputSourceFiles,
594
+ sourceMapGenerator: options.sourceMapGenerator
595
+ });
596
+ }
597
+
598
+ css = evaldRoot.toCSS({
420
599
  compress: Boolean(options.compress),
421
600
  dumpLineNumbers: env.dumpLineNumbers,
422
- strictUnits: Boolean(options.strictUnits)});
601
+ strictUnits: Boolean(options.strictUnits),
602
+ numPrecision: 8});
423
603
  } catch (e) {
424
604
  throw new(LessError)(e, env);
425
605
  }
426
606
 
427
- if (options.yuicompress && less.mode === 'node') {
428
- return require('ycssmin').cssmin(css, options.maxLineLen);
607
+ if (options.cleancss && less.mode === 'node') {
608
+ var CleanCSS = require('clean-css'),
609
+ cleancssOptions = options.cleancssOptions || {};
610
+
611
+ if (cleancssOptions.keepSpecialComments === undefined) {
612
+ cleancssOptions.keepSpecialComments = "*";
613
+ }
614
+ cleancssOptions.processImport = false;
615
+ cleancssOptions.noRebase = true;
616
+ if (cleancssOptions.noAdvanced === undefined) {
617
+ cleancssOptions.noAdvanced = true;
618
+ }
619
+
620
+ return new CleanCSS(cleancssOptions).minify(css);
429
621
  } else if (options.compress) {
430
- return css.replace(/(\s)+/g, "$1");
622
+ return css.replace(/(^(\s)+)|((\s)+$)/g, "");
431
623
  } else {
432
624
  return css;
433
625
  }
@@ -444,10 +636,9 @@ less.Parser = function Parser(env) {
444
636
  // and the part which didn't), so we can color them differently.
445
637
  if (i < input.length - 1) {
446
638
  i = furthest;
639
+ var loc = getLocation(i, input);
447
640
  lines = input.split('\n');
448
- line = (input.slice(0, i).match(/\n/g) || "").length + 1;
449
-
450
- for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
641
+ line = loc.line + 1;
451
642
 
452
643
  error = {
453
644
  type: "Parse",
@@ -455,7 +646,7 @@ less.Parser = function Parser(env) {
455
646
  index: i,
456
647
  filename: env.currentFileInfo.filename,
457
648
  line: line,
458
- column: column,
649
+ column: loc.column,
459
650
  extract: [
460
651
  lines[line - 2],
461
652
  lines[line - 1],
@@ -472,10 +663,10 @@ less.Parser = function Parser(env) {
472
663
  e = new(LessError)(e, env);
473
664
  }
474
665
 
475
- callback(e);
666
+ return callback(e);
476
667
  }
477
668
  else {
478
- callback(null, root);
669
+ return callback(null, root);
479
670
  }
480
671
  };
481
672
 
@@ -483,7 +674,7 @@ less.Parser = function Parser(env) {
483
674
  new tree.importVisitor(this.imports, finish)
484
675
  .run(root);
485
676
  } else {
486
- finish();
677
+ return finish();
487
678
  }
488
679
  },
489
680
 
@@ -516,7 +707,7 @@ less.Parser = function Parser(env) {
516
707
  // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
517
708
  // first, before parsing, that's when we use `peek()`.
518
709
  //
519
- parsers: {
710
+ parsers: parsers = {
520
711
  //
521
712
  // The `primary` rule is the *entry* and *exit* point of the parser.
522
713
  // The rules here can appear at any level of the parse tree.
@@ -533,13 +724,21 @@ less.Parser = function Parser(env) {
533
724
  // block rule: at the root level.
534
725
  //
535
726
  primary: function () {
536
- var node, root = [];
727
+ var mixin = this.mixin, $re = _$re, root = [], node;
537
728
 
538
- while ((node = $(this.extendRule) || $(this.mixin.definition) || $(this.rule) || $(this.ruleset) ||
539
- $(this.mixin.call) || $(this.comment) || $(this.directive))
540
- || $(/^[\s\n]+/) || $(/^;+/)) {
541
- node && root.push(node);
729
+ while (current)
730
+ {
731
+ node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
732
+ mixin.call() || this.comment() || this.directive();
733
+ if (node) {
734
+ root.push(node);
735
+ } else {
736
+ if (!($re(/^[\s\n]+/) || $re(/^;+/))) {
737
+ break;
738
+ }
739
+ }
542
740
  }
741
+
543
742
  return root;
544
743
  },
545
744
 
@@ -549,13 +748,29 @@ less.Parser = function Parser(env) {
549
748
  comment: function () {
550
749
  var comment;
551
750
 
552
- if (input.charAt(i) !== '/') return;
751
+ if (input.charAt(i) !== '/') { return; }
553
752
 
554
753
  if (input.charAt(i + 1) === '/') {
555
- return new(tree.Comment)($(/^\/\/.*/), true);
556
- } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
557
- return new(tree.Comment)(comment);
754
+ return new(tree.Comment)($re(/^\/\/.*/), true, i, env.currentFileInfo);
755
+ }
756
+ comment = $re(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/);
757
+ if (comment) {
758
+ return new(tree.Comment)(comment, false, i, env.currentFileInfo);
759
+ }
760
+ },
761
+
762
+ comments: function () {
763
+ var comment, comments = [];
764
+
765
+ while(true) {
766
+ comment = this.comment();
767
+ if (!comment) {
768
+ break;
769
+ }
770
+ comments.push(comment);
558
771
  }
772
+
773
+ return comments;
559
774
  },
560
775
 
561
776
  //
@@ -570,12 +785,13 @@ less.Parser = function Parser(env) {
570
785
  quoted: function () {
571
786
  var str, j = i, e, index = i;
572
787
 
573
- if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
574
- if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return;
788
+ if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings
789
+ if (input.charAt(j) !== '"' && input.charAt(j) !== "'") { return; }
575
790
 
576
- e && $('~');
791
+ if (e) { $char('~'); }
577
792
 
578
- if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
793
+ str = $re(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
794
+ if (str) {
579
795
  return new(tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo);
580
796
  }
581
797
  },
@@ -588,13 +804,13 @@ less.Parser = function Parser(env) {
588
804
  keyword: function () {
589
805
  var k;
590
806
 
591
- if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
592
- if (tree.colors.hasOwnProperty(k)) {
593
- // detect named color
594
- return new(tree.Color)(tree.colors[k].slice(1));
595
- } else {
596
- return new(tree.Keyword)(k);
807
+ k = $re(/^[_A-Za-z-][_A-Za-z0-9-]*/);
808
+ if (k) {
809
+ var color = tree.Color.fromKeyword(k);
810
+ if (color) {
811
+ return color;
597
812
  }
813
+ return new(tree.Keyword)(k);
598
814
  }
599
815
  },
600
816
 
@@ -611,26 +827,29 @@ less.Parser = function Parser(env) {
611
827
  call: function () {
612
828
  var name, nameLC, args, alpha_ret, index = i;
613
829
 
614
- if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) return;
830
+ name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(current);
831
+ if (!name) { return; }
615
832
 
616
833
  name = name[1];
617
834
  nameLC = name.toLowerCase();
835
+ if (nameLC === 'url') {
836
+ return null;
837
+ }
618
838
 
619
- if (nameLC === 'url') { return null }
620
- else { i += name.length }
839
+ i += name.length;
621
840
 
622
841
  if (nameLC === 'alpha') {
623
- alpha_ret = $(this.alpha);
842
+ alpha_ret = parsers.alpha();
624
843
  if(typeof alpha_ret !== 'undefined') {
625
844
  return alpha_ret;
626
845
  }
627
846
  }
628
847
 
629
- $('('); // Parse the '(' and consume whitespace.
848
+ $char('('); // Parse the '(' and consume whitespace.
630
849
 
631
- args = $(this.entities.arguments);
850
+ args = this.arguments();
632
851
 
633
- if (! $(')')) {
852
+ if (! $char(')')) {
634
853
  return;
635
854
  }
636
855
 
@@ -639,17 +858,23 @@ less.Parser = function Parser(env) {
639
858
  arguments: function () {
640
859
  var args = [], arg;
641
860
 
642
- while (arg = $(this.entities.assignment) || $(this.expression)) {
861
+ while (true) {
862
+ arg = this.assignment() || parsers.expression();
863
+ if (!arg) {
864
+ break;
865
+ }
643
866
  args.push(arg);
644
- if (! $(',')) { break }
867
+ if (! $char(',')) {
868
+ break;
869
+ }
645
870
  }
646
871
  return args;
647
872
  },
648
873
  literal: function () {
649
- return $(this.entities.dimension) ||
650
- $(this.entities.color) ||
651
- $(this.entities.quoted) ||
652
- $(this.entities.unicodeDescriptor);
874
+ return this.dimension() ||
875
+ this.color() ||
876
+ this.quoted() ||
877
+ this.unicodeDescriptor();
653
878
  },
654
879
 
655
880
  // Assignments are argument entities for calls.
@@ -660,7 +885,15 @@ less.Parser = function Parser(env) {
660
885
 
661
886
  assignment: function () {
662
887
  var key, value;
663
- if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) {
888
+ key = $re(/^\w+(?=\s?=)/i);
889
+ if (!key) {
890
+ return;
891
+ }
892
+ if (!$char('=')) {
893
+ return;
894
+ }
895
+ value = parsers.entity();
896
+ if (value) {
664
897
  return new(tree.Assignment)(key, value);
665
898
  }
666
899
  },
@@ -675,11 +908,14 @@ less.Parser = function Parser(env) {
675
908
  url: function () {
676
909
  var value;
677
910
 
678
- if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
679
- value = $(this.entities.quoted) || $(this.entities.variable) ||
680
- $(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
911
+ if (input.charAt(i) !== 'u' || !$re(/^url\(/)) {
912
+ return;
913
+ }
914
+
915
+ value = this.quoted() || this.variable() ||
916
+ $re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
681
917
 
682
- expect(')');
918
+ expectChar(')');
683
919
 
684
920
  return new(tree.URL)((value.value != null || value instanceof tree.Variable)
685
921
  ? value : new(tree.Anonymous)(value), env.currentFileInfo);
@@ -696,16 +932,16 @@ less.Parser = function Parser(env) {
696
932
  variable: function () {
697
933
  var name, index = i;
698
934
 
699
- if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
935
+ if (input.charAt(i) === '@' && (name = $re(/^@@?[\w-]+/))) {
700
936
  return new(tree.Variable)(name, index, env.currentFileInfo);
701
937
  }
702
938
  },
703
939
 
704
940
  // A variable entity useing the protective {} e.g. @{var}
705
941
  variableCurly: function () {
706
- var name, curly, index = i;
942
+ var curly, index = i;
707
943
 
708
- if (input.charAt(i) === '@' && (curly = $(/^@\{([\w-]+)\}/))) {
944
+ if (input.charAt(i) === '@' && (curly = $re(/^@\{([\w-]+)\}/))) {
709
945
  return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo);
710
946
  }
711
947
  },
@@ -720,7 +956,7 @@ less.Parser = function Parser(env) {
720
956
  color: function () {
721
957
  var rgb;
722
958
 
723
- if (input.charAt(i) === '#' && (rgb = $(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
959
+ if (input.charAt(i) === '#' && (rgb = $re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
724
960
  return new(tree.Color)(rgb[1]);
725
961
  }
726
962
  },
@@ -733,9 +969,12 @@ less.Parser = function Parser(env) {
733
969
  dimension: function () {
734
970
  var value, c = input.charCodeAt(i);
735
971
  //Is the first char of the dimension 0-9, '.', '+' or '-'
736
- if ((c > 57 || c < 43) || c === 47 || c == 44) return;
972
+ if ((c > 57 || c < 43) || c === 47 || c == 44) {
973
+ return;
974
+ }
737
975
 
738
- if (value = $(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/)) {
976
+ value = $re(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/);
977
+ if (value) {
739
978
  return new(tree.Dimension)(value[1], value[2]);
740
979
  }
741
980
  },
@@ -747,8 +986,9 @@ less.Parser = function Parser(env) {
747
986
  //
748
987
  unicodeDescriptor: function () {
749
988
  var ud;
750
-
751
- if (ud = $(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/)) {
989
+
990
+ ud = $re(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/);
991
+ if (ud) {
752
992
  return new(tree.UnicodeDescriptor)(ud[0]);
753
993
  }
754
994
  },
@@ -761,12 +1001,16 @@ less.Parser = function Parser(env) {
761
1001
  javascript: function () {
762
1002
  var str, j = i, e;
763
1003
 
764
- if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
765
- if (input.charAt(j) !== '`') { return }
1004
+ if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings
1005
+ if (input.charAt(j) !== '`') { return; }
1006
+ if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) {
1007
+ error("You are using JavaScript, which has been disabled.");
1008
+ }
766
1009
 
767
- e && $('~');
1010
+ if (e) { $char('~'); }
768
1011
 
769
- if (str = $(/^`([^`]*)`/)) {
1012
+ str = $re(/^`([^`]*)`/);
1013
+ if (str) {
770
1014
  return new(tree.JavaScript)(str[1], i, e);
771
1015
  }
772
1016
  }
@@ -780,33 +1024,32 @@ less.Parser = function Parser(env) {
780
1024
  variable: function () {
781
1025
  var name;
782
1026
 
783
- if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] }
1027
+ if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
784
1028
  },
785
1029
 
786
1030
  //
787
1031
  // extend syntax - used to extend selectors
788
1032
  //
789
1033
  extend: function(isRule) {
790
- var elements, e, index = i, option, extendList = [];
1034
+ var elements, e, index = i, option, extendList, extend;
791
1035
 
792
- if (!$(isRule ? /^&:extend\(/ : /^:extend\(/)) { return; }
1036
+ if (!(isRule ? $re(/^&:extend\(/) : $re(/^:extend\(/))) { return; }
793
1037
 
794
1038
  do {
795
1039
  option = null;
796
- elements = [];
797
- while (true) {
798
- option = $(/^(all)(?=\s*(\)|,))/);
799
- if (option) { break; }
800
- e = $(this.element);
1040
+ elements = null;
1041
+ while (! (option = $re(/^(all)(?=\s*(\)|,))/))) {
1042
+ e = this.element();
801
1043
  if (!e) { break; }
802
- elements.push(e);
1044
+ if (elements) { elements.push(e); } else { elements = [ e ]; }
803
1045
  }
804
1046
 
805
1047
  option = option && option[1];
806
1048
 
807
- extendList.push(new(tree.Extend)(new(tree.Selector)(elements), option, index));
1049
+ extend = new(tree.Extend)(new(tree.Selector)(elements), option, index);
1050
+ if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
808
1051
 
809
- } while($(","))
1052
+ } while($char(","));
810
1053
 
811
1054
  expect(/^\)/);
812
1055
 
@@ -840,52 +1083,62 @@ less.Parser = function Parser(env) {
840
1083
  // selector for now.
841
1084
  //
842
1085
  call: function () {
843
- var elements = [], e, c, args, delim, arg, index = i, s = input.charAt(i), important = false;
1086
+ var s = input.charAt(i), important = false, index = i, elemIndex,
1087
+ elements, elem, e, c, args;
844
1088
 
845
- if (s !== '.' && s !== '#') { return }
1089
+ if (s !== '.' && s !== '#') { return; }
846
1090
 
847
1091
  save(); // stop us absorbing part of an invalid selector
848
1092
 
849
- while (e = $(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/)) {
850
- elements.push(new(tree.Element)(c, e, i));
851
- c = $('>');
852
- }
853
- if ($('(')) {
854
- args = this.mixin.args.call(this, true).args;
855
- expect(')');
1093
+ while (true) {
1094
+ elemIndex = i;
1095
+ e = $re(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/);
1096
+ if (!e) {
1097
+ break;
1098
+ }
1099
+ elem = new(tree.Element)(c, e, elemIndex, env.currentFileInfo);
1100
+ if (elements) { elements.push(elem); } else { elements = [ elem ]; }
1101
+ c = $char('>');
856
1102
  }
857
1103
 
858
- args = args || [];
1104
+ if (elements) {
1105
+ if ($char('(')) {
1106
+ args = this.args(true).args;
1107
+ expectChar(')');
1108
+ }
859
1109
 
860
- if ($(this.important)) {
861
- important = true;
862
- }
1110
+ if (parsers.important()) {
1111
+ important = true;
1112
+ }
863
1113
 
864
- if (elements.length > 0 && ($(';') || peek('}'))) {
865
- return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
1114
+ if (parsers.end()) {
1115
+ return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
1116
+ }
866
1117
  }
867
1118
 
868
1119
  restore();
869
1120
  },
870
1121
  args: function (isCall) {
871
- var expressions = [], argsSemiColon = [], isSemiColonSeperated, argsComma = [], expressionContainsNamed, name, nameLoop, value, arg,
872
- returner = {args:null, variadic: false};
1122
+ var parsers = parser.parsers, entities = parsers.entities,
1123
+ returner = { args:null, variadic: false },
1124
+ expressions = [], argsSemiColon = [], argsComma = [],
1125
+ isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
1126
+
873
1127
  while (true) {
874
1128
  if (isCall) {
875
- arg = $(this.expression);
1129
+ arg = parsers.expression();
876
1130
  } else {
877
- $(this.comment);
878
- if (input.charAt(i) === '.' && $(/^\.{3}/)) {
1131
+ parsers.comments();
1132
+ if (input.charAt(i) === '.' && $re(/^\.{3}/)) {
879
1133
  returner.variadic = true;
880
- if ($(";") && !isSemiColonSeperated) {
1134
+ if ($char(";") && !isSemiColonSeperated) {
881
1135
  isSemiColonSeperated = true;
882
1136
  }
883
1137
  (isSemiColonSeperated ? argsSemiColon : argsComma)
884
1138
  .push({ variadic: true });
885
1139
  break;
886
1140
  }
887
- arg = $(this.entities.variable) || $(this.entities.literal)
888
- || $(this.entities.keyword);
1141
+ arg = entities.variable() || entities.literal() || entities.keyword();
889
1142
  }
890
1143
 
891
1144
  if (!arg) {
@@ -902,25 +1155,25 @@ less.Parser = function Parser(env) {
902
1155
  if (isCall) {
903
1156
  // Variable
904
1157
  if (arg.value.length == 1) {
905
- var val = arg.value[0];
1158
+ val = arg.value[0];
906
1159
  }
907
1160
  } else {
908
1161
  val = arg;
909
1162
  }
910
1163
 
911
1164
  if (val && val instanceof tree.Variable) {
912
- if ($(':')) {
1165
+ if ($char(':')) {
913
1166
  if (expressions.length > 0) {
914
1167
  if (isSemiColonSeperated) {
915
1168
  error("Cannot mix ; and , as delimiter types");
916
1169
  }
917
1170
  expressionContainsNamed = true;
918
1171
  }
919
- value = expect(this.expression);
1172
+ value = expect(parsers.expression);
920
1173
  nameLoop = (name = val.name);
921
- } else if (!isCall && $(/^\.{3}/)) {
1174
+ } else if (!isCall && $re(/^\.{3}/)) {
922
1175
  returner.variadic = true;
923
- if ($(";") && !isSemiColonSeperated) {
1176
+ if ($char(";") && !isSemiColonSeperated) {
924
1177
  isSemiColonSeperated = true;
925
1178
  }
926
1179
  (isSemiColonSeperated ? argsSemiColon : argsComma)
@@ -938,11 +1191,11 @@ less.Parser = function Parser(env) {
938
1191
 
939
1192
  argsComma.push({ name:nameLoop, value:value });
940
1193
 
941
- if ($(',')) {
1194
+ if ($char(',')) {
942
1195
  continue;
943
1196
  }
944
1197
 
945
- if ($(';') || isSemiColonSeperated) {
1198
+ if ($char(';') || isSemiColonSeperated) {
946
1199
 
947
1200
  if (expressionContainsNamed) {
948
1201
  error("Cannot mix ; and , as delimiter types");
@@ -951,7 +1204,7 @@ less.Parser = function Parser(env) {
951
1204
  isSemiColonSeperated = true;
952
1205
 
953
1206
  if (expressions.length > 1) {
954
- value = new (tree.Value)(expressions);
1207
+ value = new(tree.Value)(expressions);
955
1208
  }
956
1209
  argsSemiColon.push({ name:name, value:value });
957
1210
 
@@ -984,33 +1237,36 @@ less.Parser = function Parser(env) {
984
1237
  // the `{...}` block.
985
1238
  //
986
1239
  definition: function () {
987
- var name, params = [], match, ruleset, param, value, cond, variadic = false;
1240
+ var name, params = [], match, ruleset, cond, variadic = false;
988
1241
  if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
989
- peek(/^[^{]*\}/)) return;
1242
+ peek(/^[^{]*\}/)) {
1243
+ return;
1244
+ }
990
1245
 
991
1246
  save();
992
1247
 
993
- if (match = $(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/)) {
1248
+ match = $re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/);
1249
+ if (match) {
994
1250
  name = match[1];
995
1251
 
996
- var argInfo = this.mixin.args.call(this, false);
1252
+ var argInfo = this.args(false);
997
1253
  params = argInfo.args;
998
1254
  variadic = argInfo.variadic;
999
1255
 
1000
1256
  // .mixincall("@{a}");
1001
1257
  // looks a bit like a mixin definition.. so we have to be nice and restore
1002
- if (!$(')')) {
1258
+ if (!$char(')')) {
1003
1259
  furthest = i;
1004
1260
  restore();
1005
1261
  }
1006
1262
 
1007
- $(this.comment);
1263
+ parsers.comments();
1008
1264
 
1009
- if ($(/^when/)) { // Guard
1010
- cond = expect(this.conditions, 'expected condition');
1265
+ if ($re(/^when/)) { // Guard
1266
+ cond = expect(parsers.conditions, 'expected condition');
1011
1267
  }
1012
1268
 
1013
- ruleset = $(this.block);
1269
+ ruleset = parsers.block();
1014
1270
 
1015
1271
  if (ruleset) {
1016
1272
  return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
@@ -1026,9 +1282,11 @@ less.Parser = function Parser(env) {
1026
1282
  // and can be found inside a rule's value.
1027
1283
  //
1028
1284
  entity: function () {
1029
- return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
1030
- $(this.entities.call) || $(this.entities.keyword) ||$(this.entities.javascript) ||
1031
- $(this.comment);
1285
+ var entities = this.entities;
1286
+
1287
+ return entities.literal() || entities.variable() || entities.url() ||
1288
+ entities.call() || entities.keyword() || entities.javascript() ||
1289
+ this.comment();
1032
1290
  },
1033
1291
 
1034
1292
  //
@@ -1037,7 +1295,7 @@ less.Parser = function Parser(env) {
1037
1295
  // it's there, if ';' was ommitted.
1038
1296
  //
1039
1297
  end: function () {
1040
- return $(';') || peek('}');
1298
+ return $char(';') || peekChar('}');
1041
1299
  },
1042
1300
 
1043
1301
  //
@@ -1048,9 +1306,10 @@ less.Parser = function Parser(env) {
1048
1306
  alpha: function () {
1049
1307
  var value;
1050
1308
 
1051
- if (! $(/^\(opacity=/i)) return;
1052
- if (value = $(/^\d+/) || $(this.entities.variable)) {
1053
- expect(')');
1309
+ if (! $re(/^\(opacity=/i)) { return; }
1310
+ value = $re(/^\d+/) || this.entities.variable();
1311
+ if (value) {
1312
+ expectChar(')');
1054
1313
  return new(tree.Alpha)(value);
1055
1314
  }
1056
1315
  },
@@ -1068,23 +1327,23 @@ less.Parser = function Parser(env) {
1068
1327
  // and an element name, such as a tag a class, or `*`.
1069
1328
  //
1070
1329
  element: function () {
1071
- var e, t, c, v;
1330
+ var e, c, v, index = i;
1072
1331
 
1073
- c = $(this.combinator);
1332
+ c = this.combinator();
1074
1333
 
1075
- e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
1076
- $('*') || $('&') || $(this.attribute) || $(/^\([^()@]+\)/) || $(/^[\.#](?=@)/) || $(this.entities.variableCurly);
1334
+ e = $re(/^(?:\d+\.\d+|\d+)%/) || $re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
1335
+ $char('*') || $char('&') || this.attribute() || $re(/^\([^()@]+\)/) || $re(/^[\.#](?=@)/) ||
1336
+ this.entities.variableCurly();
1077
1337
 
1078
1338
  if (! e) {
1079
- if ($('(')) {
1080
- if ((v = ($(this.selector))) &&
1081
- $(')')) {
1339
+ if ($char('(')) {
1340
+ if ((v = this.selector()) && $char(')')) {
1082
1341
  e = new(tree.Paren)(v);
1083
1342
  }
1084
1343
  }
1085
1344
  }
1086
1345
 
1087
- if (e) { return new(tree.Element)(c, e, i) }
1346
+ if (e) { return new(tree.Element)(c, e, index, env.currentFileInfo); }
1088
1347
  },
1089
1348
 
1090
1349
  //
@@ -1098,18 +1357,28 @@ less.Parser = function Parser(env) {
1098
1357
  //
1099
1358
  combinator: function () {
1100
1359
  var c = input.charAt(i);
1101
-
1102
- if (c === '>' || c === '+' || c === '~' || c === '|') {
1360
+
1361
+ if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') {
1103
1362
  i++;
1104
- while (input.charAt(i).match(/\s/)) { i++ }
1363
+ if (input.charAt(i) === '^') {
1364
+ c = '^^';
1365
+ i++;
1366
+ }
1367
+ while (isWhitespace(input, i)) { i++; }
1105
1368
  return new(tree.Combinator)(c);
1106
- } else if (input.charAt(i - 1).match(/\s/)) {
1369
+ } else if (isWhitespace(input, i - 1)) {
1107
1370
  return new(tree.Combinator)(" ");
1108
1371
  } else {
1109
1372
  return new(tree.Combinator)(null);
1110
1373
  }
1111
1374
  },
1112
-
1375
+ //
1376
+ // A CSS selector (see selector below)
1377
+ // with less extensions e.g. the ability to extend and guard
1378
+ //
1379
+ lessSelector: function () {
1380
+ return this.selector(true);
1381
+ },
1113
1382
  //
1114
1383
  // A CSS Selector
1115
1384
  //
@@ -1118,40 +1387,46 @@ less.Parser = function Parser(env) {
1118
1387
  //
1119
1388
  // Selectors are made out of one or more Elements, see above.
1120
1389
  //
1121
- selector: function () {
1122
- var sel, e, elements = [], c, extend, extendList = [];
1123
-
1124
- while ((extend = $(this.extend)) || (e = $(this.element))) {
1125
- if (extend) {
1126
- extendList.push.apply(extendList, extend);
1390
+ selector: function (isLess) {
1391
+ var index = i, $re = _$re, elements, extendList, c, e, extend, when, condition;
1392
+
1393
+ while ((isLess && (extend = this.extend())) || (isLess && (when = $re(/^when/))) || (e = this.element())) {
1394
+ if (when) {
1395
+ condition = expect(this.conditions, 'expected condition');
1396
+ } else if (condition) {
1397
+ error("CSS guard can only be used at the end of selector");
1398
+ } else if (extend) {
1399
+ if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
1127
1400
  } else {
1128
- if (extendList.length) {
1129
- error("Extend can only be used at the end of selector");
1130
- }
1401
+ if (extendList) { error("Extend can only be used at the end of selector"); }
1131
1402
  c = input.charAt(i);
1132
- elements.push(e)
1403
+ if (elements) { elements.push(e); } else { elements = [ e ]; }
1133
1404
  e = null;
1134
1405
  }
1135
- if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') { break }
1406
+ if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
1407
+ break;
1408
+ }
1136
1409
  }
1137
1410
 
1138
- if (elements.length > 0) { return new(tree.Selector)(elements, extendList); }
1139
- if (extendList.length) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
1411
+ if (elements) { return new(tree.Selector)(elements, extendList, condition, index, env.currentFileInfo); }
1412
+ if (extendList) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
1140
1413
  },
1141
1414
  attribute: function () {
1142
- var attr = '', key, val, op;
1415
+ if (! $char('[')) { return; }
1143
1416
 
1144
- if (! $('[')) return;
1417
+ var entities = this.entities,
1418
+ key, val, op;
1145
1419
 
1146
- if (!(key = $(this.entities.variableCurly))) {
1420
+ if (!(key = entities.variableCurly())) {
1147
1421
  key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
1148
1422
  }
1149
1423
 
1150
- if ((op = $(/^[|~*$^]?=/))) {
1151
- val = $(this.entities.quoted) || $(/^[\w-]+/) || $(this.entities.variableCurly);
1424
+ op = $re(/^[|~*$^]?=/);
1425
+ if (op) {
1426
+ val = entities.quoted() || $re(/^[0-9]+%/) || $re(/^[\w-]+/) || entities.variableCurly();
1152
1427
  }
1153
1428
 
1154
- expect(']');
1429
+ expectChar(']');
1155
1430
 
1156
1431
  return new(tree.Attribute)(key, op, val);
1157
1432
  },
@@ -1162,7 +1437,7 @@ less.Parser = function Parser(env) {
1162
1437
  //
1163
1438
  block: function () {
1164
1439
  var content;
1165
- if ($('{') && (content = $(this.primary)) && $('}')) {
1440
+ if ($char('{') && (content = this.primary()) && $char('}')) {
1166
1441
  return content;
1167
1442
  }
1168
1443
  },
@@ -1171,24 +1446,36 @@ less.Parser = function Parser(env) {
1171
1446
  // div, .class, body > p {...}
1172
1447
  //
1173
1448
  ruleset: function () {
1174
- var selectors = [], s, rules, debugInfo;
1449
+ var selectors, s, rules, debugInfo;
1175
1450
 
1176
1451
  save();
1177
1452
 
1178
- if (env.dumpLineNumbers)
1453
+ if (env.dumpLineNumbers) {
1179
1454
  debugInfo = getDebugInfo(i, input, env);
1455
+ }
1180
1456
 
1181
- while (s = $(this.selector)) {
1182
- selectors.push(s);
1183
- $(this.comment);
1184
- if (! $(',')) { break }
1185
- $(this.comment);
1457
+ while (true) {
1458
+ s = this.lessSelector();
1459
+ if (!s) {
1460
+ break;
1461
+ }
1462
+ if (selectors) { selectors.push(s); } else { selectors = [ s ]; }
1463
+ this.comments();
1464
+ if (s.condition && selectors.length > 1) {
1465
+ error("Guards are only currently allowed on a single selector.");
1466
+ }
1467
+ if (! $char(',')) { break; }
1468
+ if (s.condition) {
1469
+ error("Guards are only currently allowed on a single selector.");
1470
+ }
1471
+ this.comments();
1186
1472
  }
1187
1473
 
1188
- if (selectors.length > 0 && (rules = $(this.block))) {
1474
+ if (selectors && (rules = this.block())) {
1189
1475
  var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
1190
- if (env.dumpLineNumbers)
1476
+ if (env.dumpLineNumbers) {
1191
1477
  ruleset.debugInfo = debugInfo;
1478
+ }
1192
1479
  return ruleset;
1193
1480
  } else {
1194
1481
  // Backtrack
@@ -1197,22 +1484,28 @@ less.Parser = function Parser(env) {
1197
1484
  }
1198
1485
  },
1199
1486
  rule: function (tryAnonymous) {
1200
- var name, value, c = input.charAt(i), important;
1487
+ var name, value, c = input.charAt(i), important, merge = false;
1201
1488
  save();
1202
1489
 
1203
- if (c === '.' || c === '#' || c === '&') { return }
1490
+ if (c === '.' || c === '#' || c === '&') { return; }
1204
1491
 
1205
- if (name = $(this.variable) || $(this.property)) {
1492
+ name = this.variable() || this.ruleProperty();
1493
+ if (name) {
1206
1494
  // prefer to try to parse first if its a variable or we are compressing
1207
1495
  // but always fallback on the other one
1208
- value = !tryAnonymous && (env.compress || (name.charAt(0) === '@')) ?
1209
- ($(this.value) || $(this.anonymousValue)) :
1210
- ($(this.anonymousValue) || $(this.value));
1496
+ value = !tryAnonymous && (env.compress || (name.charAt && (name.charAt(0) === '@'))) ?
1497
+ (this.value() || this.anonymousValue()) :
1498
+ (this.anonymousValue() || this.value());
1211
1499
 
1212
- important = $(this.important);
1500
+ important = this.important();
1501
+
1502
+ // a name returned by this.ruleProperty() is always an array of the form:
1503
+ // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
1504
+ // where each item is a tree.Keyword or tree.Variable
1505
+ merge = name.pop && (name.pop().value === "+");
1213
1506
 
1214
- if (value && $(this.end)) {
1215
- return new(tree.Rule)(name, value, important, memo, env.currentFileInfo);
1507
+ if (value && this.end()) {
1508
+ return new (tree.Rule)(name, value, important, merge, memo, env.currentFileInfo);
1216
1509
  } else {
1217
1510
  furthest = i;
1218
1511
  restore();
@@ -1224,7 +1517,8 @@ less.Parser = function Parser(env) {
1224
1517
  },
1225
1518
  anonymousValue: function () {
1226
1519
  var match;
1227
- if (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j])) {
1520
+ match = /^([^@+\/'"*`(;{}-]*);/.exec(current);
1521
+ if (match) {
1228
1522
  i += match[0].length - 1;
1229
1523
  return new(tree.Anonymous)(match[1]);
1230
1524
  }
@@ -1245,13 +1539,13 @@ less.Parser = function Parser(env) {
1245
1539
 
1246
1540
  save();
1247
1541
 
1248
- var dir = $(/^@import?\s+/);
1542
+ var dir = $re(/^@import?\s+/);
1249
1543
 
1250
- var options = (dir ? $(this.importOptions) : null) || {};
1544
+ var options = (dir ? this.importOptions() : null) || {};
1251
1545
 
1252
- if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) {
1253
- features = $(this.mediaFeatures);
1254
- if ($(';')) {
1546
+ if (dir && (path = this.entities.quoted() || this.entities.url())) {
1547
+ features = this.mediaFeatures();
1548
+ if ($char(';')) {
1255
1549
  features = features && new(tree.Value)(features);
1256
1550
  return new(tree.Import)(path, features, options, index, env.currentFileInfo);
1257
1551
  }
@@ -1264,9 +1558,10 @@ less.Parser = function Parser(env) {
1264
1558
  var o, options = {}, optionName, value;
1265
1559
 
1266
1560
  // list of options, surrounded by parens
1267
- if (! $('(')) { return null; }
1561
+ if (! $char('(')) { return null; }
1268
1562
  do {
1269
- if (o = $(this.importOption)) {
1563
+ o = this.importOption();
1564
+ if (o) {
1270
1565
  optionName = o;
1271
1566
  value = true;
1272
1567
  switch(optionName) {
@@ -1280,38 +1575,38 @@ less.Parser = function Parser(env) {
1280
1575
  break;
1281
1576
  }
1282
1577
  options[optionName] = value;
1283
- if (! $(',')) { break }
1578
+ if (! $char(',')) { break; }
1284
1579
  }
1285
1580
  } while (o);
1286
- expect(')');
1581
+ expectChar(')');
1287
1582
  return options;
1288
1583
  },
1289
1584
 
1290
1585
  importOption: function() {
1291
- var opt = $(/^(less|css|multiple|once)/);
1586
+ var opt = $re(/^(less|css|multiple|once|inline|reference)/);
1292
1587
  if (opt) {
1293
1588
  return opt[1];
1294
1589
  }
1295
1590
  },
1296
1591
 
1297
1592
  mediaFeature: function () {
1298
- var e, p, nodes = [];
1299
-
1593
+ var entities = this.entities, nodes = [], e, p;
1300
1594
  do {
1301
- if (e = $(this.entities.keyword)) {
1595
+ e = entities.keyword() || entities.variable();
1596
+ if (e) {
1302
1597
  nodes.push(e);
1303
- } else if ($('(')) {
1304
- p = $(this.property);
1305
- e = $(this.value);
1306
- if ($(')')) {
1598
+ } else if ($char('(')) {
1599
+ p = this.property();
1600
+ e = this.value();
1601
+ if ($char(')')) {
1307
1602
  if (p && e) {
1308
- nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, env.currentFileInfo, true)));
1603
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true)));
1309
1604
  } else if (e) {
1310
1605
  nodes.push(new(tree.Paren)(e));
1311
1606
  } else {
1312
1607
  return null;
1313
1608
  }
1314
- } else { return null }
1609
+ } else { return null; }
1315
1610
  }
1316
1611
  } while (e);
1317
1612
 
@@ -1321,16 +1616,19 @@ less.Parser = function Parser(env) {
1321
1616
  },
1322
1617
 
1323
1618
  mediaFeatures: function () {
1324
- var e, features = [];
1325
-
1619
+ var entities = this.entities, features = [], e;
1326
1620
  do {
1327
- if (e = $(this.mediaFeature)) {
1328
- features.push(e);
1329
- if (! $(',')) { break }
1330
- } else if (e = $(this.entities.variable)) {
1331
- features.push(e);
1332
- if (! $(',')) { break }
1333
- }
1621
+ e = this.mediaFeature();
1622
+ if (e) {
1623
+ features.push(e);
1624
+ if (! $char(',')) { break; }
1625
+ } else {
1626
+ e = entities.variable();
1627
+ if (e) {
1628
+ features.push(e);
1629
+ if (! $char(',')) { break; }
1630
+ }
1631
+ }
1334
1632
  } while (e);
1335
1633
 
1336
1634
  return features.length > 0 ? features : null;
@@ -1339,16 +1637,19 @@ less.Parser = function Parser(env) {
1339
1637
  media: function () {
1340
1638
  var features, rules, media, debugInfo;
1341
1639
 
1342
- if (env.dumpLineNumbers)
1640
+ if (env.dumpLineNumbers) {
1343
1641
  debugInfo = getDebugInfo(i, input, env);
1642
+ }
1344
1643
 
1345
- if ($(/^@media/)) {
1346
- features = $(this.mediaFeatures);
1644
+ if ($re(/^@media/)) {
1645
+ features = this.mediaFeatures();
1347
1646
 
1348
- if (rules = $(this.block)) {
1349
- media = new(tree.Media)(rules, features);
1350
- if(env.dumpLineNumbers)
1647
+ rules = this.block();
1648
+ if (rules) {
1649
+ media = new(tree.Media)(rules, features, i, env.currentFileInfo);
1650
+ if (env.dumpLineNumbers) {
1351
1651
  media.debugInfo = debugInfo;
1652
+ }
1352
1653
  return media;
1353
1654
  }
1354
1655
  }
@@ -1360,20 +1661,21 @@ less.Parser = function Parser(env) {
1360
1661
  // @charset "utf-8";
1361
1662
  //
1362
1663
  directive: function () {
1363
- var name, value, rules, identifier, e, nodes, nonVendorSpecificName,
1364
- hasBlock, hasIdentifier, hasExpression;
1664
+ var index = i, name, value, rules, nonVendorSpecificName,
1665
+ hasBlock, hasIdentifier, hasExpression, identifier;
1365
1666
 
1366
- if (input.charAt(i) !== '@') return;
1667
+ if (input.charAt(i) !== '@') { return; }
1367
1668
 
1368
- if (value = $(this['import']) || $(this.media)) {
1669
+ value = this['import']() || this.media();
1670
+ if (value) {
1369
1671
  return value;
1370
1672
  }
1371
1673
 
1372
1674
  save();
1373
1675
 
1374
- name = $(/^@[a-z-]+/);
1676
+ name = $re(/^@[a-z-]+/);
1375
1677
 
1376
- if (!name) return;
1678
+ if (!name) { return; }
1377
1679
 
1378
1680
  nonVendorSpecificName = name;
1379
1681
  if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
@@ -1403,6 +1705,7 @@ less.Parser = function Parser(env) {
1403
1705
  case "@right-bottom":
1404
1706
  hasBlock = true;
1405
1707
  break;
1708
+ case "@host":
1406
1709
  case "@page":
1407
1710
  case "@document":
1408
1711
  case "@supports":
@@ -1416,17 +1719,21 @@ less.Parser = function Parser(env) {
1416
1719
  }
1417
1720
 
1418
1721
  if (hasIdentifier) {
1419
- name += " " + ($(/^[^{]+/) || '').trim();
1722
+ identifier = ($re(/^[^{]+/) || '').trim();
1723
+ if (identifier) {
1724
+ name += " " + identifier;
1725
+ }
1420
1726
  }
1421
1727
 
1422
- if (hasBlock)
1423
- {
1424
- if (rules = $(this.block)) {
1425
- return new(tree.Directive)(name, rules);
1728
+ if (hasBlock) {
1729
+ rules = this.block();
1730
+ if (rules) {
1731
+ return new(tree.Directive)(name, rules, index, env.currentFileInfo);
1426
1732
  }
1427
1733
  } else {
1428
- if ((value = hasExpression ? $(this.expression) : $(this.entity)) && $(';')) {
1429
- var directive = new(tree.Directive)(name, value);
1734
+ value = hasExpression ? this.expression() : this.entity();
1735
+ if (value && $char(';')) {
1736
+ var directive = new(tree.Directive)(name, value, index, env.currentFileInfo);
1430
1737
  if (env.dumpLineNumbers) {
1431
1738
  directive.debugInfo = getDebugInfo(i, input, env);
1432
1739
  }
@@ -1446,12 +1753,15 @@ less.Parser = function Parser(env) {
1446
1753
  // and before the `;`.
1447
1754
  //
1448
1755
  value: function () {
1449
- var e, expressions = [], important;
1756
+ var e, expressions = [];
1450
1757
 
1451
- while (e = $(this.expression)) {
1452
- expressions.push(e);
1453
- if (! $(',')) { break }
1454
- }
1758
+ do {
1759
+ e = this.expression();
1760
+ if (e) {
1761
+ expressions.push(e);
1762
+ if (! $char(',')) { break; }
1763
+ }
1764
+ } while(e);
1455
1765
 
1456
1766
  if (expressions.length > 0) {
1457
1767
  return new(tree.Value)(expressions);
@@ -1459,48 +1769,66 @@ less.Parser = function Parser(env) {
1459
1769
  },
1460
1770
  important: function () {
1461
1771
  if (input.charAt(i) === '!') {
1462
- return $(/^! *important/);
1772
+ return $re(/^! *important/);
1463
1773
  }
1464
1774
  },
1465
1775
  sub: function () {
1466
1776
  var a, e;
1467
1777
 
1468
- if ($('(')) {
1469
- if (a = $(this.addition)) {
1778
+ if ($char('(')) {
1779
+ a = this.addition();
1780
+ if (a) {
1470
1781
  e = new(tree.Expression)([a]);
1471
- expect(')');
1782
+ expectChar(')');
1472
1783
  e.parens = true;
1473
1784
  return e;
1474
1785
  }
1475
1786
  }
1476
1787
  },
1477
1788
  multiplication: function () {
1478
- var m, a, op, operation, isSpaced, expression = [];
1479
- if (m = $(this.operand)) {
1480
- isSpaced = isWhitespace(input.charAt(i - 1));
1481
- while (!peek(/^\/[*\/]/) && (op = ($('/') || $('*')))) {
1482
- if (a = $(this.operand)) {
1483
- m.parensInOp = true;
1484
- a.parensInOp = true;
1485
- operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1486
- isSpaced = isWhitespace(input.charAt(i - 1));
1487
- } else {
1789
+ var m, a, op, operation, isSpaced;
1790
+ m = this.operand();
1791
+ if (m) {
1792
+ isSpaced = isWhitespace(input, i - 1);
1793
+ while (true) {
1794
+ if (peek(/^\/[*\/]/)) {
1488
1795
  break;
1489
1796
  }
1797
+ op = $char('/') || $char('*');
1798
+
1799
+ if (!op) { break; }
1800
+
1801
+ a = this.operand();
1802
+
1803
+ if (!a) { break; }
1804
+
1805
+ m.parensInOp = true;
1806
+ a.parensInOp = true;
1807
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1808
+ isSpaced = isWhitespace(input, i - 1);
1490
1809
  }
1491
1810
  return operation || m;
1492
1811
  }
1493
1812
  },
1494
1813
  addition: function () {
1495
1814
  var m, a, op, operation, isSpaced;
1496
- if (m = $(this.multiplication)) {
1497
- isSpaced = isWhitespace(input.charAt(i - 1));
1498
- while ((op = $(/^[-+]\s+/) || (!isSpaced && ($('+') || $('-')))) &&
1499
- (a = $(this.multiplication))) {
1815
+ m = this.multiplication();
1816
+ if (m) {
1817
+ isSpaced = isWhitespace(input, i - 1);
1818
+ while (true) {
1819
+ op = $re(/^[-+]\s+/) || (!isSpaced && ($char('+') || $char('-')));
1820
+ if (!op) {
1821
+ break;
1822
+ }
1823
+ a = this.multiplication();
1824
+ if (!a) {
1825
+ break;
1826
+ }
1827
+
1500
1828
  m.parensInOp = true;
1501
1829
  a.parensInOp = true;
1502
1830
  operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1503
- isSpaced = isWhitespace(input.charAt(i - 1));
1831
+ isSpaced = isWhitespace(input, i - 1);
1504
1832
  }
1505
1833
  return operation || m;
1506
1834
  }
@@ -1508,21 +1836,33 @@ less.Parser = function Parser(env) {
1508
1836
  conditions: function () {
1509
1837
  var a, b, index = i, condition;
1510
1838
 
1511
- if (a = $(this.condition)) {
1512
- while ($(',') && (b = $(this.condition))) {
1839
+ a = this.condition();
1840
+ if (a) {
1841
+ while (true) {
1842
+ if (!peek(/^,\s*(not\s*)?\(/) || !$char(',')) {
1843
+ break;
1844
+ }
1845
+ b = this.condition();
1846
+ if (!b) {
1847
+ break;
1848
+ }
1513
1849
  condition = new(tree.Condition)('or', condition || a, b, index);
1514
1850
  }
1515
1851
  return condition || a;
1516
1852
  }
1517
1853
  },
1518
1854
  condition: function () {
1519
- var a, b, c, op, index = i, negate = false;
1520
-
1521
- if ($(/^not/)) { negate = true }
1522
- expect('(');
1523
- if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1524
- if (op = $(/^(?:>=|=<|[<=>])/)) {
1525
- if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1855
+ var entities = this.entities, index = i, negate = false,
1856
+ a, b, c, op;
1857
+
1858
+ if ($re(/^not/)) { negate = true; }
1859
+ expectChar('(');
1860
+ a = this.addition() || entities.keyword() || entities.quoted();
1861
+ if (a) {
1862
+ op = $re(/^(?:>=|<=|=<|[<=>])/);
1863
+ if (op) {
1864
+ b = this.addition() || entities.keyword() || entities.quoted();
1865
+ if (b) {
1526
1866
  c = new(tree.Condition)(op, a, b, index, negate);
1527
1867
  } else {
1528
1868
  error('expected expression');
@@ -1530,8 +1870,8 @@ less.Parser = function Parser(env) {
1530
1870
  } else {
1531
1871
  c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1532
1872
  }
1533
- expect(')');
1534
- return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
1873
+ expectChar(')');
1874
+ return $re(/^and/) ? new(tree.Condition)('and', c, this.condition()) : c;
1535
1875
  }
1536
1876
  },
1537
1877
 
@@ -1540,12 +1880,13 @@ less.Parser = function Parser(env) {
1540
1880
  // such as a Color, or a Variable
1541
1881
  //
1542
1882
  operand: function () {
1543
- var negate, p = input.charAt(i + 1);
1883
+ var entities = this.entities,
1884
+ p = input.charAt(i + 1), negate;
1544
1885
 
1545
- if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') }
1546
- var o = $(this.sub) || $(this.entities.dimension) ||
1547
- $(this.entities.color) || $(this.entities.variable) ||
1548
- $(this.entities.call);
1886
+ if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $char('-'); }
1887
+ var o = this.sub() || entities.dimension() ||
1888
+ entities.color() || entities.variable() ||
1889
+ entities.call();
1549
1890
 
1550
1891
  if (negate) {
1551
1892
  o.parensInOp = true;
@@ -1563,49 +1904,78 @@ less.Parser = function Parser(env) {
1563
1904
  // @var * 2
1564
1905
  //
1565
1906
  expression: function () {
1566
- var e, delim, entities = [], d;
1907
+ var entities = [], e, delim;
1567
1908
 
1568
- while (e = $(this.addition) || $(this.entity)) {
1569
- entities.push(e);
1570
- // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
1571
- if (!peek(/^\/[\/*]/) && (delim = $('/'))) {
1572
- entities.push(new(tree.Anonymous)(delim));
1909
+ do {
1910
+ e = this.addition() || this.entity();
1911
+ if (e) {
1912
+ entities.push(e);
1913
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
1914
+ if (!peek(/^\/[\/*]/)) {
1915
+ delim = $char('/');
1916
+ if (delim) {
1917
+ entities.push(new(tree.Anonymous)(delim));
1918
+ }
1919
+ }
1573
1920
  }
1574
- }
1921
+ } while (e);
1575
1922
  if (entities.length > 0) {
1576
1923
  return new(tree.Expression)(entities);
1577
1924
  }
1578
1925
  },
1579
1926
  property: function () {
1580
- var name;
1581
-
1582
- if (name = $(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/)) {
1927
+ var name = $re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);
1928
+ if (name) {
1583
1929
  return name[1];
1584
1930
  }
1931
+ },
1932
+ ruleProperty: function () {
1933
+ var c = current, name = [], index = [], length = 0, s, k;
1934
+
1935
+ function match(re) {
1936
+ var a = re.exec(c);
1937
+ if (a) {
1938
+ index.push(i + length);
1939
+ length += a[0].length;
1940
+ c = c.slice(a[1].length);
1941
+ return name.push(a[1]);
1942
+ }
1943
+ }
1944
+
1945
+ match(/^(\*?)/);
1946
+ while (match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)); // !
1947
+ if ((name.length > 1) && match(/^\s*(\+?)\s*:/)) {
1948
+ // at last, we have the complete match now. move forward,
1949
+ // convert name particles to tree objects and return:
1950
+ skipWhitespace(length);
1951
+ if (name[0] === '') {
1952
+ name.shift();
1953
+ index.shift();
1954
+ }
1955
+ for (k = 0; k < name.length; k++) {
1956
+ s = name[k];
1957
+ name[k] = (s.charAt(0) !== '@')
1958
+ ? new(tree.Keyword)(s)
1959
+ : new(tree.Variable)('@' + s.slice(2, -1),
1960
+ index[k], env.currentFileInfo);
1961
+ }
1962
+ return name;
1963
+ }
1585
1964
  }
1586
1965
  }
1587
1966
  };
1967
+ return parser;
1588
1968
  };
1589
-
1590
- if (less.mode === 'browser' || less.mode === 'rhino') {
1591
- //
1592
- // Used by `@import` directives
1593
- //
1594
- less.Parser.importer = function (path, currentFileInfo, callback, env) {
1595
- if (!/^([a-z-]+:)?\//.test(path) && currentFileInfo.currentDirectory) {
1596
- path = currentFileInfo.currentDirectory + path;
1969
+ less.Parser.serializeVars = function(vars) {
1970
+ var s = '';
1971
+
1972
+ for (var name in vars) {
1973
+ if (Object.hasOwnProperty.call(vars, name)) {
1974
+ var value = vars[name];
1975
+ s += ((name[0] === '@') ? '' : '@') + name +': '+ value +
1976
+ ((('' + value).slice(-1) === ';') ? '' : ';');
1597
1977
  }
1598
- var sheetEnv = env.toSheet(path);
1599
- sheetEnv.processImports = false;
1600
- sheetEnv.currentFileInfo = currentFileInfo;
1601
-
1602
- // We pass `true` as 3rd argument, to force the reload of the import.
1603
- // This is so we can get the syntax tree as opposed to just the CSS output,
1604
- // as we need this to evaluate the current stylesheet.
1605
- loadStyleSheet(sheetEnv,
1606
- function (e, root, data, sheet, _, path) {
1607
- callback.call(null, e, root, path);
1608
- }, true);
1609
- };
1610
- }
1978
+ }
1611
1979
 
1980
+ return s;
1981
+ };