less 2.2.2 → 2.6.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 (441) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +17 -0
  3. data/Gemfile +7 -3
  4. data/README.md +29 -13
  5. data/bin/lessc +3 -1
  6. data/less.gemspec +2 -6
  7. data/lib/less/defaults.rb +3 -3
  8. data/lib/less/java_script/rhino_context.rb +17 -27
  9. data/lib/less/java_script/v8_context.rb +11 -11
  10. data/lib/less/js/.gitattributes +10 -0
  11. data/lib/less/js/.gitignore +24 -0
  12. data/lib/less/js/.jshintrc +11 -0
  13. data/lib/less/js/.npmignore +1 -0
  14. data/lib/less/js/.travis.yml +8 -0
  15. data/lib/less/js/CHANGELOG.md +299 -0
  16. data/lib/less/js/CONTRIBUTING.md +50 -0
  17. data/lib/less/js/Gruntfile.js +298 -0
  18. data/lib/less/js/README.md +43 -12
  19. data/lib/less/js/benchmark/benchmark.less +194 -194
  20. data/lib/less/js/benchmark/less-benchmark.js +9 -10
  21. data/lib/less/js/bin/lessc +310 -37
  22. data/lib/less/js/bower.json +18 -0
  23. data/lib/less/js/build/README.md +51 -0
  24. data/lib/less/js/build/amd.js +1 -1
  25. data/lib/less/js/build/browser-header.js +4 -0
  26. data/lib/less/js/build/build.yml +162 -0
  27. data/lib/less/js/build/require-rhino.js +7 -2
  28. data/lib/less/js/build/rhino-header.js +4 -0
  29. data/lib/less/js/build/rhino-modules.js +131 -0
  30. data/lib/less/js/build/tasks/.gitkeep +1 -0
  31. data/lib/less/js/build.gradle +347 -0
  32. data/lib/less/js/dist/less-1.3.1.js +4011 -0
  33. data/lib/less/js/dist/less-1.3.1.min.js +9 -0
  34. data/lib/less/js/dist/less-1.3.2.js +4401 -0
  35. data/lib/less/js/dist/less-1.3.2.min.js +9 -0
  36. data/lib/less/js/dist/less-1.3.3.js +4413 -0
  37. data/lib/less/js/dist/less-1.3.3.min.js +9 -0
  38. data/lib/less/js/dist/less-1.4.0-beta.js +5830 -0
  39. data/lib/less/js/dist/less-1.4.0-beta.min.js +11 -0
  40. data/lib/less/js/dist/less-1.4.0.js +5830 -0
  41. data/lib/less/js/dist/less-1.4.0.min.js +11 -0
  42. data/lib/less/js/dist/less-1.4.1.js +5837 -0
  43. data/lib/less/js/dist/less-1.4.1.min.js +11 -0
  44. data/lib/less/js/dist/less-1.4.2.js +5837 -0
  45. data/lib/less/js/dist/less-1.4.2.min.js +11 -0
  46. data/lib/less/js/dist/less-1.5.0.js +6914 -0
  47. data/lib/less/js/dist/less-1.5.0.min.js +13 -0
  48. data/lib/less/js/dist/less-1.5.1.js +6941 -0
  49. data/lib/less/js/dist/less-1.5.1.min.js +13 -0
  50. data/lib/less/js/dist/less-1.6.0.js +7485 -0
  51. data/lib/less/js/dist/less-1.6.0.min.js +16 -0
  52. data/lib/less/js/dist/less-1.6.1.js +7513 -0
  53. data/lib/less/js/dist/less-1.6.1.min.js +16 -0
  54. data/lib/less/js/dist/less-1.6.2.js +7624 -0
  55. data/lib/less/js/dist/less-1.6.2.min.js +16 -0
  56. data/lib/less/js/dist/less-1.6.3.js +7627 -0
  57. data/lib/less/js/dist/less-1.6.3.min.js +16 -0
  58. data/lib/less/js/dist/less-1.7.0.js +7921 -0
  59. data/lib/less/js/dist/less-1.7.0.min.js +16 -0
  60. data/lib/less/js/dist/less-rhino-1.3.1.js +3725 -0
  61. data/lib/less/js/dist/less-rhino-1.3.2.js +3990 -0
  62. data/lib/less/js/dist/less-rhino-1.3.3.js +4002 -0
  63. data/lib/less/js/dist/less-rhino-1.4.0.js +4273 -0
  64. data/lib/less/js/dist/less-rhino-1.5.1.js +6831 -0
  65. data/lib/less/js/dist/less-rhino-1.6.2.js +9017 -0
  66. data/lib/less/js/dist/less-rhino-1.6.3.js +9020 -0
  67. data/lib/less/js/dist/less-rhino-1.7.0.js +9301 -0
  68. data/lib/less/js/dist/lessc-rhino-1.6.2.js +449 -0
  69. data/lib/less/js/dist/lessc-rhino-1.6.3.js +449 -0
  70. data/lib/less/js/dist/lessc-rhino-1.7.0.js +449 -0
  71. data/lib/less/js/gradle/wrapper/gradle-wrapper.jar +0 -0
  72. data/lib/less/js/gradle/wrapper/gradle-wrapper.properties +6 -0
  73. data/lib/less/js/gradlew +164 -0
  74. data/lib/less/js/gradlew.bat +90 -0
  75. data/lib/less/js/lib/less/browser.js +547 -250
  76. data/lib/less/js/lib/less/encoder.js +4 -0
  77. data/lib/less/js/lib/less/env.js +136 -0
  78. data/lib/less/js/lib/less/extend-visitor.js +416 -0
  79. data/lib/less/js/lib/less/functions.js +576 -36
  80. data/lib/less/js/lib/less/import-visitor.js +142 -0
  81. data/lib/less/js/lib/less/index.js +187 -83
  82. data/lib/less/js/lib/less/join-selector-visitor.js +44 -0
  83. data/lib/less/js/lib/less/lessc_helper.js +82 -0
  84. data/lib/less/js/lib/less/parser.js +1286 -523
  85. data/lib/less/js/lib/less/rhino.js +415 -29
  86. data/lib/less/js/lib/less/source-map-output.js +141 -0
  87. data/lib/less/js/lib/less/to-css-visitor.js +240 -0
  88. data/lib/less/js/lib/less/tree/alpha.js +17 -5
  89. data/lib/less/js/lib/less/tree/anonymous.js +25 -4
  90. data/lib/less/js/lib/less/tree/assignment.js +17 -5
  91. data/lib/less/js/lib/less/tree/call.js +36 -13
  92. data/lib/less/js/lib/less/tree/color.js +119 -31
  93. data/lib/less/js/lib/less/tree/comment.js +18 -4
  94. data/lib/less/js/lib/less/tree/condition.js +35 -28
  95. data/lib/less/js/lib/less/tree/detached-ruleset.js +20 -0
  96. data/lib/less/js/lib/less/tree/dimension.js +291 -16
  97. data/lib/less/js/lib/less/tree/directive.js +56 -22
  98. data/lib/less/js/lib/less/tree/element.js +78 -17
  99. data/lib/less/js/lib/less/tree/expression.js +39 -8
  100. data/lib/less/js/lib/less/tree/extend.js +53 -0
  101. data/lib/less/js/lib/less/tree/import.js +81 -36
  102. data/lib/less/js/lib/less/tree/javascript.js +17 -10
  103. data/lib/less/js/lib/less/tree/keyword.js +8 -3
  104. data/lib/less/js/lib/less/tree/media.js +70 -27
  105. data/lib/less/js/lib/less/tree/mixin.js +250 -70
  106. data/lib/less/js/lib/less/tree/negative.js +24 -0
  107. data/lib/less/js/lib/less/tree/operation.js +38 -13
  108. data/lib/less/js/lib/less/tree/paren.js +9 -2
  109. data/lib/less/js/lib/less/tree/quoted.js +29 -10
  110. data/lib/less/js/lib/less/tree/rule.js +77 -29
  111. data/lib/less/js/lib/less/tree/ruleset-call.js +16 -0
  112. data/lib/less/js/lib/less/tree/ruleset.js +445 -107
  113. data/lib/less/js/lib/less/tree/selector.js +117 -30
  114. data/lib/less/js/lib/less/tree/unicode-descriptor.js +15 -0
  115. data/lib/less/js/lib/less/tree/url.js +43 -15
  116. data/lib/less/js/lib/less/tree/value.js +16 -6
  117. data/lib/less/js/lib/less/tree/variable.js +26 -8
  118. data/lib/less/js/lib/less/tree.js +82 -2
  119. data/lib/less/js/lib/less/visitor.js +146 -0
  120. data/lib/less/js/lib/source-map/source-map-0.1.31.js +1933 -0
  121. data/lib/less/js/lib/source-map/source-map-footer.js +4 -0
  122. data/lib/less/js/lib/source-map/source-map-header.js +3 -0
  123. data/lib/less/js/package.json +86 -11
  124. data/lib/less/js/test/browser/common.js +201 -0
  125. data/lib/less/js/test/browser/css/global-vars/simple.css +3 -0
  126. data/lib/less/js/test/browser/css/modify-vars/simple.css +8 -0
  127. data/lib/less/js/test/browser/css/postProcessor/postProcessor.css +4 -0
  128. data/lib/less/js/test/browser/css/relative-urls/urls.css +35 -0
  129. data/lib/less/js/test/browser/css/rootpath/urls.css +35 -0
  130. data/lib/less/js/test/browser/css/rootpath-relative/urls.css +35 -0
  131. data/lib/less/js/test/browser/css/urls.css +53 -0
  132. data/lib/less/js/test/browser/es5.js +27 -0
  133. data/lib/less/js/test/browser/jasmine-html.js +681 -0
  134. data/lib/less/js/test/browser/jasmine.css +82 -0
  135. data/lib/less/js/test/browser/jasmine.js +2600 -0
  136. data/lib/less/js/test/browser/less/console-errors/test-error.less +3 -0
  137. data/lib/less/js/test/browser/less/console-errors/test-error.txt +2 -0
  138. data/lib/less/js/test/browser/less/global-vars/simple.less +3 -0
  139. data/lib/less/js/test/browser/less/imports/urls.less +4 -0
  140. data/lib/less/js/test/browser/less/imports/urls2.less +4 -0
  141. data/lib/less/js/test/browser/less/modify-vars/imports/simple2.less +4 -0
  142. data/lib/less/js/test/browser/less/modify-vars/simple.less +8 -0
  143. data/lib/less/js/test/browser/less/postProcessor/postProcessor.less +4 -0
  144. data/lib/less/js/test/browser/less/relative-urls/urls.less +33 -0
  145. data/lib/less/js/test/browser/less/rootpath/urls.less +33 -0
  146. data/lib/less/js/test/browser/less/rootpath-relative/urls.less +33 -0
  147. data/lib/less/js/test/browser/less/urls.less +57 -0
  148. data/lib/less/js/test/browser/phantom-runner.js +150 -0
  149. data/lib/less/js/test/browser/runner-browser-options.js +42 -0
  150. data/lib/less/js/test/browser/runner-browser-spec.js +12 -0
  151. data/lib/less/js/test/browser/runner-console-errors.js +5 -0
  152. data/lib/less/js/test/browser/runner-errors-options.js +5 -0
  153. data/lib/less/js/test/browser/runner-errors-spec.js +4 -0
  154. data/lib/less/js/test/browser/runner-global-vars-options.js +4 -0
  155. data/lib/less/js/test/browser/runner-global-vars-spec.js +3 -0
  156. data/lib/less/js/test/browser/runner-legacy-options.js +4 -0
  157. data/lib/less/js/test/browser/runner-legacy-spec.js +3 -0
  158. data/lib/less/js/test/browser/runner-main-options.js +15 -0
  159. data/lib/less/js/test/browser/runner-main-spec.js +3 -0
  160. data/lib/less/js/test/browser/runner-modify-vars-options.js +2 -0
  161. data/lib/less/js/test/browser/runner-modify-vars-spec.js +43 -0
  162. data/lib/less/js/test/browser/runner-no-js-errors-options.js +4 -0
  163. data/lib/less/js/test/browser/runner-no-js-errors-spec.js +4 -0
  164. data/lib/less/js/test/browser/runner-postProcessor-options.js +4 -0
  165. data/lib/less/js/test/browser/runner-postProcessor.js +3 -0
  166. data/lib/less/js/test/browser/runner-production-options.js +3 -0
  167. data/lib/less/js/test/browser/runner-production-spec.js +5 -0
  168. data/lib/less/js/test/browser/runner-relative-urls-options.js +3 -0
  169. data/lib/less/js/test/browser/runner-relative-urls-spec.js +3 -0
  170. data/lib/less/js/test/browser/runner-rootpath-options.js +3 -0
  171. data/lib/less/js/test/browser/runner-rootpath-relative-options.js +4 -0
  172. data/lib/less/js/test/browser/runner-rootpath-relative-spec.js +3 -0
  173. data/lib/less/js/test/browser/runner-rootpath-spec.js +3 -0
  174. data/lib/less/js/test/browser/test-runner-template.tmpl +47 -0
  175. data/lib/less/js/test/css/charsets.css +1 -0
  176. data/lib/less/js/test/css/colors.css +29 -0
  177. data/lib/less/js/test/css/comments.css +15 -2
  178. data/lib/less/js/test/css/compression/compression.css +3 -0
  179. data/lib/less/js/test/css/css-3.css +73 -2
  180. data/lib/less/js/test/css/css-escapes.css +4 -0
  181. data/lib/less/js/test/css/css-guards.css +37 -0
  182. data/lib/less/js/test/css/css.css +20 -14
  183. data/lib/less/js/test/css/debug/linenumbers-all.css +49 -0
  184. data/lib/less/js/test/css/debug/linenumbers-comments.css +40 -0
  185. data/lib/less/js/test/css/debug/linenumbers-mediaquery.css +40 -0
  186. data/lib/less/js/test/css/detached-rulesets.css +71 -0
  187. data/lib/less/js/test/css/empty.css +0 -0
  188. data/lib/less/js/test/css/extend-chaining.css +81 -0
  189. data/lib/less/js/test/css/extend-clearfix.css +19 -0
  190. data/lib/less/js/test/css/extend-exact.css +37 -0
  191. data/lib/less/js/test/css/extend-media.css +24 -0
  192. data/lib/less/js/test/css/extend-nest.css +57 -0
  193. data/lib/less/js/test/css/extend-selector.css +80 -0
  194. data/lib/less/js/test/css/extend.css +76 -0
  195. data/lib/less/js/test/css/extract-and-length.css +133 -0
  196. data/lib/less/js/test/css/functions.css +117 -2
  197. data/lib/less/js/test/css/globalVars/extended.css +12 -0
  198. data/lib/less/js/test/css/globalVars/simple.css +6 -0
  199. data/lib/less/js/test/css/ie-filters.css +7 -3
  200. data/lib/less/js/test/css/import-inline.css +5 -0
  201. data/lib/less/js/test/css/import-interpolation.css +6 -0
  202. data/lib/less/js/test/css/import-once.css +15 -0
  203. data/lib/less/js/test/css/import-reference.css +68 -0
  204. data/lib/less/js/test/css/import.css +23 -10
  205. data/lib/less/js/test/css/javascript.css +3 -2
  206. data/lib/less/js/test/css/legacy/legacy.css +7 -0
  207. data/lib/less/js/test/css/media.css +141 -1
  208. data/lib/less/js/test/css/merge.css +34 -0
  209. data/lib/less/js/test/css/mixins-args.css +41 -4
  210. data/lib/less/js/test/css/mixins-guards-default-func.css +129 -0
  211. data/lib/less/js/test/css/mixins-guards.css +24 -0
  212. data/lib/less/js/test/css/mixins-important.css +28 -0
  213. data/lib/less/js/test/css/mixins-interpolated.css +39 -0
  214. data/lib/less/js/test/css/mixins-named-args.css +27 -0
  215. data/lib/less/js/test/css/mixins-pattern.css +4 -0
  216. data/lib/less/js/test/css/mixins.css +70 -0
  217. data/lib/less/js/test/css/modifyVars/extended.css +9 -0
  218. data/lib/less/js/test/css/no-output.css +0 -0
  219. data/lib/less/js/test/css/parens.css +21 -5
  220. data/lib/less/js/test/css/property-name-interp.css +20 -0
  221. data/lib/less/js/test/css/scope.css +23 -0
  222. data/lib/less/js/test/css/selectors.css +84 -0
  223. data/lib/less/js/test/css/static-urls/urls.css +45 -0
  224. data/lib/less/js/test/css/strings.css +5 -2
  225. data/lib/less/js/test/css/url-args/urls.css +56 -0
  226. data/lib/less/js/test/css/urls.css +71 -0
  227. data/lib/less/js/test/css/variables-in-at-rules.css +18 -0
  228. data/lib/less/js/test/css/variables.css +22 -4
  229. data/lib/less/js/test/css/whitespace.css +4 -0
  230. data/lib/less/js/test/data/data-uri-fail.png +0 -0
  231. data/lib/less/js/test/data/image.jpg +1 -0
  232. data/lib/less/js/test/data/page.html +1 -0
  233. data/lib/less/js/test/index.js +45 -0
  234. data/lib/less/js/test/less/charsets.less +3 -0
  235. data/lib/less/js/test/less/colors.less +37 -4
  236. data/lib/less/js/test/less/comments.less +21 -3
  237. data/lib/less/js/test/less/compression/compression.less +36 -0
  238. data/lib/less/js/test/less/css-3.less +70 -6
  239. data/lib/less/js/test/less/css-escapes.less +6 -1
  240. data/lib/less/js/test/less/css-guards.less +102 -0
  241. data/lib/less/js/test/less/css.less +23 -15
  242. data/lib/less/js/test/less/debug/import/test.less +25 -0
  243. data/lib/less/js/test/less/debug/linenumbers.less +33 -0
  244. data/lib/less/js/test/less/detached-rulesets.less +103 -0
  245. data/lib/less/js/test/less/empty.less +0 -0
  246. data/lib/less/js/test/less/errors/add-mixed-units.less +3 -0
  247. data/lib/less/js/test/less/errors/add-mixed-units.txt +4 -0
  248. data/lib/less/js/test/less/errors/add-mixed-units2.less +3 -0
  249. data/lib/less/js/test/less/errors/add-mixed-units2.txt +4 -0
  250. data/lib/less/js/test/less/errors/at-rules-undefined-var.less +4 -0
  251. data/lib/less/js/test/less/errors/at-rules-undefined-var.txt +4 -0
  252. data/lib/less/js/test/less/errors/bad-variable-declaration1.less +1 -0
  253. data/lib/less/js/test/less/errors/bad-variable-declaration1.txt +2 -0
  254. data/lib/less/js/test/less/errors/color-func-invalid-color.less +3 -0
  255. data/lib/less/js/test/less/errors/color-func-invalid-color.txt +4 -0
  256. data/lib/less/js/test/less/errors/comment-in-selector.less +1 -0
  257. data/lib/less/js/test/less/errors/comment-in-selector.txt +2 -0
  258. data/lib/less/js/test/less/errors/css-guard-default-func.less +4 -0
  259. data/lib/less/js/test/less/errors/css-guard-default-func.txt +4 -0
  260. data/lib/less/js/test/less/errors/detached-ruleset-1.less +6 -0
  261. data/lib/less/js/test/less/errors/detached-ruleset-1.txt +4 -0
  262. data/lib/less/js/test/less/errors/detached-ruleset-2.less +6 -0
  263. data/lib/less/js/test/less/errors/detached-ruleset-2.txt +4 -0
  264. data/lib/less/js/test/less/errors/detached-ruleset-3.less +4 -0
  265. data/lib/less/js/test/less/errors/detached-ruleset-3.txt +4 -0
  266. data/lib/less/js/test/less/errors/detached-ruleset-4.less +5 -0
  267. data/lib/less/js/test/less/errors/detached-ruleset-4.txt +3 -0
  268. data/lib/less/js/test/less/errors/detached-ruleset-5.less +4 -0
  269. data/lib/less/js/test/less/errors/detached-ruleset-5.txt +3 -0
  270. data/lib/less/js/test/less/errors/detached-ruleset-6.less +5 -0
  271. data/lib/less/js/test/less/errors/detached-ruleset-6.txt +4 -0
  272. data/lib/less/js/test/less/errors/divide-mixed-units.less +3 -0
  273. data/lib/less/js/test/less/errors/divide-mixed-units.txt +4 -0
  274. data/lib/less/js/test/less/errors/extend-no-selector.less +3 -0
  275. data/lib/less/js/test/less/errors/extend-no-selector.txt +3 -0
  276. data/lib/less/js/test/less/errors/extend-not-at-end.less +3 -0
  277. data/lib/less/js/test/less/errors/extend-not-at-end.txt +3 -0
  278. data/lib/less/js/test/less/errors/import-missing.less +6 -0
  279. data/lib/less/js/test/less/errors/import-missing.txt +3 -0
  280. data/lib/less/js/test/less/errors/import-no-semi.less +1 -0
  281. data/lib/less/js/test/less/errors/import-no-semi.txt +2 -0
  282. data/lib/less/js/test/less/errors/import-subfolder1.less +1 -0
  283. data/lib/less/js/test/less/errors/import-subfolder1.txt +3 -0
  284. data/lib/less/js/test/less/errors/import-subfolder2.less +1 -0
  285. data/lib/less/js/test/less/errors/import-subfolder2.txt +4 -0
  286. data/lib/less/js/test/less/errors/imports/import-subfolder1.less +1 -0
  287. data/lib/less/js/test/less/errors/imports/import-subfolder2.less +1 -0
  288. data/lib/less/js/test/less/errors/imports/import-test.less +4 -0
  289. data/lib/less/js/test/less/errors/imports/subfolder/mixin-not-defined.less +1 -0
  290. data/lib/less/js/test/less/errors/imports/subfolder/parse-error-curly-bracket.less +1 -0
  291. data/lib/less/js/test/less/errors/javascript-error.less +3 -0
  292. data/lib/less/js/test/less/errors/javascript-error.txt +4 -0
  293. data/lib/less/js/test/less/errors/javascript-undefined-var.less +3 -0
  294. data/lib/less/js/test/less/errors/javascript-undefined-var.txt +4 -0
  295. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-1.less +6 -0
  296. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-1.txt +4 -0
  297. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-2.less +6 -0
  298. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-2.txt +4 -0
  299. data/lib/less/js/test/less/errors/mixin-not-defined.less +11 -0
  300. data/lib/less/js/test/less/errors/mixin-not-defined.txt +3 -0
  301. data/lib/less/js/test/less/errors/mixin-not-matched.less +6 -0
  302. data/lib/less/js/test/less/errors/mixin-not-matched.txt +3 -0
  303. data/lib/less/js/test/less/errors/mixin-not-matched2.less +6 -0
  304. data/lib/less/js/test/less/errors/mixin-not-matched2.txt +3 -0
  305. data/lib/less/js/test/less/errors/mixin-not-visible-in-scope-1.less +9 -0
  306. data/lib/less/js/test/less/errors/mixin-not-visible-in-scope-1.txt +4 -0
  307. data/lib/less/js/test/less/errors/mixins-guards-default-func-1.less +9 -0
  308. data/lib/less/js/test/less/errors/mixins-guards-default-func-1.txt +4 -0
  309. data/lib/less/js/test/less/errors/mixins-guards-default-func-2.less +9 -0
  310. data/lib/less/js/test/less/errors/mixins-guards-default-func-2.txt +4 -0
  311. data/lib/less/js/test/less/errors/mixins-guards-default-func-3.less +9 -0
  312. data/lib/less/js/test/less/errors/mixins-guards-default-func-3.txt +4 -0
  313. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors.less +4 -0
  314. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors.txt +4 -0
  315. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors2.less +4 -0
  316. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors2.txt +4 -0
  317. data/lib/less/js/test/less/errors/multiply-mixed-units.less +7 -0
  318. data/lib/less/js/test/less/errors/multiply-mixed-units.txt +4 -0
  319. data/lib/less/js/test/less/errors/parens-error-1.less +3 -0
  320. data/lib/less/js/test/less/errors/parens-error-1.txt +4 -0
  321. data/lib/less/js/test/less/errors/parens-error-2.less +3 -0
  322. data/lib/less/js/test/less/errors/parens-error-2.txt +4 -0
  323. data/lib/less/js/test/less/errors/parens-error-3.less +3 -0
  324. data/lib/less/js/test/less/errors/parens-error-3.txt +4 -0
  325. data/lib/less/js/test/less/errors/parse-error-curly-bracket.less +4 -0
  326. data/lib/less/js/test/less/errors/parse-error-curly-bracket.txt +4 -0
  327. data/lib/less/js/test/less/errors/parse-error-extra-parens.less +5 -0
  328. data/lib/less/js/test/less/errors/parse-error-extra-parens.txt +3 -0
  329. data/lib/less/js/test/less/errors/parse-error-missing-bracket.less +2 -0
  330. data/lib/less/js/test/less/errors/parse-error-missing-bracket.txt +3 -0
  331. data/lib/less/js/test/less/errors/parse-error-missing-parens.less +5 -0
  332. data/lib/less/js/test/less/errors/parse-error-missing-parens.txt +3 -0
  333. data/lib/less/js/test/less/errors/parse-error-with-import.less +13 -0
  334. data/lib/less/js/test/less/errors/parse-error-with-import.txt +4 -0
  335. data/lib/less/js/test/less/errors/percentage-missing-space.less +3 -0
  336. data/lib/less/js/test/less/errors/percentage-missing-space.txt +4 -0
  337. data/lib/less/js/test/less/errors/property-asterisk-only-name.less +3 -0
  338. data/lib/less/js/test/less/errors/property-asterisk-only-name.txt +4 -0
  339. data/lib/less/js/test/less/errors/property-ie5-hack.less +3 -0
  340. data/lib/less/js/test/less/errors/property-ie5-hack.txt +4 -0
  341. data/lib/less/js/test/less/errors/property-in-root.less +4 -0
  342. data/lib/less/js/test/less/errors/property-in-root.txt +4 -0
  343. data/lib/less/js/test/less/errors/property-in-root2.less +1 -0
  344. data/lib/less/js/test/less/errors/property-in-root2.txt +4 -0
  345. data/lib/less/js/test/less/errors/property-in-root3.less +4 -0
  346. data/lib/less/js/test/less/errors/property-in-root3.txt +3 -0
  347. data/lib/less/js/test/less/errors/property-interp-not-defined.less +1 -0
  348. data/lib/less/js/test/less/errors/property-interp-not-defined.txt +2 -0
  349. data/lib/less/js/test/less/errors/recursive-variable.less +1 -0
  350. data/lib/less/js/test/less/errors/recursive-variable.txt +2 -0
  351. data/lib/less/js/test/less/errors/svg-gradient1.less +3 -0
  352. data/lib/less/js/test/less/errors/svg-gradient1.txt +4 -0
  353. data/lib/less/js/test/less/errors/svg-gradient2.less +3 -0
  354. data/lib/less/js/test/less/errors/svg-gradient2.txt +4 -0
  355. data/lib/less/js/test/less/errors/svg-gradient3.less +3 -0
  356. data/lib/less/js/test/less/errors/svg-gradient3.txt +4 -0
  357. data/lib/less/js/test/less/errors/unit-function.less +3 -0
  358. data/lib/less/js/test/less/errors/unit-function.txt +4 -0
  359. data/lib/less/js/test/less/extend-chaining.less +91 -0
  360. data/lib/less/js/test/less/extend-clearfix.less +19 -0
  361. data/lib/less/js/test/less/extend-exact.less +46 -0
  362. data/lib/less/js/test/less/extend-media.less +24 -0
  363. data/lib/less/js/test/less/extend-nest.less +65 -0
  364. data/lib/less/js/test/less/extend-selector.less +99 -0
  365. data/lib/less/js/test/less/extend.less +81 -0
  366. data/lib/less/js/test/less/extract-and-length.less +133 -0
  367. data/lib/less/js/test/less/functions.less +130 -6
  368. data/lib/less/js/test/less/globalVars/extended.json +5 -0
  369. data/lib/less/js/test/less/globalVars/extended.less +10 -0
  370. data/lib/less/js/test/less/globalVars/simple.json +3 -0
  371. data/lib/less/js/test/less/globalVars/simple.less +3 -0
  372. data/lib/less/js/test/less/ie-filters.less +7 -0
  373. data/lib/less/js/test/less/import/deeper/import-once-test-a.less +1 -0
  374. data/lib/less/js/test/less/import/import-and-relative-paths-test.less +17 -0
  375. data/lib/less/js/test/less/import/import-charset-test.less +1 -0
  376. data/lib/less/js/test/less/import/import-interpolation.less +1 -0
  377. data/lib/less/js/test/less/import/import-interpolation2.less +5 -0
  378. data/lib/less/js/test/less/import/import-once-test-c.less +6 -0
  379. data/lib/less/js/test/less/import/import-reference.less +51 -0
  380. data/lib/less/js/test/less/import/import-test-a.less +1 -0
  381. data/lib/less/js/test/less/import/import-test-c.less +0 -1
  382. data/lib/less/js/test/less/import/import-test-f.less +5 -0
  383. data/lib/less/js/test/less/import/imports/font.less +8 -0
  384. data/lib/less/js/test/less/import/imports/logo.less +5 -0
  385. data/lib/less/js/test/less/import/invalid-css.less +1 -0
  386. data/lib/less/js/test/less/import/urls.less +1 -0
  387. data/lib/less/js/test/less/import-inline.less +2 -0
  388. data/lib/less/js/test/less/import-interpolation.less +8 -0
  389. data/lib/less/js/test/less/import-once.less +6 -0
  390. data/lib/less/js/test/less/import-reference.less +21 -0
  391. data/lib/less/js/test/less/import.less +13 -3
  392. data/lib/less/js/test/less/javascript.less +5 -3
  393. data/lib/less/js/test/less/legacy/legacy.less +7 -0
  394. data/lib/less/js/test/less/media.less +158 -3
  395. data/lib/less/js/test/less/merge.less +78 -0
  396. data/lib/less/js/test/less/mixins-args.less +93 -15
  397. data/lib/less/js/test/less/mixins-guards-default-func.less +195 -0
  398. data/lib/less/js/test/less/mixins-guards.less +65 -0
  399. data/lib/less/js/test/less/mixins-important.less +8 -1
  400. data/lib/less/js/test/less/mixins-interpolated.less +69 -0
  401. data/lib/less/js/test/less/mixins-named-args.less +36 -0
  402. data/lib/less/js/test/less/mixins-nested.less +2 -2
  403. data/lib/less/js/test/less/mixins-pattern.less +4 -1
  404. data/lib/less/js/test/less/mixins.less +74 -0
  405. data/lib/less/js/test/less/modifyVars/extended.json +5 -0
  406. data/lib/less/js/test/less/modifyVars/extended.less +11 -0
  407. data/lib/less/js/test/less/no-js-errors/no-js-errors.less +3 -0
  408. data/lib/less/js/test/less/no-js-errors/no-js-errors.txt +4 -0
  409. data/lib/less/js/test/less/no-output.less +2 -0
  410. data/lib/less/js/test/less/operations.less +27 -27
  411. data/lib/less/js/test/less/parens.less +24 -5
  412. data/lib/less/js/test/less/property-name-interp.less +53 -0
  413. data/lib/less/js/test/less/scope.less +73 -1
  414. data/lib/less/js/test/less/selectors.less +101 -0
  415. data/lib/less/js/test/less/sourcemaps/basic.json +3 -0
  416. data/lib/less/js/test/less/sourcemaps/basic.less +27 -0
  417. data/lib/less/js/test/less/sourcemaps/imported.css +7 -0
  418. data/lib/less/js/test/less/static-urls/urls.less +33 -0
  419. data/lib/less/js/test/less/strings.less +7 -1
  420. data/lib/less/js/test/less/url-args/urls.less +63 -0
  421. data/lib/less/js/test/less/urls.less +72 -0
  422. data/lib/less/js/test/less/variables-in-at-rules.less +20 -0
  423. data/lib/less/js/test/less/variables.less +42 -13
  424. data/lib/less/js/test/less/whitespace.less +7 -0
  425. data/lib/less/js/test/less-test.js +251 -65
  426. data/lib/less/js/test/rhino/test-header.js +15 -0
  427. data/lib/less/js/test/sourcemaps/basic.json +1 -0
  428. data/lib/less/js/test/sourcemaps/index.html +17 -0
  429. data/lib/less/loader.rb +191 -27
  430. data/lib/less/parser.rb +47 -8
  431. data/lib/less/version.rb +2 -2
  432. data/spec/less/loader_spec.rb +45 -0
  433. data/spec/less/parser_spec.rb +43 -17
  434. data/spec/less/some/some.css +0 -0
  435. data/spec/less/some/some.less +4 -0
  436. metadata +352 -57
  437. data/lib/less/js/CHANGELOG +0 -41
  438. data/lib/less/js/Makefile +0 -75
  439. data/lib/less/js/build/ecma-5.js +0 -120
  440. data/lib/less/js/build/header.js +0 -7
  441. data/lib/less/js/lib/less/cssmin.js +0 -355
@@ -1,23 +1,10 @@
1
1
  var less, tree;
2
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,
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
  //
@@ -56,125 +43,212 @@ less.Parser = function Parser(env) {
56
43
  var input, // LeSS input string
57
44
  i, // current index in `input`
58
45
  j, // current chunk
59
- temp, // temporarily holds a chunk's state, for backtracking
60
- memo, // temporarily holds `i`, when backtracking
46
+ saveStack = [], // holds state for backtracking
61
47
  furthest, // furthest index the parser has gone to
62
48
  chunks, // chunkified input
63
- current, // index of current chunk, in `input`
64
- parser;
65
-
66
- var that = this;
67
-
68
- // This function is called after all files
69
- // have been imported through `@import`.
70
- var finish = function () {};
49
+ current, // current chunk
50
+ currentPos, // index of current chunk, in `input`
51
+ parser,
52
+ parsers,
53
+ rootFilename = env && env.filename;
54
+
55
+ // Top parser on an import tree must be sure there is one "env"
56
+ // which will then be passed around by reference.
57
+ if (!(env instanceof tree.parseEnv)) {
58
+ env = new tree.parseEnv(env);
59
+ }
71
60
 
72
61
  var imports = this.imports = {
73
- paths: env && env.paths || [], // Search paths, when importing
74
- queue: [], // Files which haven't been imported yet
75
- files: {}, // Holds the imported parse trees
76
- contents: {}, // Holds the imported file contents
77
- mime: env && env.mime, // MIME type of .less files
78
- error: null, // Error in parsing/evaluating an import
79
- push: function (path, callback) {
80
- var that = this;
62
+ paths: env.paths || [], // Search paths, when importing
63
+ queue: [], // Files which haven't been imported yet
64
+ files: env.files, // Holds the imported parse trees
65
+ contents: env.contents, // Holds the imported file contents
66
+ contentsIgnoredChars: env.contentsIgnoredChars, // lines inserted, not in the original less
67
+ mime: env.mime, // MIME type of .less files
68
+ error: null, // Error in parsing/evaluating an import
69
+ push: function (path, currentFileInfo, importOptions, callback) {
70
+ var parserImports = this;
81
71
  this.queue.push(path);
82
72
 
83
- //
84
- // Import a file asynchronously
85
- //
86
- less.Parser.importer(path, this.paths, function (e, root, contents) {
87
- that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
88
- that.files[path] = root; // Store the root
89
- that.contents[path] = contents;
73
+ var fileParsedFunc = function (e, root, fullPath) {
74
+ parserImports.queue.splice(parserImports.queue.indexOf(path), 1); // Remove the path from the queue
75
+
76
+ var importedPreviously = fullPath === rootFilename;
77
+
78
+ parserImports.files[fullPath] = root; // Store the root
90
79
 
91
- if (e && !that.error) { that.error = e }
92
- callback(e, root);
80
+ if (e && !parserImports.error) { parserImports.error = e; }
93
81
 
94
- if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing
95
- }, env);
82
+ callback(e, root, importedPreviously, fullPath);
83
+ };
84
+
85
+ if (less.Parser.importer) {
86
+ less.Parser.importer(path, currentFileInfo, fileParsedFunc, env);
87
+ } else {
88
+ less.Parser.fileLoader(path, currentFileInfo, function(e, contents, fullPath, newFileInfo) {
89
+ if (e) {fileParsedFunc(e); return;}
90
+
91
+ var newEnv = new tree.parseEnv(env);
92
+
93
+ newEnv.currentFileInfo = newFileInfo;
94
+ newEnv.processImports = false;
95
+ newEnv.contents[fullPath] = contents;
96
+
97
+ if (currentFileInfo.reference || importOptions.reference) {
98
+ newFileInfo.reference = true;
99
+ }
100
+
101
+ if (importOptions.inline) {
102
+ fileParsedFunc(null, contents, fullPath);
103
+ } else {
104
+ new(less.Parser)(newEnv).parse(contents, function (e, root) {
105
+ fileParsedFunc(e, root, fullPath);
106
+ });
107
+ }
108
+ }, env);
109
+ }
96
110
  }
97
111
  };
98
112
 
99
- function save() { temp = chunks[j], memo = i, current = i }
100
- function restore() { chunks[j] = temp, i = memo, current = i }
113
+ function save() { currentPos = i; saveStack.push( { current: current, i: i, j: j }); }
114
+ function restore() { var state = saveStack.pop(); current = state.current; currentPos = i = state.i; j = state.j; }
115
+ function forget() { saveStack.pop(); }
101
116
 
102
117
  function sync() {
103
- if (i > current) {
104
- chunks[j] = chunks[j].slice(i - current);
105
- current = i;
118
+ if (i > currentPos) {
119
+ current = current.slice(i - currentPos);
120
+ currentPos = i;
106
121
  }
107
122
  }
123
+ function isWhitespace(str, pos) {
124
+ var code = str.charCodeAt(pos | 0);
125
+ return (code <= 32) && (code === 32 || code === 10 || code === 9);
126
+ }
108
127
  //
109
128
  // Parse from a token, regexp or string, and move forward if match
110
129
  //
111
130
  function $(tok) {
112
- var match, args, length, c, index, endIndex, k, mem;
131
+ var tokType = typeof tok,
132
+ match, length;
113
133
 
134
+ // Either match a single character in the input,
135
+ // or match a regexp in the current chunk (`current`).
114
136
  //
115
- // Non-terminal
116
- //
117
- if (tok instanceof Function) {
118
- return tok.call(parser.parsers);
119
- //
120
- // Terminal
121
- //
122
- // Either match a single character in the input,
123
- // or match a regexp in the current chunk (chunk[j]).
124
- //
125
- } else if (typeof(tok) === 'string') {
126
- match = input.charAt(i) === tok ? tok : null;
127
- length = 1;
128
- sync ();
129
- } else {
130
- sync ();
131
-
132
- if (match = tok.exec(chunks[j])) {
133
- length = match[0].length;
134
- } else {
137
+ if (tokType === "string") {
138
+ if (input.charAt(i) !== tok) {
135
139
  return null;
136
140
  }
141
+ skipWhitespace(1);
142
+ return tok;
143
+ }
144
+
145
+ // regexp
146
+ sync ();
147
+ if (! (match = tok.exec(current))) {
148
+ return null;
137
149
  }
138
150
 
151
+ length = match[0].length;
152
+
139
153
  // The match is confirmed, add the match length to `i`,
140
154
  // and consume any extra white-space characters (' ' || '\n')
141
155
  // which come after that. The reason for this is that LeSS's
142
156
  // grammar is mostly white-space insensitive.
143
157
  //
144
- if (match) {
145
- mem = i += length;
146
- endIndex = i + chunks[j].length - length;
147
-
148
- while (i < endIndex) {
149
- c = input.charCodeAt(i);
150
- if (! (c === 32 || c === 10 || c === 9)) { break }
151
- i++;
152
- }
153
- chunks[j] = chunks[j].slice(length + (i - mem));
154
- current = i;
158
+ skipWhitespace(length);
159
+
160
+ if(typeof(match) === 'string') {
161
+ return match;
162
+ } else {
163
+ return match.length === 1 ? match[0] : match;
164
+ }
165
+ }
155
166
 
156
- if (chunks[j].length === 0 && j < chunks.length - 1) { j++ }
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
+ }
157
177
 
158
- if(typeof(match) === 'string') {
159
- return match;
160
- } else {
161
- return match.length === 1 ? match[0] : match;
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
+
197
+ function skipWhitespace(length) {
198
+ var oldi = i, oldj = j,
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
+ }
210
+
211
+ if ((c !== 32) && (c !== 10) && (c !== 9) && (c !== 13)) {
212
+ break;
162
213
  }
214
+ }
215
+
216
+ current = current.slice(length + i - mem + curr);
217
+ currentPos = i;
218
+
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
163
223
  }
224
+
225
+ return oldi !== i || oldj !== j;
164
226
  }
165
227
 
166
228
  function expect(arg, msg) {
167
- var result = $(arg);
168
- if (! result) {
169
- error(msg || (typeof(arg) === 'string' ? "expected '" + arg + "' got '" + input.charAt(i) + "'"
170
- : "unexpected token"));
171
- } 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) {
172
232
  return result;
173
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) + "'");
174
245
  }
175
246
 
176
247
  function error(msg, type) {
177
- throw { index: i, type: type || 'Syntax', message: msg };
248
+ var e = new Error(msg);
249
+ e.index = i;
250
+ e.type = type || 'Syntax';
251
+ throw e;
178
252
  }
179
253
 
180
254
  // Same as $(), but don't change the state of the parser,
@@ -183,37 +257,53 @@ less.Parser = function Parser(env) {
183
257
  if (typeof(tok) === 'string') {
184
258
  return input.charAt(i) === tok;
185
259
  } else {
186
- if (tok.test(chunks[j])) {
187
- return true;
188
- } else {
189
- return false;
190
- }
260
+ return tok.test(current);
191
261
  }
192
262
  }
193
263
 
194
- function basename(pathname) {
195
- if (less.mode === 'node') {
196
- return require('path').basename(pathname);
197
- } else {
198
- return pathname.match(/[^\/]+$/)[0];
199
- }
264
+ // Specialization of peek()
265
+ function peekChar(tok) {
266
+ return input.charAt(i) === tok;
200
267
  }
201
268
 
269
+
202
270
  function getInput(e, env) {
203
- if (e.filename && env.filename && (e.filename !== env.filename)) {
204
- return parser.imports.contents[basename(e.filename)];
271
+ if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) {
272
+ return parser.imports.contents[e.filename];
205
273
  } else {
206
274
  return input;
207
275
  }
208
276
  }
209
277
 
210
- function getLocation(index, input) {
211
- for (var n = index, column = -1;
212
- n >= 0 && input.charAt(n) !== '\n';
213
- 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
+ }
286
+
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
+ };
295
+ }
296
+
297
+ function getDebugInfo(index, inputStream, env) {
298
+ var filename = env.currentFileInfo.filename;
299
+ if(less.mode !== 'browser' && less.mode !== 'rhino') {
300
+ filename = require('path').resolve(filename);
301
+ }
214
302
 
215
- return { line: typeof(index) === 'number' ? (input.slice(0, index).match(/\n/g) || "").length : null,
216
- column: column };
303
+ return {
304
+ lineNumber: getLocation(index, inputStream).line + 1,
305
+ fileName: filename
306
+ };
217
307
  }
218
308
 
219
309
  function LessError(e, env) {
@@ -221,15 +311,16 @@ less.Parser = function Parser(env) {
221
311
  loc = getLocation(e.index, input),
222
312
  line = loc.line,
223
313
  col = loc.column,
314
+ callLine = e.call && getLocation(e.call, input).line,
224
315
  lines = input.split('\n');
225
316
 
226
317
  this.type = e.type || 'Syntax';
227
318
  this.message = e.message;
228
- this.filename = e.filename || env.filename;
319
+ this.filename = e.filename || env.currentFileInfo.filename;
229
320
  this.index = e.index;
230
321
  this.line = typeof(line) === 'number' ? line + 1 : null;
231
- this.callLine = e.call && (getLocation(e.call, input).line + 1);
232
- this.callExtract = lines[getLocation(e.call, input).line];
322
+ this.callLine = callLine + 1;
323
+ this.callExtract = lines[callLine];
233
324
  this.stack = e.stack;
234
325
  this.column = col;
235
326
  this.extract = [
@@ -239,6 +330,9 @@ less.Parser = function Parser(env) {
239
330
  ];
240
331
  }
241
332
 
333
+ LessError.prototype = new Error();
334
+ LessError.prototype.constructor = LessError;
335
+
242
336
  this.env = env = env || {};
243
337
 
244
338
  // The optimization level dictates the thoroughness of the parser,
@@ -247,109 +341,183 @@ less.Parser = function Parser(env) {
247
341
  // the individual nodes in the tree.
248
342
  this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
249
343
 
250
- this.env.filename = this.env.filename || null;
251
-
252
344
  //
253
345
  // The Parser
254
346
  //
255
- return parser = {
347
+ parser = {
256
348
 
257
349
  imports: imports,
258
350
  //
259
351
  // Parse an input string into an abstract syntax tree,
260
- // 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
261
355
  //
262
- parse: function (str, callback) {
263
- 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;
360
+
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
+ }
264
368
 
265
- i = j = current = furthest = 0;
266
- input = str.replace(/\r\n/g, '\n');
369
+ str = str.replace(/\r\n/g, '\n');
370
+ // Remove potential UTF Byte Order Mark
371
+ input = str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
372
+ parser.imports.contents[env.currentFileInfo.filename] = str;
267
373
 
268
374
  // Split the input into chunks.
269
- chunks = (function (chunks) {
270
- var j = 0,
271
- skip = /[^"'`\{\}\/\(\)\\]+/g,
272
- comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
273
- string = /"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'|`((?:[^`\\\r\n]|\\.)*)`/g,
274
- level = 0,
275
- match,
276
- chunk = chunks[0],
277
- inParam;
278
-
279
- for (var i = 0, c, cc; i < input.length; i++) {
280
- skip.lastIndex = i;
281
- if (match = skip.exec(input)) {
282
- if (match.index === i) {
283
- i += match[0].length;
284
- chunk.push(match[0]);
285
- }
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;
286
394
  }
287
- c = input.charAt(i);
288
- comment.lastIndex = string.lastIndex = i;
395
+ chunks.push(input.slice(emitFrom, parserCurrentIndex + 1));
396
+ emitFrom = parserCurrentIndex + 1;
397
+ }
289
398
 
290
- if (match = string.exec(input)) {
291
- if (match.index === i) {
292
- i += match[0].length;
293
- chunk.push(match[0]);
294
- c = input.charAt(i);
295
- }
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;
296
404
  }
297
405
 
298
- if (!inParam && c === '/') {
299
- cc = input.charAt(i + 1);
300
- if (cc === '/' || cc === '*') {
301
- if (match = comment.exec(input)) {
302
- if (match.index === i) {
303
- i += match[0].length;
304
- chunk.push(match[0]);
305
- c = input.charAt(i);
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 && !parenLevel) { 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++;
306
446
  }
307
447
  }
308
- }
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;
309
479
  }
480
+ }
310
481
 
311
- switch (c) {
312
- case '{': if (! inParam) { level ++; chunk.push(c); break }
313
- case '}': if (! inParam) { level --; chunk.push(c); chunks[++j] = chunk = []; break }
314
- case '(': if (! inParam) { inParam = true; chunk.push(c); break }
315
- case ')': if ( inParam) { inParam = false; chunk.push(c); break }
316
- default: chunk.push(c);
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);
317
487
  }
318
- }
319
- if (level > 0) {
320
- error = new(LessError)({
321
- index: i,
322
- type: 'Parse',
323
- message: "missing closing `}`",
324
- filename: env.filename
325
- }, env);
488
+ } else if (parenLevel !== 0) {
489
+ return fail("missing closing `)`", lastOpeningParen);
326
490
  }
327
491
 
328
- return chunks.map(function (c) { return c.join('') });;
329
- })([[]]);
492
+ emitChunk(true);
493
+ return chunks;
494
+ })(str);
330
495
 
331
496
  if (error) {
332
- return callback(error);
497
+ return callback(new(LessError)(error, env));
333
498
  }
334
499
 
500
+ current = chunks[0];
501
+
335
502
  // Start with the primary rule.
336
503
  // The whole syntax tree is held under a Ruleset node,
337
504
  // with the `root` property set to true, so no `{}` are
338
505
  // output. The callback is called when the input is parsed.
339
506
  try {
340
- root = new(tree.Ruleset)([], $(this.parsers.primary));
507
+ root = new(tree.Ruleset)(null, this.parsers.primary());
341
508
  root.root = true;
509
+ root.firstRoot = true;
342
510
  } catch (e) {
343
511
  return callback(new(LessError)(e, env));
344
512
  }
345
513
 
346
514
  root.toCSS = (function (evaluate) {
347
- var line, lines, column;
348
-
349
515
  return function (options, variables) {
350
- var frames = [], importError;
351
-
352
516
  options = options || {};
517
+ var evaldRoot,
518
+ css,
519
+ evalEnv = new tree.evalEnv(options);
520
+
353
521
  //
354
522
  // Allows setting variables with a hash, so:
355
523
  //
@@ -373,27 +541,85 @@ less.Parser = function Parser(env) {
373
541
  }
374
542
  value = new(tree.Value)([value]);
375
543
  }
376
- return new(tree.Rule)('@' + k, value, false, 0);
544
+ return new(tree.Rule)('@' + k, value, false, null, 0);
377
545
  });
378
- frames = [new(tree.Ruleset)(null, variables)];
546
+ evalEnv.frames = [new(tree.Ruleset)(null, variables)];
379
547
  }
380
548
 
381
549
  try {
382
- var css = evaluate.call(this, { frames: frames })
383
- .toCSS([], { compress: options.compress || false });
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
+ }
570
+
571
+ for(i = 0; i < preEvalVisitors.length; i++) {
572
+ preEvalVisitors[i].run(root);
573
+ }
574
+
575
+ evaldRoot = evaluate.call(root, evalEnv);
576
+
577
+ for(i = 0; i < visitors.length; i++) {
578
+ visitors[i].run(evaldRoot);
579
+ }
580
+
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({
599
+ compress: Boolean(options.compress),
600
+ dumpLineNumbers: env.dumpLineNumbers,
601
+ strictUnits: Boolean(options.strictUnits),
602
+ numPrecision: 8});
384
603
  } catch (e) {
385
604
  throw new(LessError)(e, env);
386
605
  }
387
606
 
388
- if ((importError = parser.imports.error)) { // Check if there was an error during importing
389
- if (importError instanceof LessError) throw importError;
390
- else throw new(LessError)(importError, env);
391
- }
607
+ if (options.cleancss && less.mode === 'node') {
608
+ var CleanCSS = require('clean-css'),
609
+ cleancssOptions = options.cleancssOptions || {};
392
610
 
393
- if (options.yuicompress && less.mode === 'node') {
394
- return require('./cssmin').compressor.cssmin(css);
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);
395
621
  } else if (options.compress) {
396
- return css.replace(/(\s)+/g, "$1");
622
+ return css.replace(/(^(\s)+)|((\s)+$)/g, "");
397
623
  } else {
398
624
  return css;
399
625
  }
@@ -410,18 +636,17 @@ less.Parser = function Parser(env) {
410
636
  // and the part which didn't), so we can color them differently.
411
637
  if (i < input.length - 1) {
412
638
  i = furthest;
639
+ var loc = getLocation(i, input);
413
640
  lines = input.split('\n');
414
- line = (input.slice(0, i).match(/\n/g) || "").length + 1;
415
-
416
- for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
641
+ line = loc.line + 1;
417
642
 
418
643
  error = {
419
644
  type: "Parse",
420
- message: "Syntax Error on line " + line,
645
+ message: "Unrecognised input",
421
646
  index: i,
422
- filename: env.filename,
647
+ filename: env.currentFileInfo.filename,
423
648
  line: line,
424
- column: column,
649
+ column: loc.column,
425
650
  extract: [
426
651
  lines[line - 2],
427
652
  lines[line - 1],
@@ -430,10 +655,26 @@ less.Parser = function Parser(env) {
430
655
  };
431
656
  }
432
657
 
433
- if (this.imports.queue.length > 0) {
434
- finish = function () { callback(error, root) };
658
+ var finish = function (e) {
659
+ e = error || e || parser.imports.error;
660
+
661
+ if (e) {
662
+ if (!(e instanceof LessError)) {
663
+ e = new(LessError)(e, env);
664
+ }
665
+
666
+ return callback(e);
667
+ }
668
+ else {
669
+ return callback(null, root);
670
+ }
671
+ };
672
+
673
+ if (env.processImports !== false) {
674
+ new tree.importVisitor(this.imports, finish)
675
+ .run(root);
435
676
  } else {
436
- callback(error, root);
677
+ return finish();
437
678
  }
438
679
  },
439
680
 
@@ -466,7 +707,7 @@ less.Parser = function Parser(env) {
466
707
  // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
467
708
  // first, before parsing, that's when we use `peek()`.
468
709
  //
469
- parsers: {
710
+ parsers: parsers = {
470
711
  //
471
712
  // The `primary` rule is the *entry* and *exit* point of the parser.
472
713
  // The rules here can appear at any level of the parse tree.
@@ -483,13 +724,24 @@ less.Parser = function Parser(env) {
483
724
  // block rule: at the root level.
484
725
  //
485
726
  primary: function () {
486
- var node, root = [];
487
-
488
- while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) ||
489
- $(this.mixin.call) || $(this.comment) || $(this.directive))
490
- || $(/^[\s\n]+/)) {
491
- node && root.push(node);
727
+ var mixin = this.mixin, $re = _$re, root = [], node;
728
+
729
+ while (current)
730
+ {
731
+ node = this.extendRule() || mixin.definition() || this.rule() || this.ruleset() ||
732
+ mixin.call() || this.comment() || this.rulesetCall() || this.directive();
733
+ if (node) {
734
+ root.push(node);
735
+ } else {
736
+ if (!($re(/^[\s\n]+/) || $re(/^;+/))) {
737
+ break;
738
+ }
739
+ }
740
+ if (peekChar('}')) {
741
+ break;
742
+ }
492
743
  }
744
+
493
745
  return root;
494
746
  },
495
747
 
@@ -499,13 +751,29 @@ less.Parser = function Parser(env) {
499
751
  comment: function () {
500
752
  var comment;
501
753
 
502
- if (input.charAt(i) !== '/') return;
754
+ if (input.charAt(i) !== '/') { return; }
503
755
 
504
756
  if (input.charAt(i + 1) === '/') {
505
- return new(tree.Comment)($(/^\/\/.*/), true);
506
- } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
507
- return new(tree.Comment)(comment);
757
+ return new(tree.Comment)($re(/^\/\/.*/), true, i, env.currentFileInfo);
758
+ }
759
+ comment = $re(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/);
760
+ if (comment) {
761
+ return new(tree.Comment)(comment, false, i, env.currentFileInfo);
762
+ }
763
+ },
764
+
765
+ comments: function () {
766
+ var comment, comments = [];
767
+
768
+ while(true) {
769
+ comment = this.comment();
770
+ if (!comment) {
771
+ break;
772
+ }
773
+ comments.push(comment);
508
774
  }
775
+
776
+ return comments;
509
777
  },
510
778
 
511
779
  //
@@ -518,15 +786,16 @@ less.Parser = function Parser(env) {
518
786
  // "milky way" 'he\'s the one!'
519
787
  //
520
788
  quoted: function () {
521
- var str, j = i, e;
789
+ var str, j = i, e, index = i;
522
790
 
523
- if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
524
- if (input.charAt(j) !== '"' && input.charAt(j) !== "'") return;
791
+ if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings
792
+ if (input.charAt(j) !== '"' && input.charAt(j) !== "'") { return; }
525
793
 
526
- e && $('~');
794
+ if (e) { $char('~'); }
527
795
 
528
- if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
529
- return new(tree.Quoted)(str[0], str[1] || str[2], e);
796
+ str = $re(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/);
797
+ if (str) {
798
+ return new(tree.Quoted)(str[0], str[1] || str[2], e, index, env.currentFileInfo);
530
799
  }
531
800
  },
532
801
 
@@ -538,13 +807,13 @@ less.Parser = function Parser(env) {
538
807
  keyword: function () {
539
808
  var k;
540
809
 
541
- if (k = $(/^[_A-Za-z-][_A-Za-z0-9-]*/)) {
542
- if (tree.colors.hasOwnProperty(k)) {
543
- // detect named color
544
- return new(tree.Color)(tree.colors[k].slice(1));
545
- } else {
546
- return new(tree.Keyword)(k);
810
+ k = $re(/^%|^[_A-Za-z-][_A-Za-z0-9-]*/);
811
+ if (k) {
812
+ var color = tree.Color.fromKeyword(k);
813
+ if (color) {
814
+ return color;
547
815
  }
816
+ return new(tree.Keyword)(k);
548
817
  }
549
818
  },
550
819
 
@@ -559,38 +828,56 @@ less.Parser = function Parser(env) {
559
828
  // The arguments are parsed with the `entities.arguments` parser.
560
829
  //
561
830
  call: function () {
562
- var name, args, index = i;
831
+ var name, nameLC, args, alpha_ret, index = i;
563
832
 
564
- if (! (name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(chunks[j]))) return;
833
+ name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(current);
834
+ if (!name) { return; }
565
835
 
566
- name = name[1].toLowerCase();
836
+ name = name[1];
837
+ nameLC = name.toLowerCase();
838
+ if (nameLC === 'url') {
839
+ return null;
840
+ }
567
841
 
568
- if (name === 'url') { return null }
569
- else { i += name.length }
842
+ i += name.length;
570
843
 
571
- if (name === 'alpha') { return $(this.alpha) }
844
+ if (nameLC === 'alpha') {
845
+ alpha_ret = parsers.alpha();
846
+ if(typeof alpha_ret !== 'undefined') {
847
+ return alpha_ret;
848
+ }
849
+ }
572
850
 
573
- $('('); // Parse the '(' and consume whitespace.
851
+ $char('('); // Parse the '(' and consume whitespace.
574
852
 
575
- args = $(this.entities.arguments);
853
+ args = this.arguments();
576
854
 
577
- if (! $(')')) return;
855
+ if (! $char(')')) {
856
+ return;
857
+ }
578
858
 
579
- if (name) { return new(tree.Call)(name, args, index, env.filename) }
859
+ if (name) { return new(tree.Call)(name, args, index, env.currentFileInfo); }
580
860
  },
581
861
  arguments: function () {
582
862
  var args = [], arg;
583
863
 
584
- while (arg = $(this.entities.assignment) || $(this.expression)) {
864
+ while (true) {
865
+ arg = this.assignment() || parsers.expression();
866
+ if (!arg) {
867
+ break;
868
+ }
585
869
  args.push(arg);
586
- if (! $(',')) { break }
870
+ if (! $char(',')) {
871
+ break;
872
+ }
587
873
  }
588
874
  return args;
589
875
  },
590
876
  literal: function () {
591
- return $(this.entities.dimension) ||
592
- $(this.entities.color) ||
593
- $(this.entities.quoted);
877
+ return this.dimension() ||
878
+ this.color() ||
879
+ this.quoted() ||
880
+ this.unicodeDescriptor();
594
881
  },
595
882
 
596
883
  // Assignments are argument entities for calls.
@@ -601,7 +888,15 @@ less.Parser = function Parser(env) {
601
888
 
602
889
  assignment: function () {
603
890
  var key, value;
604
- if ((key = $(/^\w+(?=\s?=)/i)) && $('=') && (value = $(this.entity))) {
891
+ key = $re(/^\w+(?=\s?=)/i);
892
+ if (!key) {
893
+ return;
894
+ }
895
+ if (!$char('=')) {
896
+ return;
897
+ }
898
+ value = parsers.entity();
899
+ if (value) {
605
900
  return new(tree.Assignment)(key, value);
606
901
  }
607
902
  },
@@ -616,28 +911,17 @@ less.Parser = function Parser(env) {
616
911
  url: function () {
617
912
  var value;
618
913
 
619
- if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
620
- value = $(this.entities.quoted) || $(this.entities.variable) ||
621
- $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
622
-
623
- expect(')');
624
-
625
- return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
626
- ? value : new(tree.Anonymous)(value), imports.paths);
627
- },
914
+ if (input.charAt(i) !== 'u' || !$re(/^url\(/)) {
915
+ return;
916
+ }
628
917
 
629
- dataURI: function () {
630
- var obj;
918
+ value = this.quoted() || this.variable() ||
919
+ $re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
631
920
 
632
- if ($(/^data:/)) {
633
- obj = {};
634
- obj.mime = $(/^[^\/]+\/[^,;)]+/) || '';
635
- obj.charset = $(/^;\s*charset=[^,;)]+/) || '';
636
- obj.base64 = $(/^;\s*base64/) || '';
637
- obj.data = $(/^,\s*[^)]+/);
921
+ expectChar(')');
638
922
 
639
- if (obj.data) { return obj }
640
- }
923
+ return new(tree.URL)((value.value != null || value instanceof tree.Variable)
924
+ ? value : new(tree.Anonymous)(value), env.currentFileInfo);
641
925
  },
642
926
 
643
927
  //
@@ -651,8 +935,17 @@ less.Parser = function Parser(env) {
651
935
  variable: function () {
652
936
  var name, index = i;
653
937
 
654
- if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
655
- return new(tree.Variable)(name, index, env.filename);
938
+ if (input.charAt(i) === '@' && (name = $re(/^@@?[\w-]+/))) {
939
+ return new(tree.Variable)(name, index, env.currentFileInfo);
940
+ }
941
+ },
942
+
943
+ // A variable entity useing the protective {} e.g. @{var}
944
+ variableCurly: function () {
945
+ var curly, index = i;
946
+
947
+ if (input.charAt(i) === '@' && (curly = $re(/^@\{([\w-]+)\}/))) {
948
+ return new(tree.Variable)("@" + curly[1], index, env.currentFileInfo);
656
949
  }
657
950
  },
658
951
 
@@ -666,7 +959,7 @@ less.Parser = function Parser(env) {
666
959
  color: function () {
667
960
  var rgb;
668
961
 
669
- if (input.charAt(i) === '#' && (rgb = $(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/))) {
962
+ if (input.charAt(i) === '#' && (rgb = $re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) {
670
963
  return new(tree.Color)(rgb[1]);
671
964
  }
672
965
  },
@@ -678,13 +971,31 @@ less.Parser = function Parser(env) {
678
971
  //
679
972
  dimension: function () {
680
973
  var value, c = input.charCodeAt(i);
681
- if ((c > 57 || c < 45) || c === 47) return;
974
+ //Is the first char of the dimension 0-9, '.', '+' or '-'
975
+ if ((c > 57 || c < 43) || c === 47 || c == 44) {
976
+ return;
977
+ }
682
978
 
683
- if (value = $(/^(-?\d*\.?\d+)(px|%|em|rem|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
979
+ value = $re(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/);
980
+ if (value) {
684
981
  return new(tree.Dimension)(value[1], value[2]);
685
982
  }
686
983
  },
687
984
 
985
+ //
986
+ // A unicode descriptor, as is used in unicode-range
987
+ //
988
+ // U+0?? or U+00A1-00A9
989
+ //
990
+ unicodeDescriptor: function () {
991
+ var ud;
992
+
993
+ ud = $re(/^U\+[0-9a-fA-F?]+(\-[0-9a-fA-F?]+)?/);
994
+ if (ud) {
995
+ return new(tree.UnicodeDescriptor)(ud[0]);
996
+ }
997
+ },
998
+
688
999
  //
689
1000
  // JavaScript code to be evaluated
690
1001
  //
@@ -693,12 +1004,16 @@ less.Parser = function Parser(env) {
693
1004
  javascript: function () {
694
1005
  var str, j = i, e;
695
1006
 
696
- if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
697
- if (input.charAt(j) !== '`') { return }
1007
+ if (input.charAt(j) === '~') { j++; e = true; } // Escaped strings
1008
+ if (input.charAt(j) !== '`') { return; }
1009
+ if (env.javascriptEnabled !== undefined && !env.javascriptEnabled) {
1010
+ error("You are using JavaScript, which has been disabled.");
1011
+ }
698
1012
 
699
- e && $('~');
1013
+ if (e) { $char('~'); }
700
1014
 
701
- if (str = $(/^`([^`]*)`/)) {
1015
+ str = $re(/^`([^`]*)`/);
1016
+ if (str) {
702
1017
  return new(tree.JavaScript)(str[1], i, e);
703
1018
  }
704
1019
  }
@@ -712,26 +1027,62 @@ less.Parser = function Parser(env) {
712
1027
  variable: function () {
713
1028
  var name;
714
1029
 
715
- if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] }
1030
+ if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
716
1031
  },
717
1032
 
718
1033
  //
719
- // A font size/line-height shorthand
1034
+ // The variable part of a variable definition. Used in the `rule` parser
1035
+ //
1036
+ // @fink();
720
1037
  //
721
- // small/12px
1038
+ rulesetCall: function () {
1039
+ var name;
1040
+
1041
+ if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) {
1042
+ return new tree.RulesetCall(name[1]);
1043
+ }
1044
+ },
1045
+
722
1046
  //
723
- // We need to peek first, or we'll match on keywords and dimensions
1047
+ // extend syntax - used to extend selectors
724
1048
  //
725
- shorthand: function () {
726
- var a, b;
1049
+ extend: function(isRule) {
1050
+ var elements, e, index = i, option, extendList, extend;
1051
+
1052
+ if (!(isRule ? $re(/^&:extend\(/) : $re(/^:extend\(/))) { return; }
1053
+
1054
+ do {
1055
+ option = null;
1056
+ elements = null;
1057
+ while (! (option = $re(/^(all)(?=\s*(\)|,))/))) {
1058
+ e = this.element();
1059
+ if (!e) { break; }
1060
+ if (elements) { elements.push(e); } else { elements = [ e ]; }
1061
+ }
1062
+
1063
+ option = option && option[1];
727
1064
 
728
- if (! peek(/^[@\w.%-]+\/[@\w.-]+/)) return;
1065
+ extend = new(tree.Extend)(new(tree.Selector)(elements), option, index);
1066
+ if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
729
1067
 
730
- if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) {
731
- return new(tree.Shorthand)(a, b);
1068
+ } while($char(","));
1069
+
1070
+ expect(/^\)/);
1071
+
1072
+ if (isRule) {
1073
+ expect(/^;/);
732
1074
  }
1075
+
1076
+ return extendList;
733
1077
  },
734
1078
 
1079
+ //
1080
+ // extendRule - used in a rule to extend all the parent selectors
1081
+ //
1082
+ extendRule: function() {
1083
+ return this.extend(true);
1084
+ },
1085
+
735
1086
  //
736
1087
  // Mixins
737
1088
  //
@@ -748,25 +1099,158 @@ less.Parser = function Parser(env) {
748
1099
  // selector for now.
749
1100
  //
750
1101
  call: function () {
751
- var elements = [], e, c, args, index = i, s = input.charAt(i), important = false;
1102
+ var s = input.charAt(i), important = false, index = i, elemIndex,
1103
+ elements, elem, e, c, args;
752
1104
 
753
- if (s !== '.' && s !== '#') { return }
1105
+ if (s !== '.' && s !== '#') { return; }
754
1106
 
755
- while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) {
756
- elements.push(new(tree.Element)(c, e, i));
757
- c = $('>');
758
- }
759
- $('(') && (args = $(this.entities.arguments)) && $(')');
1107
+ save(); // stop us absorbing part of an invalid selector
760
1108
 
761
- if ($(this.important)) {
762
- important = true;
1109
+ while (true) {
1110
+ elemIndex = i;
1111
+ e = $re(/^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/);
1112
+ if (!e) {
1113
+ break;
1114
+ }
1115
+ elem = new(tree.Element)(c, e, elemIndex, env.currentFileInfo);
1116
+ if (elements) { elements.push(elem); } else { elements = [ elem ]; }
1117
+ c = $char('>');
763
1118
  }
764
1119
 
765
- if (elements.length > 0 && ($(';') || peek('}'))) {
766
- return new(tree.mixin.Call)(elements, args || [], index, env.filename, important);
1120
+ if (elements) {
1121
+ if ($char('(')) {
1122
+ args = this.args(true).args;
1123
+ expectChar(')');
1124
+ }
1125
+
1126
+ if (parsers.important()) {
1127
+ important = true;
1128
+ }
1129
+
1130
+ if (parsers.end()) {
1131
+ forget();
1132
+ return new(tree.mixin.Call)(elements, args, index, env.currentFileInfo, important);
1133
+ }
767
1134
  }
1135
+
1136
+ restore();
768
1137
  },
1138
+ args: function (isCall) {
1139
+ var parsers = parser.parsers, entities = parsers.entities,
1140
+ returner = { args:null, variadic: false },
1141
+ expressions = [], argsSemiColon = [], argsComma = [],
1142
+ isSemiColonSeperated, expressionContainsNamed, name, nameLoop, value, arg;
769
1143
 
1144
+ save();
1145
+
1146
+ while (true) {
1147
+ if (isCall) {
1148
+ arg = parsers.detachedRuleset() || parsers.expression();
1149
+ } else {
1150
+ parsers.comments();
1151
+ if (input.charAt(i) === '.' && $re(/^\.{3}/)) {
1152
+ returner.variadic = true;
1153
+ if ($char(";") && !isSemiColonSeperated) {
1154
+ isSemiColonSeperated = true;
1155
+ }
1156
+ (isSemiColonSeperated ? argsSemiColon : argsComma)
1157
+ .push({ variadic: true });
1158
+ break;
1159
+ }
1160
+ arg = entities.variable() || entities.literal() || entities.keyword();
1161
+ }
1162
+
1163
+ if (!arg) {
1164
+ break;
1165
+ }
1166
+
1167
+ nameLoop = null;
1168
+ if (arg.throwAwayComments) {
1169
+ arg.throwAwayComments();
1170
+ }
1171
+ value = arg;
1172
+ var val = null;
1173
+
1174
+ if (isCall) {
1175
+ // Variable
1176
+ if (arg.value && arg.value.length == 1) {
1177
+ val = arg.value[0];
1178
+ }
1179
+ } else {
1180
+ val = arg;
1181
+ }
1182
+
1183
+ if (val && val instanceof tree.Variable) {
1184
+ if ($char(':')) {
1185
+ if (expressions.length > 0) {
1186
+ if (isSemiColonSeperated) {
1187
+ error("Cannot mix ; and , as delimiter types");
1188
+ }
1189
+ expressionContainsNamed = true;
1190
+ }
1191
+
1192
+ // we do not support setting a ruleset as a default variable - it doesn't make sense
1193
+ // However if we do want to add it, there is nothing blocking it, just don't error
1194
+ // and remove isCall dependency below
1195
+ value = (isCall && parsers.detachedRuleset()) || parsers.expression();
1196
+
1197
+ if (!value) {
1198
+ if (isCall) {
1199
+ error("could not understand value for named argument");
1200
+ } else {
1201
+ restore();
1202
+ returner.args = [];
1203
+ return returner;
1204
+ }
1205
+ }
1206
+ nameLoop = (name = val.name);
1207
+ } else if (!isCall && $re(/^\.{3}/)) {
1208
+ returner.variadic = true;
1209
+ if ($char(";") && !isSemiColonSeperated) {
1210
+ isSemiColonSeperated = true;
1211
+ }
1212
+ (isSemiColonSeperated ? argsSemiColon : argsComma)
1213
+ .push({ name: arg.name, variadic: true });
1214
+ break;
1215
+ } else if (!isCall) {
1216
+ name = nameLoop = val.name;
1217
+ value = null;
1218
+ }
1219
+ }
1220
+
1221
+ if (value) {
1222
+ expressions.push(value);
1223
+ }
1224
+
1225
+ argsComma.push({ name:nameLoop, value:value });
1226
+
1227
+ if ($char(',')) {
1228
+ continue;
1229
+ }
1230
+
1231
+ if ($char(';') || isSemiColonSeperated) {
1232
+
1233
+ if (expressionContainsNamed) {
1234
+ error("Cannot mix ; and , as delimiter types");
1235
+ }
1236
+
1237
+ isSemiColonSeperated = true;
1238
+
1239
+ if (expressions.length > 1) {
1240
+ value = new(tree.Value)(expressions);
1241
+ }
1242
+ argsSemiColon.push({ name:name, value:value });
1243
+
1244
+ name = null;
1245
+ expressions = [];
1246
+ expressionContainsNamed = false;
1247
+ }
1248
+ }
1249
+
1250
+ forget();
1251
+ returner.args = isSemiColonSeperated ? argsSemiColon : argsComma;
1252
+ return returner;
1253
+ },
770
1254
  //
771
1255
  // A Mixin definition, with a list of parameters
772
1256
  //
@@ -787,54 +1271,49 @@ less.Parser = function Parser(env) {
787
1271
  // the `{...}` block.
788
1272
  //
789
1273
  definition: function () {
790
- var name, params = [], match, ruleset, param, value, cond, variadic = false;
1274
+ var name, params = [], match, ruleset, cond, variadic = false;
791
1275
  if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
792
- peek(/^[^{]*(;|})/)) return;
1276
+ peek(/^[^{]*\}/)) {
1277
+ return;
1278
+ }
793
1279
 
794
1280
  save();
795
1281
 
796
- if (match = $(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)) {
1282
+ match = $re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/);
1283
+ if (match) {
797
1284
  name = match[1];
798
1285
 
799
- do {
800
- if (input.charAt(i) === '.' && $(/^\.{3}/)) {
801
- variadic = true;
802
- break;
803
- } else if (param = $(this.entities.variable) || $(this.entities.literal)
804
- || $(this.entities.keyword)) {
805
- // Variable
806
- if (param instanceof tree.Variable) {
807
- if ($(':')) {
808
- value = expect(this.expression, 'expected expression');
809
- params.push({ name: param.name, value: value });
810
- } else if ($(/^\.{3}/)) {
811
- params.push({ name: param.name, variadic: true });
812
- variadic = true;
813
- break;
814
- } else {
815
- params.push({ name: param.name });
816
- }
817
- } else {
818
- params.push({ value: param });
819
- }
820
- } else {
821
- break;
822
- }
823
- } while ($(','))
824
-
825
- expect(')');
1286
+ var argInfo = this.args(false);
1287
+ params = argInfo.args;
1288
+ variadic = argInfo.variadic;
1289
+
1290
+ // .mixincall("@{a}");
1291
+ // looks a bit like a mixin definition..
1292
+ // also
1293
+ // .mixincall(@a: {rule: set;});
1294
+ // so we have to be nice and restore
1295
+ if (!$char(')')) {
1296
+ furthest = i;
1297
+ restore();
1298
+ return;
1299
+ }
1300
+
1301
+ parsers.comments();
826
1302
 
827
- if ($(/^when/)) { // Guard
828
- cond = expect(this.conditions, 'expected condition');
1303
+ if ($re(/^when/)) { // Guard
1304
+ cond = expect(parsers.conditions, 'expected condition');
829
1305
  }
830
1306
 
831
- ruleset = $(this.block);
1307
+ ruleset = parsers.block();
832
1308
 
833
1309
  if (ruleset) {
1310
+ forget();
834
1311
  return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
835
1312
  } else {
836
1313
  restore();
837
1314
  }
1315
+ } else {
1316
+ forget();
838
1317
  }
839
1318
  }
840
1319
  },
@@ -844,9 +1323,11 @@ less.Parser = function Parser(env) {
844
1323
  // and can be found inside a rule's value.
845
1324
  //
846
1325
  entity: function () {
847
- return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
848
- $(this.entities.call) || $(this.entities.keyword) || $(this.entities.javascript) ||
849
- $(this.comment);
1326
+ var entities = this.entities;
1327
+
1328
+ return entities.literal() || entities.variable() || entities.url() ||
1329
+ entities.call() || entities.keyword() || entities.javascript() ||
1330
+ this.comment();
850
1331
  },
851
1332
 
852
1333
  //
@@ -855,7 +1336,7 @@ less.Parser = function Parser(env) {
855
1336
  // it's there, if ';' was ommitted.
856
1337
  //
857
1338
  end: function () {
858
- return $(';') || peek('}');
1339
+ return $char(';') || peekChar('}');
859
1340
  },
860
1341
 
861
1342
  //
@@ -866,9 +1347,10 @@ less.Parser = function Parser(env) {
866
1347
  alpha: function () {
867
1348
  var value;
868
1349
 
869
- if (! $(/^\(opacity=/i)) return;
870
- if (value = $(/^\d+/) || $(this.entities.variable)) {
871
- expect(')');
1350
+ if (! $re(/^\(opacity=/i)) { return; }
1351
+ value = $re(/^\d+/) || this.entities.variable();
1352
+ if (value) {
1353
+ expectChar(')');
872
1354
  return new(tree.Alpha)(value);
873
1355
  }
874
1356
  },
@@ -886,21 +1368,29 @@ less.Parser = function Parser(env) {
886
1368
  // and an element name, such as a tag a class, or `*`.
887
1369
  //
888
1370
  element: function () {
889
- var e, t, c, v;
1371
+ var e, c, v, index = i;
890
1372
 
891
- c = $(this.combinator);
892
- e = $(/^(?:\d+\.\d+|\d+)%/) || $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) ||
893
- $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
1373
+ c = this.combinator();
1374
+
1375
+ e = $re(/^(?:\d+\.\d+|\d+)%/) || $re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
1376
+ $char('*') || $char('&') || this.attribute() || $re(/^\([^()@]+\)/) || $re(/^[\.#](?=@)/) ||
1377
+ this.entities.variableCurly();
894
1378
 
895
1379
  if (! e) {
896
- $('(') && (v = $(this.entities.variable)) && $(')') && (e = new(tree.Paren)(v));
1380
+ save();
1381
+ if ($char('(')) {
1382
+ if ((v = this.selector()) && $char(')')) {
1383
+ e = new(tree.Paren)(v);
1384
+ forget();
1385
+ } else {
1386
+ restore();
1387
+ }
1388
+ } else {
1389
+ forget();
1390
+ }
897
1391
  }
898
1392
 
899
- if (e) { return new(tree.Element)(c, e, i) }
900
-
901
- if (c.value && c.value.charAt(0) === '&') {
902
- return new(tree.Element)(c, null, i);
903
- }
1393
+ if (e) { return new(tree.Element)(c, e, index, env.currentFileInfo); }
904
1394
  },
905
1395
 
906
1396
  //
@@ -913,27 +1403,29 @@ less.Parser = function Parser(env) {
913
1403
  // we deal with this in *combinator.js*.
914
1404
  //
915
1405
  combinator: function () {
916
- var match, c = input.charAt(i);
917
-
918
- if (c === '>' || c === '+' || c === '~') {
919
- i++;
920
- while (input.charAt(i) === ' ') { i++ }
921
- return new(tree.Combinator)(c);
922
- } else if (c === '&') {
923
- match = '&';
1406
+ var c = input.charAt(i);
1407
+
1408
+ if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') {
924
1409
  i++;
925
- if(input.charAt(i) === ' ') {
926
- match = '& ';
1410
+ if (input.charAt(i) === '^') {
1411
+ c = '^^';
1412
+ i++;
927
1413
  }
928
- while (input.charAt(i) === ' ') { i++ }
929
- return new(tree.Combinator)(match);
930
- } else if (input.charAt(i - 1) === ' ') {
1414
+ while (isWhitespace(input, i)) { i++; }
1415
+ return new(tree.Combinator)(c);
1416
+ } else if (isWhitespace(input, i - 1)) {
931
1417
  return new(tree.Combinator)(" ");
932
1418
  } else {
933
1419
  return new(tree.Combinator)(null);
934
1420
  }
935
1421
  },
936
-
1422
+ //
1423
+ // A CSS selector (see selector below)
1424
+ // with less extensions e.g. the ability to extend and guard
1425
+ //
1426
+ lessSelector: function () {
1427
+ return this.selector(true);
1428
+ },
937
1429
  //
938
1430
  // A CSS Selector
939
1431
  //
@@ -942,41 +1434,48 @@ less.Parser = function Parser(env) {
942
1434
  //
943
1435
  // Selectors are made out of one or more Elements, see above.
944
1436
  //
945
- selector: function () {
946
- var sel, e, elements = [], c, match;
947
-
948
- if ($('(')) {
949
- sel = $(this.entity);
950
- expect(')');
951
- return new(tree.Selector)([new(tree.Element)('', sel, i)]);
952
- }
953
-
954
- while (e = $(this.element)) {
955
- c = input.charAt(i);
956
- elements.push(e)
957
- if (c === '{' || c === '}' || c === ';' || c === ',') { break }
1437
+ selector: function (isLess) {
1438
+ var index = i, $re = _$re, elements, extendList, c, e, extend, when, condition;
1439
+
1440
+ while ((isLess && (extend = this.extend())) || (isLess && (when = $re(/^when/))) || (e = this.element())) {
1441
+ if (when) {
1442
+ condition = expect(this.conditions, 'expected condition');
1443
+ } else if (condition) {
1444
+ error("CSS guard can only be used at the end of selector");
1445
+ } else if (extend) {
1446
+ if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; }
1447
+ } else {
1448
+ if (extendList) { error("Extend can only be used at the end of selector"); }
1449
+ c = input.charAt(i);
1450
+ if (elements) { elements.push(e); } else { elements = [ e ]; }
1451
+ e = null;
1452
+ }
1453
+ if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
1454
+ break;
1455
+ }
958
1456
  }
959
1457
 
960
- if (elements.length > 0) { return new(tree.Selector)(elements) }
961
- },
962
- tag: function () {
963
- return $(/^[a-zA-Z][a-zA-Z-]*[0-9]?/) || $('*');
1458
+ if (elements) { return new(tree.Selector)(elements, extendList, condition, index, env.currentFileInfo); }
1459
+ if (extendList) { error("Extend must be used to extend a selector, it cannot be used on its own"); }
964
1460
  },
965
1461
  attribute: function () {
966
- var attr = '', key, val, op;
1462
+ if (! $char('[')) { return; }
1463
+
1464
+ var entities = this.entities,
1465
+ key, val, op;
967
1466
 
968
- if (! $('[')) return;
1467
+ if (!(key = entities.variableCurly())) {
1468
+ key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
1469
+ }
969
1470
 
970
- if (key = $(/^[a-zA-Z-]+/) || $(this.entities.quoted)) {
971
- if ((op = $(/^[|~*$^]?=/)) &&
972
- (val = $(this.entities.quoted) || $(/^[\w-]+/))) {
973
- attr = [key, op, val.toCSS ? val.toCSS() : val].join('');
974
- } else { attr = key }
1471
+ op = $re(/^[|~*$^]?=/);
1472
+ if (op) {
1473
+ val = entities.quoted() || $re(/^[0-9]+%/) || $re(/^[\w-]+/) || entities.variableCurly();
975
1474
  }
976
1475
 
977
- if (! $(']')) return;
1476
+ expectChar(']');
978
1477
 
979
- if (attr) { return "[" + attr + "]" }
1478
+ return new(tree.Attribute)(key, op, val);
980
1479
  },
981
1480
 
982
1481
  //
@@ -985,57 +1484,119 @@ less.Parser = function Parser(env) {
985
1484
  //
986
1485
  block: function () {
987
1486
  var content;
988
-
989
- if ($('{') && (content = $(this.primary)) && $('}')) {
1487
+ if ($char('{') && (content = this.primary()) && $char('}')) {
990
1488
  return content;
991
1489
  }
992
1490
  },
993
1491
 
1492
+ blockRuleset: function() {
1493
+ var block = this.block();
1494
+
1495
+ if (block) {
1496
+ block = new tree.Ruleset(null, block);
1497
+ }
1498
+ return block;
1499
+ },
1500
+
1501
+ detachedRuleset: function() {
1502
+ var blockRuleset = this.blockRuleset();
1503
+ if (blockRuleset) {
1504
+ return new tree.DetachedRuleset(blockRuleset);
1505
+ }
1506
+ },
1507
+
994
1508
  //
995
1509
  // div, .class, body > p {...}
996
1510
  //
997
1511
  ruleset: function () {
998
- var selectors = [], s, rules, match;
1512
+ var selectors, s, rules, debugInfo;
1513
+
999
1514
  save();
1000
1515
 
1001
- while (s = $(this.selector)) {
1002
- selectors.push(s);
1003
- $(this.comment);
1004
- if (! $(',')) { break }
1005
- $(this.comment);
1516
+ if (env.dumpLineNumbers) {
1517
+ debugInfo = getDebugInfo(i, input, env);
1518
+ }
1519
+
1520
+ while (true) {
1521
+ s = this.lessSelector();
1522
+ if (!s) {
1523
+ break;
1524
+ }
1525
+ if (selectors) { selectors.push(s); } else { selectors = [ s ]; }
1526
+ this.comments();
1527
+ if (s.condition && selectors.length > 1) {
1528
+ error("Guards are only currently allowed on a single selector.");
1529
+ }
1530
+ if (! $char(',')) { break; }
1531
+ if (s.condition) {
1532
+ error("Guards are only currently allowed on a single selector.");
1533
+ }
1534
+ this.comments();
1006
1535
  }
1007
1536
 
1008
- if (selectors.length > 0 && (rules = $(this.block))) {
1009
- return new(tree.Ruleset)(selectors, rules, env.strictImports);
1537
+ if (selectors && (rules = this.block())) {
1538
+ forget();
1539
+ var ruleset = new(tree.Ruleset)(selectors, rules, env.strictImports);
1540
+ if (env.dumpLineNumbers) {
1541
+ ruleset.debugInfo = debugInfo;
1542
+ }
1543
+ return ruleset;
1010
1544
  } else {
1011
1545
  // Backtrack
1012
1546
  furthest = i;
1013
1547
  restore();
1014
1548
  }
1015
1549
  },
1016
- rule: function () {
1017
- var name, value, c = input.charAt(i), important, match;
1018
- save();
1550
+ rule: function (tryAnonymous) {
1551
+ var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable;
1019
1552
 
1020
- if (c === '.' || c === '#' || c === '&') { return }
1553
+ if (c === '.' || c === '#' || c === '&') { return; }
1021
1554
 
1022
- if (name = $(this.variable) || $(this.property)) {
1023
- if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) {
1024
- i += match[0].length - 1;
1025
- value = new(tree.Anonymous)(match[1]);
1026
- } else if (name === "font") {
1027
- value = $(this.font);
1028
- } else {
1029
- value = $(this.value);
1555
+ save();
1556
+
1557
+ name = this.variable() || this.ruleProperty();
1558
+ if (name) {
1559
+ isVariable = typeof name === "string";
1560
+
1561
+ if (isVariable) {
1562
+ value = this.detachedRuleset();
1563
+ }
1564
+
1565
+ if (!value) {
1566
+ // prefer to try to parse first if its a variable or we are compressing
1567
+ // but always fallback on the other one
1568
+ value = !tryAnonymous && (env.compress || isVariable) ?
1569
+ (this.value() || this.anonymousValue()) :
1570
+ (this.anonymousValue() || this.value());
1571
+
1572
+ important = this.important();
1573
+
1574
+ // a name returned by this.ruleProperty() is always an array of the form:
1575
+ // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
1576
+ // where each item is a tree.Keyword or tree.Variable
1577
+ merge = !isVariable && name.pop().value;
1030
1578
  }
1031
- important = $(this.important);
1032
1579
 
1033
- if (value && $(this.end)) {
1034
- return new(tree.Rule)(name, value, important, memo);
1580
+ if (value && this.end()) {
1581
+ forget();
1582
+ return new (tree.Rule)(name, value, important, merge, startOfRule, env.currentFileInfo);
1035
1583
  } else {
1036
1584
  furthest = i;
1037
1585
  restore();
1586
+ if (value && !tryAnonymous) {
1587
+ return this.rule(true);
1588
+ }
1038
1589
  }
1590
+ } else {
1591
+ forget();
1592
+ }
1593
+ },
1594
+ anonymousValue: function () {
1595
+ var match;
1596
+ match = /^([^@+\/'"*`(;{}-]*);/.exec(current);
1597
+ if (match) {
1598
+ i += match[0].length - 1;
1599
+ return new(tree.Anonymous)(match[1]);
1039
1600
  }
1040
1601
  },
1041
1602
 
@@ -1051,33 +1612,78 @@ less.Parser = function Parser(env) {
1051
1612
  //
1052
1613
  "import": function () {
1053
1614
  var path, features, index = i;
1054
- if ($(/^@import\s+/) &&
1055
- (path = $(this.entities.quoted) || $(this.entities.url))) {
1056
- features = $(this.mediaFeatures);
1057
- if ($(';')) {
1058
- return new(tree.Import)(path, imports, features, index);
1615
+
1616
+ save();
1617
+
1618
+ var dir = $re(/^@import?\s+/);
1619
+
1620
+ var options = (dir ? this.importOptions() : null) || {};
1621
+
1622
+ if (dir && (path = this.entities.quoted() || this.entities.url())) {
1623
+ features = this.mediaFeatures();
1624
+ if ($char(';')) {
1625
+ forget();
1626
+ features = features && new(tree.Value)(features);
1627
+ return new(tree.Import)(path, features, options, index, env.currentFileInfo);
1059
1628
  }
1060
1629
  }
1630
+
1631
+ restore();
1061
1632
  },
1062
1633
 
1063
- mediaFeature: function () {
1064
- var e, p, nodes = [];
1634
+ importOptions: function() {
1635
+ var o, options = {}, optionName, value;
1636
+
1637
+ // list of options, surrounded by parens
1638
+ if (! $char('(')) { return null; }
1639
+ do {
1640
+ o = this.importOption();
1641
+ if (o) {
1642
+ optionName = o;
1643
+ value = true;
1644
+ switch(optionName) {
1645
+ case "css":
1646
+ optionName = "less";
1647
+ value = false;
1648
+ break;
1649
+ case "once":
1650
+ optionName = "multiple";
1651
+ value = false;
1652
+ break;
1653
+ }
1654
+ options[optionName] = value;
1655
+ if (! $char(',')) { break; }
1656
+ }
1657
+ } while (o);
1658
+ expectChar(')');
1659
+ return options;
1660
+ },
1661
+
1662
+ importOption: function() {
1663
+ var opt = $re(/^(less|css|multiple|once|inline|reference)/);
1664
+ if (opt) {
1665
+ return opt[1];
1666
+ }
1667
+ },
1065
1668
 
1669
+ mediaFeature: function () {
1670
+ var entities = this.entities, nodes = [], e, p;
1066
1671
  do {
1067
- if (e = $(this.entities.keyword)) {
1672
+ e = entities.keyword() || entities.variable();
1673
+ if (e) {
1068
1674
  nodes.push(e);
1069
- } else if ($('(')) {
1070
- p = $(this.property);
1071
- e = $(this.entity);
1072
- if ($(')')) {
1675
+ } else if ($char('(')) {
1676
+ p = this.property();
1677
+ e = this.value();
1678
+ if ($char(')')) {
1073
1679
  if (p && e) {
1074
- nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, i, true)));
1680
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true)));
1075
1681
  } else if (e) {
1076
1682
  nodes.push(new(tree.Paren)(e));
1077
1683
  } else {
1078
1684
  return null;
1079
1685
  }
1080
- } else { return null }
1686
+ } else { return null; }
1081
1687
  }
1082
1688
  } while (e);
1083
1689
 
@@ -1087,29 +1693,41 @@ less.Parser = function Parser(env) {
1087
1693
  },
1088
1694
 
1089
1695
  mediaFeatures: function () {
1090
- var e, features = [];
1091
-
1696
+ var entities = this.entities, features = [], e;
1092
1697
  do {
1093
- if (e = $(this.mediaFeature)) {
1094
- features.push(e);
1095
- if (! $(',')) { break }
1096
- } else if (e = $(this.entities.variable)) {
1097
- features.push(e);
1098
- if (! $(',')) { break }
1099
- }
1698
+ e = this.mediaFeature();
1699
+ if (e) {
1700
+ features.push(e);
1701
+ if (! $char(',')) { break; }
1702
+ } else {
1703
+ e = entities.variable();
1704
+ if (e) {
1705
+ features.push(e);
1706
+ if (! $char(',')) { break; }
1707
+ }
1708
+ }
1100
1709
  } while (e);
1101
-
1710
+
1102
1711
  return features.length > 0 ? features : null;
1103
1712
  },
1104
1713
 
1105
1714
  media: function () {
1106
- var features, rules;
1715
+ var features, rules, media, debugInfo;
1107
1716
 
1108
- if ($(/^@media/)) {
1109
- features = $(this.mediaFeatures);
1717
+ if (env.dumpLineNumbers) {
1718
+ debugInfo = getDebugInfo(i, input, env);
1719
+ }
1720
+
1721
+ if ($re(/^@media/)) {
1722
+ features = this.mediaFeatures();
1110
1723
 
1111
- if (rules = $(this.block)) {
1112
- return new(tree.Media)(rules, features);
1724
+ rules = this.block();
1725
+ if (rules) {
1726
+ media = new(tree.Media)(rules, features, i, env.currentFileInfo);
1727
+ if (env.dumpLineNumbers) {
1728
+ media.debugInfo = debugInfo;
1729
+ }
1730
+ return media;
1113
1731
  }
1114
1732
  }
1115
1733
  },
@@ -1120,42 +1738,97 @@ less.Parser = function Parser(env) {
1120
1738
  // @charset "utf-8";
1121
1739
  //
1122
1740
  directive: function () {
1123
- var name, value, rules, types, e, nodes;
1741
+ var index = i, name, value, rules, nonVendorSpecificName,
1742
+ hasIdentifier, hasExpression, hasUnknown, hasBlock = true;
1124
1743
 
1125
- if (input.charAt(i) !== '@') return;
1744
+ if (input.charAt(i) !== '@') { return; }
1126
1745
 
1127
- if (value = $(this['import']) || $(this.media)) {
1746
+ value = this['import']() || this.media();
1747
+ if (value) {
1128
1748
  return value;
1129
- } else if (name = $(/^@page|@keyframes/) || $(/^@(?:-webkit-|-moz-|-o-|-ms-)[a-z0-9-]+/)) {
1130
- types = ($(/^[^{]+/) || '').trim();
1131
- if (rules = $(this.block)) {
1132
- return new(tree.Directive)(name + " " + types, rules);
1133
- }
1134
- } else if (name = $(/^@[-a-z]+/)) {
1135
- if (name === '@font-face') {
1136
- if (rules = $(this.block)) {
1137
- return new(tree.Directive)(name, rules);
1138
- }
1139
- } else if ((value = $(this.entity)) && $(';')) {
1140
- return new(tree.Directive)(name, value);
1141
- }
1142
1749
  }
1143
- },
1144
- font: function () {
1145
- var value = [], expression = [], weight, shorthand, font, e;
1146
1750
 
1147
- while (e = $(this.shorthand) || $(this.entity)) {
1148
- expression.push(e);
1751
+ save();
1752
+
1753
+ name = $re(/^@[a-z-]+/);
1754
+
1755
+ if (!name) { return; }
1756
+
1757
+ nonVendorSpecificName = name;
1758
+ if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
1759
+ nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1);
1760
+ }
1761
+
1762
+ switch(nonVendorSpecificName) {
1763
+ /*
1764
+ case "@font-face":
1765
+ case "@viewport":
1766
+ case "@top-left":
1767
+ case "@top-left-corner":
1768
+ case "@top-center":
1769
+ case "@top-right":
1770
+ case "@top-right-corner":
1771
+ case "@bottom-left":
1772
+ case "@bottom-left-corner":
1773
+ case "@bottom-center":
1774
+ case "@bottom-right":
1775
+ case "@bottom-right-corner":
1776
+ case "@left-top":
1777
+ case "@left-middle":
1778
+ case "@left-bottom":
1779
+ case "@right-top":
1780
+ case "@right-middle":
1781
+ case "@right-bottom":
1782
+ hasBlock = true;
1783
+ break;
1784
+ */
1785
+ case "@charset":
1786
+ hasIdentifier = true;
1787
+ hasBlock = false;
1788
+ break;
1789
+ case "@namespace":
1790
+ hasExpression = true;
1791
+ hasBlock = false;
1792
+ break;
1793
+ case "@keyframes":
1794
+ hasIdentifier = true;
1795
+ break;
1796
+ case "@host":
1797
+ case "@page":
1798
+ case "@document":
1799
+ case "@supports":
1800
+ hasUnknown = true;
1801
+ break;
1149
1802
  }
1150
- value.push(new(tree.Expression)(expression));
1151
1803
 
1152
- if ($(',')) {
1153
- while (e = $(this.expression)) {
1154
- value.push(e);
1155
- if (! $(',')) { break }
1804
+ if (hasIdentifier) {
1805
+ value = this.entity();
1806
+ if (!value) {
1807
+ error("expected " + name + " identifier");
1156
1808
  }
1809
+ } else if (hasExpression) {
1810
+ value = this.expression();
1811
+ if (!value) {
1812
+ error("expected " + name + " expression");
1813
+ }
1814
+ } else if (hasUnknown) {
1815
+ value = ($re(/^[^{;]+/) || '').trim();
1816
+ if (value) {
1817
+ value = new(tree.Anonymous)(value);
1818
+ }
1819
+ }
1820
+
1821
+ if (hasBlock) {
1822
+ rules = this.blockRuleset();
1823
+ }
1824
+
1825
+ if (rules || (!hasBlock && value && $char(';'))) {
1826
+ forget();
1827
+ return new(tree.Directive)(name, value, rules, index, env.currentFileInfo,
1828
+ env.dumpLineNumbers ? getDebugInfo(index, input, env) : null);
1157
1829
  }
1158
- return new(tree.Value)(value);
1830
+
1831
+ restore();
1159
1832
  },
1160
1833
 
1161
1834
  //
@@ -1167,12 +1840,15 @@ less.Parser = function Parser(env) {
1167
1840
  // and before the `;`.
1168
1841
  //
1169
1842
  value: function () {
1170
- var e, expressions = [], important;
1843
+ var e, expressions = [];
1171
1844
 
1172
- while (e = $(this.expression)) {
1173
- expressions.push(e);
1174
- if (! $(',')) { break }
1175
- }
1845
+ do {
1846
+ e = this.expression();
1847
+ if (e) {
1848
+ expressions.push(e);
1849
+ if (! $char(',')) { break; }
1850
+ }
1851
+ } while(e);
1176
1852
 
1177
1853
  if (expressions.length > 0) {
1178
1854
  return new(tree.Value)(expressions);
@@ -1180,31 +1856,66 @@ less.Parser = function Parser(env) {
1180
1856
  },
1181
1857
  important: function () {
1182
1858
  if (input.charAt(i) === '!') {
1183
- return $(/^! *important/);
1859
+ return $re(/^! *important/);
1184
1860
  }
1185
1861
  },
1186
1862
  sub: function () {
1187
- var e;
1188
-
1189
- if ($('(') && (e = $(this.expression)) && $(')')) {
1190
- return e;
1863
+ var a, e;
1864
+
1865
+ if ($char('(')) {
1866
+ a = this.addition();
1867
+ if (a) {
1868
+ e = new(tree.Expression)([a]);
1869
+ expectChar(')');
1870
+ e.parens = true;
1871
+ return e;
1872
+ }
1191
1873
  }
1192
1874
  },
1193
1875
  multiplication: function () {
1194
- var m, a, op, operation;
1195
- if (m = $(this.operand)) {
1196
- while (!peek(/^\/\*/) && (op = ($('/') || $('*'))) && (a = $(this.operand))) {
1197
- operation = new(tree.Operation)(op, [operation || m, a]);
1876
+ var m, a, op, operation, isSpaced;
1877
+ m = this.operand();
1878
+ if (m) {
1879
+ isSpaced = isWhitespace(input, i - 1);
1880
+ while (true) {
1881
+ if (peek(/^\/[*\/]/)) {
1882
+ break;
1883
+ }
1884
+ op = $char('/') || $char('*');
1885
+
1886
+ if (!op) { break; }
1887
+
1888
+ a = this.operand();
1889
+
1890
+ if (!a) { break; }
1891
+
1892
+ m.parensInOp = true;
1893
+ a.parensInOp = true;
1894
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1895
+ isSpaced = isWhitespace(input, i - 1);
1198
1896
  }
1199
1897
  return operation || m;
1200
1898
  }
1201
1899
  },
1202
1900
  addition: function () {
1203
- var m, a, op, operation;
1204
- if (m = $(this.multiplication)) {
1205
- while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) &&
1206
- (a = $(this.multiplication))) {
1207
- operation = new(tree.Operation)(op, [operation || m, a]);
1901
+ var m, a, op, operation, isSpaced;
1902
+ m = this.multiplication();
1903
+ if (m) {
1904
+ isSpaced = isWhitespace(input, i - 1);
1905
+ while (true) {
1906
+ op = $re(/^[-+]\s+/) || (!isSpaced && ($char('+') || $char('-')));
1907
+ if (!op) {
1908
+ break;
1909
+ }
1910
+ a = this.multiplication();
1911
+ if (!a) {
1912
+ break;
1913
+ }
1914
+
1915
+ m.parensInOp = true;
1916
+ a.parensInOp = true;
1917
+ operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
1918
+ isSpaced = isWhitespace(input, i - 1);
1208
1919
  }
1209
1920
  return operation || m;
1210
1921
  }
@@ -1212,21 +1923,33 @@ less.Parser = function Parser(env) {
1212
1923
  conditions: function () {
1213
1924
  var a, b, index = i, condition;
1214
1925
 
1215
- if (a = $(this.condition)) {
1216
- while ($(',') && (b = $(this.condition))) {
1926
+ a = this.condition();
1927
+ if (a) {
1928
+ while (true) {
1929
+ if (!peek(/^,\s*(not\s*)?\(/) || !$char(',')) {
1930
+ break;
1931
+ }
1932
+ b = this.condition();
1933
+ if (!b) {
1934
+ break;
1935
+ }
1217
1936
  condition = new(tree.Condition)('or', condition || a, b, index);
1218
1937
  }
1219
1938
  return condition || a;
1220
1939
  }
1221
1940
  },
1222
1941
  condition: function () {
1223
- var a, b, c, op, index = i, negate = false;
1224
-
1225
- if ($(/^not/)) { negate = true }
1226
- expect('(');
1227
- if (a = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1228
- if (op = $(/^(?:>=|=<|[<=>])/)) {
1229
- if (b = $(this.addition) || $(this.entities.keyword) || $(this.entities.quoted)) {
1942
+ var entities = this.entities, index = i, negate = false,
1943
+ a, b, c, op;
1944
+
1945
+ if ($re(/^not/)) { negate = true; }
1946
+ expectChar('(');
1947
+ a = this.addition() || entities.keyword() || entities.quoted();
1948
+ if (a) {
1949
+ op = $re(/^(?:>=|<=|=<|[<=>])/);
1950
+ if (op) {
1951
+ b = this.addition() || entities.keyword() || entities.quoted();
1952
+ if (b) {
1230
1953
  c = new(tree.Condition)(op, a, b, index, negate);
1231
1954
  } else {
1232
1955
  error('expected expression');
@@ -1234,8 +1957,8 @@ less.Parser = function Parser(env) {
1234
1957
  } else {
1235
1958
  c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1236
1959
  }
1237
- expect(')');
1238
- return $(/^and/) ? new(tree.Condition)('and', c, $(this.condition)) : c;
1960
+ expectChar(')');
1961
+ return $re(/^and/) ? new(tree.Condition)('and', c, this.condition()) : c;
1239
1962
  }
1240
1963
  },
1241
1964
 
@@ -1244,14 +1967,20 @@ less.Parser = function Parser(env) {
1244
1967
  // such as a Color, or a Variable
1245
1968
  //
1246
1969
  operand: function () {
1247
- var negate, p = input.charAt(i + 1);
1248
-
1249
- if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') }
1250
- var o = $(this.sub) || $(this.entities.dimension) ||
1251
- $(this.entities.color) || $(this.entities.variable) ||
1252
- $(this.entities.call);
1253
- return negate ? new(tree.Operation)('*', [new(tree.Dimension)(-1), o])
1254
- : o;
1970
+ var entities = this.entities,
1971
+ p = input.charAt(i + 1), negate;
1972
+
1973
+ if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $char('-'); }
1974
+ var o = this.sub() || entities.dimension() ||
1975
+ entities.color() || entities.variable() ||
1976
+ entities.call();
1977
+
1978
+ if (negate) {
1979
+ o.parensInOp = true;
1980
+ o = new(tree.Negative)(o);
1981
+ }
1982
+
1983
+ return o;
1255
1984
  },
1256
1985
 
1257
1986
  //
@@ -1262,44 +1991,78 @@ less.Parser = function Parser(env) {
1262
1991
  // @var * 2
1263
1992
  //
1264
1993
  expression: function () {
1265
- var e, delim, entities = [], d;
1994
+ var entities = [], e, delim;
1266
1995
 
1267
- while (e = $(this.addition) || $(this.entity)) {
1268
- entities.push(e);
1269
- }
1996
+ do {
1997
+ e = this.addition() || this.entity();
1998
+ if (e) {
1999
+ entities.push(e);
2000
+ // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
2001
+ if (!peek(/^\/[\/*]/)) {
2002
+ delim = $char('/');
2003
+ if (delim) {
2004
+ entities.push(new(tree.Anonymous)(delim));
2005
+ }
2006
+ }
2007
+ }
2008
+ } while (e);
1270
2009
  if (entities.length > 0) {
1271
2010
  return new(tree.Expression)(entities);
1272
2011
  }
1273
2012
  },
1274
2013
  property: function () {
1275
- var name;
1276
-
1277
- if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) {
2014
+ var name = $re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);
2015
+ if (name) {
1278
2016
  return name[1];
1279
2017
  }
2018
+ },
2019
+ ruleProperty: function () {
2020
+ var c = current, name = [], index = [], length = 0, s, k;
2021
+
2022
+ function match(re) {
2023
+ var a = re.exec(c);
2024
+ if (a) {
2025
+ index.push(i + length);
2026
+ length += a[0].length;
2027
+ c = c.slice(a[1].length);
2028
+ return name.push(a[1]);
2029
+ }
2030
+ }
2031
+
2032
+ match(/^(\*?)/);
2033
+ while (match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)); // !
2034
+ if ((name.length > 1) && match(/^\s*((?:\+_|\+)?)\s*:/)) {
2035
+ // at last, we have the complete match now. move forward,
2036
+ // convert name particles to tree objects and return:
2037
+ skipWhitespace(length);
2038
+ if (name[0] === '') {
2039
+ name.shift();
2040
+ index.shift();
2041
+ }
2042
+ for (k = 0; k < name.length; k++) {
2043
+ s = name[k];
2044
+ name[k] = (s.charAt(0) !== '@')
2045
+ ? new(tree.Keyword)(s)
2046
+ : new(tree.Variable)('@' + s.slice(2, -1),
2047
+ index[k], env.currentFileInfo);
2048
+ }
2049
+ return name;
2050
+ }
1280
2051
  }
1281
2052
  }
1282
2053
  };
2054
+ return parser;
1283
2055
  };
1284
-
1285
- if (less.mode === 'browser' || less.mode === 'rhino') {
1286
- //
1287
- // Used by `@import` directives
1288
- //
1289
- less.Parser.importer = function (path, paths, callback, env) {
1290
- if (!/^([a-z]+:)?\//.test(path) && paths.length > 0) {
1291
- path = paths[0] + path;
2056
+ less.Parser.serializeVars = function(vars) {
2057
+ var s = '';
2058
+
2059
+ for (var name in vars) {
2060
+ if (Object.hasOwnProperty.call(vars, name)) {
2061
+ var value = vars[name];
2062
+ s += ((name[0] === '@') ? '' : '@') + name +': '+ value +
2063
+ ((('' + value).slice(-1) === ';') ? '' : ';');
1292
2064
  }
1293
- // We pass `true` as 3rd argument, to force the reload of the import.
1294
- // This is so we can get the syntax tree as opposed to just the CSS output,
1295
- // as we need this to evaluate the current stylesheet.
1296
- loadStyleSheet({ href: path, title: path, type: env.mime }, function (e) {
1297
- if (e && typeof(env.errback) === "function") {
1298
- env.errback.call(null, path, paths, callback, env);
1299
- } else {
1300
- callback.apply(null, arguments);
1301
- }
1302
- }, true);
1303
- };
1304
- }
2065
+ }
1305
2066
 
2067
+ return s;
2068
+ };