less 2.0.0 → 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 (468) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.gitmodules +1 -4
  4. data/.travis.yml +12 -0
  5. data/Changelog.md +17 -0
  6. data/Gemfile +10 -2
  7. data/README.md +30 -14
  8. data/Rakefile +2 -0
  9. data/bin/lessc +101 -1
  10. data/less.gemspec +5 -8
  11. data/lib/less/defaults.rb +13 -0
  12. data/lib/less/errors.rb +44 -0
  13. data/lib/less/java_script/rhino_context.rb +100 -0
  14. data/lib/less/java_script/v8_context.rb +105 -0
  15. data/lib/less/java_script.rb +34 -0
  16. data/lib/less/js/.gitattributes +10 -0
  17. data/lib/less/js/.gitignore +24 -0
  18. data/lib/less/js/.jshintrc +11 -0
  19. data/lib/less/js/.npmignore +1 -0
  20. data/lib/less/js/.travis.yml +8 -0
  21. data/lib/less/js/CHANGELOG.md +299 -0
  22. data/lib/less/js/CONTRIBUTING.md +50 -0
  23. data/lib/less/js/Gruntfile.js +298 -0
  24. data/lib/less/js/README.md +43 -12
  25. data/lib/less/js/benchmark/benchmark.less +205 -205
  26. data/lib/less/js/benchmark/less-benchmark.js +11 -14
  27. data/lib/less/js/bin/lessc +350 -43
  28. data/lib/less/js/bower.json +18 -0
  29. data/lib/less/js/build/README.md +51 -0
  30. data/lib/less/js/build/amd.js +6 -0
  31. data/lib/less/js/build/browser-header.js +4 -0
  32. data/lib/less/js/build/build.yml +162 -0
  33. data/lib/less/js/build/require-rhino.js +12 -0
  34. data/lib/less/js/build/rhino-header.js +4 -0
  35. data/lib/less/js/build/rhino-modules.js +131 -0
  36. data/lib/less/js/build/tasks/.gitkeep +1 -0
  37. data/lib/less/js/build.gradle +347 -0
  38. data/lib/less/js/dist/{less-1.0.44.js → less-1.1.4.js} +159 -45
  39. data/lib/less/js/dist/less-1.1.4.min.js +16 -0
  40. data/lib/less/js/dist/less-1.1.5.js +2805 -0
  41. data/lib/less/js/dist/less-1.1.5.min.js +9 -0
  42. data/lib/less/js/dist/less-1.1.6.js +3004 -0
  43. data/lib/less/js/dist/less-1.1.6.min.js +9 -0
  44. data/lib/less/js/dist/less-1.2.0.js +3293 -0
  45. data/lib/less/js/dist/less-1.2.0.min.js +9 -0
  46. data/lib/less/js/dist/less-1.2.1.js +3318 -0
  47. data/lib/less/js/dist/less-1.2.1.min.js +9 -0
  48. data/lib/less/js/dist/less-1.2.2.js +3337 -0
  49. data/lib/less/js/dist/less-1.2.2.min.js +9 -0
  50. data/lib/less/js/dist/less-1.3.0.js +3478 -0
  51. data/lib/less/js/dist/less-1.3.0.min.js +9 -0
  52. data/lib/less/js/dist/less-1.3.1.js +4011 -0
  53. data/lib/less/js/dist/less-1.3.1.min.js +9 -0
  54. data/lib/less/js/dist/less-1.3.2.js +4401 -0
  55. data/lib/less/js/dist/less-1.3.2.min.js +9 -0
  56. data/lib/less/js/dist/less-1.3.3.js +4413 -0
  57. data/lib/less/js/dist/less-1.3.3.min.js +9 -0
  58. data/lib/less/js/dist/less-1.4.0-beta.js +5830 -0
  59. data/lib/less/js/dist/less-1.4.0-beta.min.js +11 -0
  60. data/lib/less/js/dist/less-1.4.0.js +5830 -0
  61. data/lib/less/js/dist/less-1.4.0.min.js +11 -0
  62. data/lib/less/js/dist/less-1.4.1.js +5837 -0
  63. data/lib/less/js/dist/less-1.4.1.min.js +11 -0
  64. data/lib/less/js/dist/less-1.4.2.js +5837 -0
  65. data/lib/less/js/dist/less-1.4.2.min.js +11 -0
  66. data/lib/less/js/dist/less-1.5.0.js +6914 -0
  67. data/lib/less/js/dist/less-1.5.0.min.js +13 -0
  68. data/lib/less/js/dist/less-1.5.1.js +6941 -0
  69. data/lib/less/js/dist/less-1.5.1.min.js +13 -0
  70. data/lib/less/js/dist/less-1.6.0.js +7485 -0
  71. data/lib/less/js/dist/less-1.6.0.min.js +16 -0
  72. data/lib/less/js/dist/less-1.6.1.js +7513 -0
  73. data/lib/less/js/dist/less-1.6.1.min.js +16 -0
  74. data/lib/less/js/dist/less-1.6.2.js +7624 -0
  75. data/lib/less/js/dist/less-1.6.2.min.js +16 -0
  76. data/lib/less/js/dist/less-1.6.3.js +7627 -0
  77. data/lib/less/js/dist/less-1.6.3.min.js +16 -0
  78. data/lib/less/js/dist/less-1.7.0.js +7921 -0
  79. data/lib/less/js/dist/less-1.7.0.min.js +16 -0
  80. data/lib/less/js/dist/less-rhino-1.1.3.js +2460 -0
  81. data/lib/less/js/dist/less-rhino-1.1.5.js +2481 -0
  82. data/lib/less/js/dist/less-rhino-1.3.1.js +3725 -0
  83. data/lib/less/js/dist/less-rhino-1.3.2.js +3990 -0
  84. data/lib/less/js/dist/less-rhino-1.3.3.js +4002 -0
  85. data/lib/less/js/dist/less-rhino-1.4.0.js +4273 -0
  86. data/lib/less/js/dist/less-rhino-1.5.1.js +6831 -0
  87. data/lib/less/js/dist/less-rhino-1.6.2.js +9017 -0
  88. data/lib/less/js/dist/less-rhino-1.6.3.js +9020 -0
  89. data/lib/less/js/dist/less-rhino-1.7.0.js +9301 -0
  90. data/lib/less/js/dist/lessc-rhino-1.6.2.js +449 -0
  91. data/lib/less/js/dist/lessc-rhino-1.6.3.js +449 -0
  92. data/lib/less/js/dist/lessc-rhino-1.7.0.js +449 -0
  93. data/lib/less/js/gradle/wrapper/gradle-wrapper.jar +0 -0
  94. data/lib/less/js/gradle/wrapper/gradle-wrapper.properties +6 -0
  95. data/lib/less/js/gradlew +164 -0
  96. data/lib/less/js/gradlew.bat +90 -0
  97. data/lib/less/js/lib/less/browser.js +556 -248
  98. data/lib/less/js/lib/less/colors.js +151 -0
  99. data/lib/less/js/lib/less/encoder.js +4 -0
  100. data/lib/less/js/lib/less/env.js +136 -0
  101. data/lib/less/js/lib/less/extend-visitor.js +416 -0
  102. data/lib/less/js/lib/less/functions.js +622 -28
  103. data/lib/less/js/lib/less/import-visitor.js +142 -0
  104. data/lib/less/js/lib/less/index.js +198 -83
  105. data/lib/less/js/lib/less/join-selector-visitor.js +44 -0
  106. data/lib/less/js/lib/less/lessc_helper.js +82 -0
  107. data/lib/less/js/lib/less/parser.js +1419 -449
  108. data/lib/less/js/lib/less/rhino.js +448 -0
  109. data/lib/less/js/lib/less/source-map-output.js +141 -0
  110. data/lib/less/js/lib/less/to-css-visitor.js +240 -0
  111. data/lib/less/js/lib/less/tree/alpha.js +18 -6
  112. data/lib/less/js/lib/less/tree/anonymous.js +26 -5
  113. data/lib/less/js/lib/less/tree/assignment.js +29 -0
  114. data/lib/less/js/lib/less/tree/call.js +40 -14
  115. data/lib/less/js/lib/less/tree/color.js +122 -31
  116. data/lib/less/js/lib/less/tree/comment.js +19 -5
  117. data/lib/less/js/lib/less/tree/condition.js +49 -0
  118. data/lib/less/js/lib/less/tree/detached-ruleset.js +20 -0
  119. data/lib/less/js/lib/less/tree/dimension.js +304 -14
  120. data/lib/less/js/lib/less/tree/directive.js +57 -21
  121. data/lib/less/js/lib/less/tree/element.js +89 -13
  122. data/lib/less/js/lib/less/tree/expression.js +40 -9
  123. data/lib/less/js/lib/less/tree/extend.js +53 -0
  124. data/lib/less/js/lib/less/tree/import.js +84 -37
  125. data/lib/less/js/lib/less/tree/javascript.js +18 -11
  126. data/lib/less/js/lib/less/tree/keyword.js +19 -4
  127. data/lib/less/js/lib/less/tree/media.js +157 -0
  128. data/lib/less/js/lib/less/tree/mixin.js +261 -52
  129. data/lib/less/js/lib/less/tree/negative.js +24 -0
  130. data/lib/less/js/lib/less/tree/operation.js +39 -14
  131. data/lib/less/js/lib/less/tree/paren.js +23 -0
  132. data/lib/less/js/lib/less/tree/quoted.js +30 -11
  133. data/lib/less/js/lib/less/tree/rule.js +79 -27
  134. data/lib/less/js/lib/less/tree/ruleset-call.js +16 -0
  135. data/lib/less/js/lib/less/tree/ruleset.js +469 -94
  136. data/lib/less/js/lib/less/tree/selector.js +119 -18
  137. data/lib/less/js/lib/less/tree/unicode-descriptor.js +15 -0
  138. data/lib/less/js/lib/less/tree/url.js +44 -16
  139. data/lib/less/js/lib/less/tree/value.js +17 -7
  140. data/lib/less/js/lib/less/tree/variable.js +29 -9
  141. data/lib/less/js/lib/less/tree.js +88 -4
  142. data/lib/less/js/lib/less/visitor.js +146 -0
  143. data/lib/less/js/lib/source-map/source-map-0.1.31.js +1933 -0
  144. data/lib/less/js/lib/source-map/source-map-footer.js +4 -0
  145. data/lib/less/js/lib/source-map/source-map-header.js +3 -0
  146. data/lib/less/js/package.json +86 -11
  147. data/lib/less/js/test/browser/common.js +201 -0
  148. data/lib/less/js/test/browser/css/global-vars/simple.css +3 -0
  149. data/lib/less/js/test/browser/css/modify-vars/simple.css +8 -0
  150. data/lib/less/js/test/browser/css/postProcessor/postProcessor.css +4 -0
  151. data/lib/less/js/test/browser/css/relative-urls/urls.css +35 -0
  152. data/lib/less/js/test/browser/css/rootpath/urls.css +35 -0
  153. data/lib/less/js/test/browser/css/rootpath-relative/urls.css +35 -0
  154. data/lib/less/js/test/browser/css/urls.css +53 -0
  155. data/lib/less/js/test/browser/es5.js +27 -0
  156. data/lib/less/js/test/browser/jasmine-html.js +681 -0
  157. data/lib/less/js/test/browser/jasmine.css +82 -0
  158. data/lib/less/js/test/browser/jasmine.js +2600 -0
  159. data/lib/less/js/test/browser/less/console-errors/test-error.less +3 -0
  160. data/lib/less/js/test/browser/less/console-errors/test-error.txt +2 -0
  161. data/lib/less/js/test/browser/less/global-vars/simple.less +3 -0
  162. data/lib/less/js/test/browser/less/imports/urls.less +4 -0
  163. data/lib/less/js/test/browser/less/imports/urls2.less +4 -0
  164. data/lib/less/js/test/browser/less/modify-vars/imports/simple2.less +4 -0
  165. data/lib/less/js/test/browser/less/modify-vars/simple.less +8 -0
  166. data/lib/less/js/test/browser/less/postProcessor/postProcessor.less +4 -0
  167. data/lib/less/js/test/browser/less/relative-urls/urls.less +33 -0
  168. data/lib/less/js/test/browser/less/rootpath/urls.less +33 -0
  169. data/lib/less/js/test/browser/less/rootpath-relative/urls.less +33 -0
  170. data/lib/less/js/test/browser/less/urls.less +57 -0
  171. data/lib/less/js/test/browser/phantom-runner.js +150 -0
  172. data/lib/less/js/test/browser/runner-browser-options.js +42 -0
  173. data/lib/less/js/test/browser/runner-browser-spec.js +12 -0
  174. data/lib/less/js/test/browser/runner-console-errors.js +5 -0
  175. data/lib/less/js/test/browser/runner-errors-options.js +5 -0
  176. data/lib/less/js/test/browser/runner-errors-spec.js +4 -0
  177. data/lib/less/js/test/browser/runner-global-vars-options.js +4 -0
  178. data/lib/less/js/test/browser/runner-global-vars-spec.js +3 -0
  179. data/lib/less/js/test/browser/runner-legacy-options.js +4 -0
  180. data/lib/less/js/test/browser/runner-legacy-spec.js +3 -0
  181. data/lib/less/js/test/browser/runner-main-options.js +15 -0
  182. data/lib/less/js/test/browser/runner-main-spec.js +3 -0
  183. data/lib/less/js/test/browser/runner-modify-vars-options.js +2 -0
  184. data/lib/less/js/test/browser/runner-modify-vars-spec.js +43 -0
  185. data/lib/less/js/test/browser/runner-no-js-errors-options.js +4 -0
  186. data/lib/less/js/test/browser/runner-no-js-errors-spec.js +4 -0
  187. data/lib/less/js/test/browser/runner-postProcessor-options.js +4 -0
  188. data/lib/less/js/test/browser/runner-postProcessor.js +3 -0
  189. data/lib/less/js/test/browser/runner-production-options.js +3 -0
  190. data/lib/less/js/test/browser/runner-production-spec.js +5 -0
  191. data/lib/less/js/test/browser/runner-relative-urls-options.js +3 -0
  192. data/lib/less/js/test/browser/runner-relative-urls-spec.js +3 -0
  193. data/lib/less/js/test/browser/runner-rootpath-options.js +3 -0
  194. data/lib/less/js/test/browser/runner-rootpath-relative-options.js +4 -0
  195. data/lib/less/js/test/browser/runner-rootpath-relative-spec.js +3 -0
  196. data/lib/less/js/test/browser/runner-rootpath-spec.js +3 -0
  197. data/lib/less/js/test/browser/test-runner-template.tmpl +47 -0
  198. data/lib/less/js/test/css/charsets.css +1 -0
  199. data/lib/less/js/test/css/colors.css +45 -0
  200. data/lib/less/js/test/css/comments.css +24 -7
  201. data/lib/less/js/test/css/compression/compression.css +3 -0
  202. data/lib/less/js/test/css/css-3.css +91 -4
  203. data/lib/less/js/test/css/css-escapes.css +5 -1
  204. data/lib/less/js/test/css/css-guards.css +37 -0
  205. data/lib/less/js/test/css/css.css +29 -16
  206. data/lib/less/js/test/css/debug/linenumbers-all.css +49 -0
  207. data/lib/less/js/test/css/debug/linenumbers-comments.css +40 -0
  208. data/lib/less/js/test/css/debug/linenumbers-mediaquery.css +40 -0
  209. data/lib/less/js/test/css/detached-rulesets.css +71 -0
  210. data/lib/less/js/test/css/empty.css +0 -0
  211. data/lib/less/js/test/css/extend-chaining.css +81 -0
  212. data/lib/less/js/test/css/extend-clearfix.css +19 -0
  213. data/lib/less/js/test/css/extend-exact.css +37 -0
  214. data/lib/less/js/test/css/extend-media.css +24 -0
  215. data/lib/less/js/test/css/extend-nest.css +57 -0
  216. data/lib/less/js/test/css/extend-selector.css +80 -0
  217. data/lib/less/js/test/css/extend.css +76 -0
  218. data/lib/less/js/test/css/extract-and-length.css +133 -0
  219. data/lib/less/js/test/css/functions.css +128 -0
  220. data/lib/less/js/test/css/globalVars/extended.css +12 -0
  221. data/lib/less/js/test/css/globalVars/simple.css +6 -0
  222. data/lib/less/js/test/css/ie-filters.css +9 -0
  223. data/lib/less/js/test/css/import-inline.css +5 -0
  224. data/lib/less/js/test/css/import-interpolation.css +6 -0
  225. data/lib/less/js/test/css/import-once.css +15 -0
  226. data/lib/less/js/test/css/import-reference.css +68 -0
  227. data/lib/less/js/test/css/import.css +30 -8
  228. data/lib/less/js/test/css/javascript.css +3 -2
  229. data/lib/less/js/test/css/legacy/legacy.css +7 -0
  230. data/lib/less/js/test/css/media.css +200 -2
  231. data/lib/less/js/test/css/merge.css +34 -0
  232. data/lib/less/js/test/css/mixins-args.css +58 -6
  233. data/lib/less/js/test/css/mixins-guards-default-func.css +129 -0
  234. data/lib/less/js/test/css/mixins-guards.css +82 -0
  235. data/lib/less/js/test/css/mixins-important.css +45 -0
  236. data/lib/less/js/test/css/mixins-interpolated.css +39 -0
  237. data/lib/less/js/test/css/mixins-named-args.css +27 -0
  238. data/lib/less/js/test/css/mixins-pattern.css +8 -6
  239. data/lib/less/js/test/css/mixins.css +91 -0
  240. data/lib/less/js/test/css/modifyVars/extended.css +9 -0
  241. data/lib/less/js/test/css/no-output.css +0 -0
  242. data/lib/less/js/test/css/operations.css +3 -0
  243. data/lib/less/js/test/css/parens.css +22 -6
  244. data/lib/less/js/test/css/property-name-interp.css +20 -0
  245. data/lib/less/js/test/css/rulesets.css +6 -2
  246. data/lib/less/js/test/css/scope.css +29 -6
  247. data/lib/less/js/test/css/selectors.css +122 -1
  248. data/lib/less/js/test/css/static-urls/urls.css +45 -0
  249. data/lib/less/js/test/css/strings.css +10 -5
  250. data/lib/less/js/test/css/url-args/urls.css +56 -0
  251. data/lib/less/js/test/css/urls.css +71 -0
  252. data/lib/less/js/test/css/variables-in-at-rules.css +18 -0
  253. data/lib/less/js/test/css/variables.css +23 -2
  254. data/lib/less/js/test/css/whitespace.css +9 -3
  255. data/lib/less/js/test/data/data-uri-fail.png +0 -0
  256. data/lib/less/js/test/data/image.jpg +1 -0
  257. data/lib/less/js/test/data/page.html +1 -0
  258. data/lib/less/js/test/index.js +45 -0
  259. data/lib/less/js/test/less/charsets.less +3 -0
  260. data/lib/less/js/test/less/colors.less +56 -4
  261. data/lib/less/js/test/less/comments.less +28 -8
  262. data/lib/less/js/test/less/compression/compression.less +36 -0
  263. data/lib/less/js/test/less/css-3.less +80 -6
  264. data/lib/less/js/test/less/css-escapes.less +6 -1
  265. data/lib/less/js/test/less/css-guards.less +102 -0
  266. data/lib/less/js/test/less/css.less +25 -13
  267. data/lib/less/js/test/less/debug/import/test.less +25 -0
  268. data/lib/less/js/test/less/debug/linenumbers.less +33 -0
  269. data/lib/less/js/test/less/detached-rulesets.less +103 -0
  270. data/lib/less/js/test/less/empty.less +0 -0
  271. data/lib/less/js/test/less/errors/add-mixed-units.less +3 -0
  272. data/lib/less/js/test/less/errors/add-mixed-units.txt +4 -0
  273. data/lib/less/js/test/less/errors/add-mixed-units2.less +3 -0
  274. data/lib/less/js/test/less/errors/add-mixed-units2.txt +4 -0
  275. data/lib/less/js/test/less/errors/at-rules-undefined-var.less +4 -0
  276. data/lib/less/js/test/less/errors/at-rules-undefined-var.txt +4 -0
  277. data/lib/less/js/test/less/errors/bad-variable-declaration1.less +1 -0
  278. data/lib/less/js/test/less/errors/bad-variable-declaration1.txt +2 -0
  279. data/lib/less/js/test/less/errors/color-func-invalid-color.less +3 -0
  280. data/lib/less/js/test/less/errors/color-func-invalid-color.txt +4 -0
  281. data/lib/less/js/test/less/errors/comment-in-selector.less +1 -0
  282. data/lib/less/js/test/less/errors/comment-in-selector.txt +2 -0
  283. data/lib/less/js/test/less/errors/css-guard-default-func.less +4 -0
  284. data/lib/less/js/test/less/errors/css-guard-default-func.txt +4 -0
  285. data/lib/less/js/test/less/errors/detached-ruleset-1.less +6 -0
  286. data/lib/less/js/test/less/errors/detached-ruleset-1.txt +4 -0
  287. data/lib/less/js/test/less/errors/detached-ruleset-2.less +6 -0
  288. data/lib/less/js/test/less/errors/detached-ruleset-2.txt +4 -0
  289. data/lib/less/js/test/less/errors/detached-ruleset-3.less +4 -0
  290. data/lib/less/js/test/less/errors/detached-ruleset-3.txt +4 -0
  291. data/lib/less/js/test/less/errors/detached-ruleset-4.less +5 -0
  292. data/lib/less/js/test/less/errors/detached-ruleset-4.txt +3 -0
  293. data/lib/less/js/test/less/errors/detached-ruleset-5.less +4 -0
  294. data/lib/less/js/test/less/errors/detached-ruleset-5.txt +3 -0
  295. data/lib/less/js/test/less/errors/detached-ruleset-6.less +5 -0
  296. data/lib/less/js/test/less/errors/detached-ruleset-6.txt +4 -0
  297. data/lib/less/js/test/less/errors/divide-mixed-units.less +3 -0
  298. data/lib/less/js/test/less/errors/divide-mixed-units.txt +4 -0
  299. data/lib/less/js/test/less/errors/extend-no-selector.less +3 -0
  300. data/lib/less/js/test/less/errors/extend-no-selector.txt +3 -0
  301. data/lib/less/js/test/less/errors/extend-not-at-end.less +3 -0
  302. data/lib/less/js/test/less/errors/extend-not-at-end.txt +3 -0
  303. data/lib/less/js/test/less/errors/import-missing.less +6 -0
  304. data/lib/less/js/test/less/errors/import-missing.txt +3 -0
  305. data/lib/less/js/test/less/errors/import-no-semi.less +1 -0
  306. data/lib/less/js/test/less/errors/import-no-semi.txt +2 -0
  307. data/lib/less/js/test/less/errors/import-subfolder1.less +1 -0
  308. data/lib/less/js/test/less/errors/import-subfolder1.txt +3 -0
  309. data/lib/less/js/test/less/errors/import-subfolder2.less +1 -0
  310. data/lib/less/js/test/less/errors/import-subfolder2.txt +4 -0
  311. data/lib/less/js/test/less/errors/imports/import-subfolder1.less +1 -0
  312. data/lib/less/js/test/less/errors/imports/import-subfolder2.less +1 -0
  313. data/lib/less/js/test/less/errors/imports/import-test.less +4 -0
  314. data/lib/less/js/test/less/errors/imports/subfolder/mixin-not-defined.less +1 -0
  315. data/lib/less/js/test/less/errors/imports/subfolder/parse-error-curly-bracket.less +1 -0
  316. data/lib/less/js/test/less/errors/javascript-error.less +3 -0
  317. data/lib/less/js/test/less/errors/javascript-error.txt +4 -0
  318. data/lib/less/js/test/less/errors/javascript-undefined-var.less +3 -0
  319. data/lib/less/js/test/less/errors/javascript-undefined-var.txt +4 -0
  320. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-1.less +6 -0
  321. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-1.txt +4 -0
  322. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-2.less +6 -0
  323. data/lib/less/js/test/less/errors/mixed-mixin-definition-args-2.txt +4 -0
  324. data/lib/less/js/test/less/errors/mixin-not-defined.less +11 -0
  325. data/lib/less/js/test/less/errors/mixin-not-defined.txt +3 -0
  326. data/lib/less/js/test/less/errors/mixin-not-matched.less +6 -0
  327. data/lib/less/js/test/less/errors/mixin-not-matched.txt +3 -0
  328. data/lib/less/js/test/less/errors/mixin-not-matched2.less +6 -0
  329. data/lib/less/js/test/less/errors/mixin-not-matched2.txt +3 -0
  330. data/lib/less/js/test/less/errors/mixin-not-visible-in-scope-1.less +9 -0
  331. data/lib/less/js/test/less/errors/mixin-not-visible-in-scope-1.txt +4 -0
  332. data/lib/less/js/test/less/errors/mixins-guards-default-func-1.less +9 -0
  333. data/lib/less/js/test/less/errors/mixins-guards-default-func-1.txt +4 -0
  334. data/lib/less/js/test/less/errors/mixins-guards-default-func-2.less +9 -0
  335. data/lib/less/js/test/less/errors/mixins-guards-default-func-2.txt +4 -0
  336. data/lib/less/js/test/less/errors/mixins-guards-default-func-3.less +9 -0
  337. data/lib/less/js/test/less/errors/mixins-guards-default-func-3.txt +4 -0
  338. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors.less +4 -0
  339. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors.txt +4 -0
  340. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors2.less +4 -0
  341. data/lib/less/js/test/less/errors/multiple-guards-on-css-selectors2.txt +4 -0
  342. data/lib/less/js/test/less/errors/multiply-mixed-units.less +7 -0
  343. data/lib/less/js/test/less/errors/multiply-mixed-units.txt +4 -0
  344. data/lib/less/js/test/less/errors/parens-error-1.less +3 -0
  345. data/lib/less/js/test/less/errors/parens-error-1.txt +4 -0
  346. data/lib/less/js/test/less/errors/parens-error-2.less +3 -0
  347. data/lib/less/js/test/less/errors/parens-error-2.txt +4 -0
  348. data/lib/less/js/test/less/errors/parens-error-3.less +3 -0
  349. data/lib/less/js/test/less/errors/parens-error-3.txt +4 -0
  350. data/lib/less/js/test/less/errors/parse-error-curly-bracket.less +4 -0
  351. data/lib/less/js/test/less/errors/parse-error-curly-bracket.txt +4 -0
  352. data/lib/less/js/test/less/errors/parse-error-extra-parens.less +5 -0
  353. data/lib/less/js/test/less/errors/parse-error-extra-parens.txt +3 -0
  354. data/lib/less/js/test/less/errors/parse-error-missing-bracket.less +2 -0
  355. data/lib/less/js/test/less/errors/parse-error-missing-bracket.txt +3 -0
  356. data/lib/less/js/test/less/errors/parse-error-missing-parens.less +5 -0
  357. data/lib/less/js/test/less/errors/parse-error-missing-parens.txt +3 -0
  358. data/lib/less/js/test/less/errors/parse-error-with-import.less +13 -0
  359. data/lib/less/js/test/less/errors/parse-error-with-import.txt +4 -0
  360. data/lib/less/js/test/less/errors/percentage-missing-space.less +3 -0
  361. data/lib/less/js/test/less/errors/percentage-missing-space.txt +4 -0
  362. data/lib/less/js/test/less/errors/property-asterisk-only-name.less +3 -0
  363. data/lib/less/js/test/less/errors/property-asterisk-only-name.txt +4 -0
  364. data/lib/less/js/test/less/errors/property-ie5-hack.less +3 -0
  365. data/lib/less/js/test/less/errors/property-ie5-hack.txt +4 -0
  366. data/lib/less/js/test/less/errors/property-in-root.less +4 -0
  367. data/lib/less/js/test/less/errors/property-in-root.txt +4 -0
  368. data/lib/less/js/test/less/errors/property-in-root2.less +1 -0
  369. data/lib/less/js/test/less/errors/property-in-root2.txt +4 -0
  370. data/lib/less/js/test/less/errors/property-in-root3.less +4 -0
  371. data/lib/less/js/test/less/errors/property-in-root3.txt +3 -0
  372. data/lib/less/js/test/less/errors/property-interp-not-defined.less +1 -0
  373. data/lib/less/js/test/less/errors/property-interp-not-defined.txt +2 -0
  374. data/lib/less/js/test/less/errors/recursive-variable.less +1 -0
  375. data/lib/less/js/test/less/errors/recursive-variable.txt +2 -0
  376. data/lib/less/js/test/less/errors/svg-gradient1.less +3 -0
  377. data/lib/less/js/test/less/errors/svg-gradient1.txt +4 -0
  378. data/lib/less/js/test/less/errors/svg-gradient2.less +3 -0
  379. data/lib/less/js/test/less/errors/svg-gradient2.txt +4 -0
  380. data/lib/less/js/test/less/errors/svg-gradient3.less +3 -0
  381. data/lib/less/js/test/less/errors/svg-gradient3.txt +4 -0
  382. data/lib/less/js/test/less/errors/unit-function.less +3 -0
  383. data/lib/less/js/test/less/errors/unit-function.txt +4 -0
  384. data/lib/less/js/test/less/extend-chaining.less +91 -0
  385. data/lib/less/js/test/less/extend-clearfix.less +19 -0
  386. data/lib/less/js/test/less/extend-exact.less +46 -0
  387. data/lib/less/js/test/less/extend-media.less +24 -0
  388. data/lib/less/js/test/less/extend-nest.less +65 -0
  389. data/lib/less/js/test/less/extend-selector.less +99 -0
  390. data/lib/less/js/test/less/extend.less +81 -0
  391. data/lib/less/js/test/less/extract-and-length.less +133 -0
  392. data/lib/less/js/test/less/functions.less +142 -4
  393. data/lib/less/js/test/less/globalVars/extended.json +5 -0
  394. data/lib/less/js/test/less/globalVars/extended.less +10 -0
  395. data/lib/less/js/test/less/globalVars/simple.json +3 -0
  396. data/lib/less/js/test/less/globalVars/simple.less +3 -0
  397. data/lib/less/js/test/less/ie-filters.less +15 -0
  398. data/lib/less/js/test/less/import/deeper/import-once-test-a.less +1 -0
  399. data/lib/less/js/test/less/import/import-and-relative-paths-test.less +17 -0
  400. data/lib/less/js/test/less/import/import-charset-test.less +1 -0
  401. data/lib/less/js/test/less/import/import-interpolation.less +1 -0
  402. data/lib/less/js/test/less/import/import-interpolation2.less +5 -0
  403. data/lib/less/js/test/less/import/import-once-test-c.less +6 -0
  404. data/lib/less/js/test/less/import/import-reference.less +51 -0
  405. data/lib/less/js/test/less/import/import-test-a.less +1 -0
  406. data/lib/less/js/test/less/import/import-test-c.less +0 -1
  407. data/lib/less/js/test/less/import/import-test-e.less +2 -0
  408. data/lib/less/js/test/less/import/import-test-f.less +5 -0
  409. data/lib/less/js/test/less/import/imports/font.less +8 -0
  410. data/lib/less/js/test/less/import/imports/logo.less +5 -0
  411. data/lib/less/js/test/less/import/invalid-css.less +1 -0
  412. data/lib/less/js/test/less/import/urls.less +1 -0
  413. data/lib/less/js/test/less/import-inline.less +2 -0
  414. data/lib/less/js/test/less/import-interpolation.less +8 -0
  415. data/lib/less/js/test/less/import-once.less +6 -0
  416. data/lib/less/js/test/less/import-reference.less +21 -0
  417. data/lib/less/js/test/less/import.less +16 -3
  418. data/lib/less/js/test/less/javascript.less +5 -3
  419. data/lib/less/js/test/less/legacy/legacy.less +7 -0
  420. data/lib/less/js/test/less/media.less +212 -3
  421. data/lib/less/js/test/less/merge.less +78 -0
  422. data/lib/less/js/test/less/mixins-args.less +113 -16
  423. data/lib/less/js/test/less/mixins-guards-default-func.less +195 -0
  424. data/lib/less/js/test/less/mixins-guards.less +159 -0
  425. data/lib/less/js/test/less/mixins-important.less +25 -0
  426. data/lib/less/js/test/less/mixins-interpolated.less +69 -0
  427. data/lib/less/js/test/less/mixins-named-args.less +36 -0
  428. data/lib/less/js/test/less/mixins-nested.less +2 -2
  429. data/lib/less/js/test/less/mixins-pattern.less +7 -1
  430. data/lib/less/js/test/less/mixins.less +94 -0
  431. data/lib/less/js/test/less/modifyVars/extended.json +5 -0
  432. data/lib/less/js/test/less/modifyVars/extended.less +11 -0
  433. data/lib/less/js/test/less/no-js-errors/no-js-errors.less +3 -0
  434. data/lib/less/js/test/less/no-js-errors/no-js-errors.txt +4 -0
  435. data/lib/less/js/test/less/no-output.less +2 -0
  436. data/lib/less/js/test/less/operations.less +30 -26
  437. data/lib/less/js/test/less/parens.less +24 -5
  438. data/lib/less/js/test/less/property-name-interp.less +53 -0
  439. data/lib/less/js/test/less/scope.less +73 -1
  440. data/lib/less/js/test/less/selectors.less +132 -0
  441. data/lib/less/js/test/less/sourcemaps/basic.json +3 -0
  442. data/lib/less/js/test/less/sourcemaps/basic.less +27 -0
  443. data/lib/less/js/test/less/sourcemaps/imported.css +7 -0
  444. data/lib/less/js/test/less/static-urls/urls.less +33 -0
  445. data/lib/less/js/test/less/strings.less +9 -1
  446. data/lib/less/js/test/less/url-args/urls.less +63 -0
  447. data/lib/less/js/test/less/urls.less +72 -0
  448. data/lib/less/js/test/less/variables-in-at-rules.less +20 -0
  449. data/lib/less/js/test/less/variables.less +44 -11
  450. data/lib/less/js/test/less/whitespace.less +7 -0
  451. data/lib/less/js/test/less-test.js +244 -60
  452. data/lib/less/js/test/rhino/test-header.js +15 -0
  453. data/lib/less/js/test/sourcemaps/basic.json +1 -0
  454. data/lib/less/js/test/sourcemaps/index.html +17 -0
  455. data/lib/less/loader.rb +208 -42
  456. data/lib/less/parser.rb +91 -30
  457. data/lib/less/version.rb +2 -2
  458. data/lib/less.rb +23 -8
  459. data/spec/less/faulty/faulty.less +3 -0
  460. data/spec/less/loader_spec.rb +45 -0
  461. data/spec/less/parser_spec.rb +141 -4
  462. data/spec/less/some/some.css +0 -0
  463. data/spec/less/some/some.less +4 -0
  464. data/spec/spec_helper.rb +4 -2
  465. metadata +418 -76
  466. data/lib/less/js/Makefile +0 -56
  467. data/lib/less/js/build/ecma-5.js +0 -120
  468. data/lib/less/js/build/header.js +0 -7
@@ -1,12 +1,10 @@
1
1
  var less, tree;
2
2
 
3
- if (typeof(window) === 'undefined') {
4
- less = exports,
5
- tree = require('less/tree');
6
- } else {
7
- if (typeof(window.less) === 'undefined') { window.less = {} }
8
- less = window.less,
9
- tree = window.less.tree = {};
3
+ // Node.js does not have a header file added which defines less
4
+ if (less === undefined) {
5
+ less = exports;
6
+ tree = require('./tree');
7
+ less.mode = 'node';
10
8
  }
11
9
  //
12
10
  // less.js - parser
@@ -21,7 +19,7 @@ if (typeof(window) === 'undefined') {
21
19
  // - Matching and slicing on a huge input is often cause of slowdowns.
22
20
  // The solution is to chunkify the input into smaller strings.
23
21
  // The chunks are stored in the `chunks` var,
24
- // `j` holds the current chunk index, and `current` holds
22
+ // `j` holds the current chunk index, and `currentPos` holds
25
23
  // the index of the current chunk in relation to `input`.
26
24
  // This gives us an almost 4x speed-up.
27
25
  //
@@ -45,107 +43,212 @@ less.Parser = function Parser(env) {
45
43
  var input, // LeSS input string
46
44
  i, // current index in `input`
47
45
  j, // current chunk
48
- temp, // temporarily holds a chunk's state, for backtracking
49
- memo, // temporarily holds `i`, when backtracking
46
+ saveStack = [], // holds state for backtracking
50
47
  furthest, // furthest index the parser has gone to
51
48
  chunks, // chunkified input
52
- current, // index of current chunk, in `input`
53
- parser;
54
-
55
- var that = this;
56
-
57
- // This function is called after all files
58
- // have been imported through `@import`.
59
- 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
+ }
60
60
 
61
61
  var imports = this.imports = {
62
- paths: env && env.paths || [], // Search paths, when importing
63
- queue: [], // Files which haven't been imported yet
64
- files: {}, // Holds the imported parse trees
65
- mime: env && env.mime, // MIME type of .less files
66
- push: function (path, callback) {
67
- 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;
68
71
  this.queue.push(path);
69
72
 
70
- //
71
- // Import a file asynchronously
72
- //
73
- less.Parser.importer(path, this.paths, function (root) {
74
- that.queue.splice(that.queue.indexOf(path), 1); // Remove the path from the queue
75
- that.files[path] = root; // Store the root
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
79
+
80
+ if (e && !parserImports.error) { parserImports.error = e; }
81
+
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;
76
96
 
77
- callback(root);
97
+ if (currentFileInfo.reference || importOptions.reference) {
98
+ newFileInfo.reference = true;
99
+ }
78
100
 
79
- if (that.queue.length === 0) { finish() } // Call `finish` if we're done importing
80
- }, env);
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
+ }
81
110
  }
82
111
  };
83
112
 
84
- function save() { temp = chunks[j], memo = i, current = i }
85
- 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(); }
86
116
 
87
117
  function sync() {
88
- if (i > current) {
89
- chunks[j] = chunks[j].slice(i - current);
90
- current = i;
118
+ if (i > currentPos) {
119
+ current = current.slice(i - currentPos);
120
+ currentPos = i;
91
121
  }
92
122
  }
123
+ function isWhitespace(str, pos) {
124
+ var code = str.charCodeAt(pos | 0);
125
+ return (code <= 32) && (code === 32 || code === 10 || code === 9);
126
+ }
93
127
  //
94
128
  // Parse from a token, regexp or string, and move forward if match
95
129
  //
96
130
  function $(tok) {
97
- var match, args, length, c, index, endIndex, k, mem;
131
+ var tokType = typeof tok,
132
+ match, length;
98
133
 
134
+ // Either match a single character in the input,
135
+ // or match a regexp in the current chunk (`current`).
99
136
  //
100
- // Non-terminal
101
- //
102
- if (tok instanceof Function) {
103
- return tok.call(parser.parsers);
104
- //
105
- // Terminal
106
- //
107
- // Either match a single character in the input,
108
- // or match a regexp in the current chunk (chunk[j]).
109
- //
110
- } else if (typeof(tok) === 'string') {
111
- match = input.charAt(i) === tok ? tok : null;
112
- length = 1;
113
- sync ();
114
- } else {
115
- sync ();
116
-
117
- if (match = tok.exec(chunks[j])) {
118
- length = match[0].length;
119
- } else {
137
+ if (tokType === "string") {
138
+ if (input.charAt(i) !== tok) {
120
139
  return null;
121
140
  }
141
+ skipWhitespace(1);
142
+ return tok;
122
143
  }
123
144
 
145
+ // regexp
146
+ sync ();
147
+ if (! (match = tok.exec(current))) {
148
+ return null;
149
+ }
150
+
151
+ length = match[0].length;
152
+
124
153
  // The match is confirmed, add the match length to `i`,
125
154
  // and consume any extra white-space characters (' ' || '\n')
126
155
  // which come after that. The reason for this is that LeSS's
127
156
  // grammar is mostly white-space insensitive.
128
157
  //
129
- if (match) {
130
- mem = i += length;
131
- endIndex = i + chunks[j].length - length;
132
-
133
- while (i < endIndex) {
134
- c = input.charCodeAt(i);
135
- if (! (c === 32 || c === 10 || c === 9)) { break }
136
- i++;
137
- }
138
- chunks[j] = chunks[j].slice(length + (i - mem));
139
- current = i;
158
+ skipWhitespace(length);
140
159
 
141
- if (chunks[j].length === 0 && j < chunks.length - 1) { j++ }
160
+ if(typeof(match) === 'string') {
161
+ return match;
162
+ } else {
163
+ return match.length === 1 ? match[0] : match;
164
+ }
165
+ }
142
166
 
143
- if(typeof(match) === 'string') {
144
- return match;
145
- } else {
146
- return match.length === 1 ? match[0] : match;
167
+ // Specialization of $(tok)
168
+ function $re(tok) {
169
+ if (i > currentPos) {
170
+ current = current.slice(i - currentPos);
171
+ currentPos = i;
172
+ }
173
+ var m = tok.exec(current);
174
+ if (!m) {
175
+ return null;
176
+ }
177
+
178
+ skipWhitespace(m[0].length);
179
+ if(typeof m === "string") {
180
+ return m;
181
+ }
182
+
183
+ return m.length === 1 ? m[0] : m;
184
+ }
185
+
186
+ var _$re = $re;
187
+
188
+ // Specialization of $(tok)
189
+ function $char(tok) {
190
+ if (input.charAt(i) !== tok) {
191
+ return null;
192
+ }
193
+ skipWhitespace(1);
194
+ return tok;
195
+ }
196
+
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;
147
209
  }
210
+
211
+ if ((c !== 32) && (c !== 10) && (c !== 9) && (c !== 13)) {
212
+ break;
213
+ }
214
+ }
215
+
216
+ current = current.slice(length + i - mem + curr);
217
+ currentPos = i;
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
148
223
  }
224
+
225
+ return oldi !== i || oldj !== j;
226
+ }
227
+
228
+ function expect(arg, msg) {
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) {
232
+ return result;
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) + "'");
245
+ }
246
+
247
+ function error(msg, type) {
248
+ var e = new Error(msg);
249
+ e.index = i;
250
+ e.type = type || 'Syntax';
251
+ throw e;
149
252
  }
150
253
 
151
254
  // Same as $(), but don't change the state of the parser,
@@ -154,14 +257,82 @@ less.Parser = function Parser(env) {
154
257
  if (typeof(tok) === 'string') {
155
258
  return input.charAt(i) === tok;
156
259
  } else {
157
- if (tok.test(chunks[j])) {
158
- return true;
159
- } else {
160
- return false;
161
- }
260
+ return tok.test(current);
261
+ }
262
+ }
263
+
264
+ // Specialization of peek()
265
+ function peekChar(tok) {
266
+ return input.charAt(i) === tok;
267
+ }
268
+
269
+
270
+ function getInput(e, env) {
271
+ if (e.filename && env.currentFileInfo.filename && (e.filename !== env.currentFileInfo.filename)) {
272
+ return parser.imports.contents[e.filename];
273
+ } else {
274
+ return input;
275
+ }
276
+ }
277
+
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);
162
301
  }
302
+
303
+ return {
304
+ lineNumber: getLocation(index, inputStream).line + 1,
305
+ fileName: filename
306
+ };
307
+ }
308
+
309
+ function LessError(e, env) {
310
+ var input = getInput(e, env),
311
+ loc = getLocation(e.index, input),
312
+ line = loc.line,
313
+ col = loc.column,
314
+ callLine = e.call && getLocation(e.call, input).line,
315
+ lines = input.split('\n');
316
+
317
+ this.type = e.type || 'Syntax';
318
+ this.message = e.message;
319
+ this.filename = e.filename || env.currentFileInfo.filename;
320
+ this.index = e.index;
321
+ this.line = typeof(line) === 'number' ? line + 1 : null;
322
+ this.callLine = callLine + 1;
323
+ this.callExtract = lines[callLine];
324
+ this.stack = e.stack;
325
+ this.column = col;
326
+ this.extract = [
327
+ lines[line - 1],
328
+ lines[line],
329
+ lines[line + 1]
330
+ ];
163
331
  }
164
332
 
333
+ LessError.prototype = new Error();
334
+ LessError.prototype.constructor = LessError;
335
+
165
336
  this.env = env = env || {};
166
337
 
167
338
  // The optimization level dictates the thoroughness of the parser,
@@ -170,107 +341,183 @@ less.Parser = function Parser(env) {
170
341
  // the individual nodes in the tree.
171
342
  this.optimization = ('optimization' in this.env) ? this.env.optimization : 1;
172
343
 
173
- this.env.filename = this.env.filename || null;
174
-
175
344
  //
176
345
  // The Parser
177
346
  //
178
- return parser = {
347
+ parser = {
179
348
 
180
349
  imports: imports,
181
350
  //
182
351
  // Parse an input string into an abstract syntax tree,
183
- // 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
184
355
  //
185
- parse: function (str, callback) {
186
- 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;
187
360
 
188
- i = j = current = furthest = 0;
189
- chunks = [];
190
- input = str.replace(/\r\n/g, '\n');
361
+ globalVars = (additionalData && additionalData.globalVars) ? less.Parser.serializeVars(additionalData.globalVars) + '\n' : '';
362
+ modifyVars = (additionalData && additionalData.modifyVars) ? '\n' + less.Parser.serializeVars(additionalData.modifyVars) : '';
363
+
364
+ if (globalVars || (additionalData && additionalData.banner)) {
365
+ preText = ((additionalData && additionalData.banner) ? additionalData.banner : "") + globalVars;
366
+ parser.imports.contentsIgnoredChars[env.currentFileInfo.filename] = preText.length;
367
+ }
368
+
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;
191
373
 
192
374
  // Split the input into chunks.
193
- chunks = (function (chunks) {
194
- var j = 0,
195
- skip = /[^"'`\{\}\/\(\)]+/g,
196
- comment = /\/\*(?:[^*]|\*+[^\/*])*\*+\/|\/\/.*/g,
197
- level = 0,
198
- match,
199
- chunk = chunks[0],
200
- inParam,
201
- inString;
202
-
203
- for (var i = 0, c, cc; i < input.length; i++) {
204
- skip.lastIndex = i;
205
- if (match = skip.exec(input)) {
206
- if (match.index === i) {
207
- i += match[0].length;
208
- chunk.push(match[0]);
209
- }
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;
210
394
  }
211
- c = input.charAt(i);
212
- comment.lastIndex = i;
213
-
214
- if (!inString && !inParam && c === '/') {
215
- cc = input.charAt(i + 1);
216
- if (cc === '/' || cc === '*') {
217
- if (match = comment.exec(input)) {
218
- if (match.index === i) {
219
- i += match[0].length;
220
- chunk.push(match[0]);
221
- c = input.charAt(i);
395
+ chunks.push(input.slice(emitFrom, parserCurrentIndex + 1));
396
+ emitFrom = parserCurrentIndex + 1;
397
+ }
398
+
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;
404
+ }
405
+
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++;
222
446
  }
223
447
  }
224
- }
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;
225
479
  }
480
+ }
226
481
 
227
- if (c === '{' && !inString && !inParam) { level ++;
228
- chunk.push(c);
229
- } else if (c === '}' && !inString && !inParam) { level --;
230
- chunk.push(c);
231
- chunks[++j] = chunk = [];
232
- } else if (c === '(' && !inString && !inParam) {
233
- chunk.push(c);
234
- inParam = true;
235
- } else if (c === ')' && !inString && inParam) {
236
- chunk.push(c);
237
- inParam = false;
482
+ if (level !== 0) {
483
+ if ((lastMultiComment > lastOpening) && (lastMultiCommentEndBrace > lastMultiComment)) {
484
+ return fail("missing closing `}` or `*/`", lastOpening);
238
485
  } else {
239
- if (c === '"' || c === "'" || c === '`') {
240
- if (! inString) {
241
- inString = c;
242
- } else {
243
- inString = inString === c ? false : inString;
244
- }
245
- }
246
- chunk.push(c);
486
+ return fail("missing closing `}`", lastOpening);
247
487
  }
248
- }
249
- if (level > 0) {
250
- throw {
251
- type: 'Syntax',
252
- message: "Missing closing `}`",
253
- filename: env.filename
254
- };
488
+ } else if (parenLevel !== 0) {
489
+ return fail("missing closing `)`", lastOpeningParen);
255
490
  }
256
491
 
257
- return chunks.map(function (c) { return c.join('') });;
258
- })([[]]);
492
+ emitChunk(true);
493
+ return chunks;
494
+ })(str);
495
+
496
+ if (error) {
497
+ return callback(new(LessError)(error, env));
498
+ }
499
+
500
+ current = chunks[0];
259
501
 
260
502
  // Start with the primary rule.
261
503
  // The whole syntax tree is held under a Ruleset node,
262
504
  // with the `root` property set to true, so no `{}` are
263
505
  // output. The callback is called when the input is parsed.
264
- root = new(tree.Ruleset)([], $(this.parsers.primary));
265
- root.root = true;
506
+ try {
507
+ root = new(tree.Ruleset)(null, this.parsers.primary());
508
+ root.root = true;
509
+ root.firstRoot = true;
510
+ } catch (e) {
511
+ return callback(new(LessError)(e, env));
512
+ }
266
513
 
267
514
  root.toCSS = (function (evaluate) {
268
- var line, lines, column;
269
-
270
515
  return function (options, variables) {
271
- var frames = [];
272
-
273
516
  options = options || {};
517
+ var evaldRoot,
518
+ css,
519
+ evalEnv = new tree.evalEnv(options);
520
+
274
521
  //
275
522
  // Allows setting variables with a hash, so:
276
523
  //
@@ -294,47 +541,87 @@ less.Parser = function Parser(env) {
294
541
  }
295
542
  value = new(tree.Value)([value]);
296
543
  }
297
- return new(tree.Rule)('@' + k, value, false, 0);
544
+ return new(tree.Rule)('@' + k, value, false, null, 0);
298
545
  });
299
- frames = [new(tree.Ruleset)(null, variables)];
546
+ evalEnv.frames = [new(tree.Ruleset)(null, variables)];
300
547
  }
301
548
 
302
549
  try {
303
- var css = evaluate.call(this, { frames: frames })
304
- .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});
305
603
  } catch (e) {
306
- lines = input.split('\n');
307
- line = getLine(e.index);
308
-
309
- for (var n = e.index, column = -1;
310
- n >= 0 && input.charAt(n) !== '\n';
311
- n--) { column++ }
312
-
313
- throw {
314
- type: e.type,
315
- message: e.message,
316
- filename: env.filename,
317
- index: e.index,
318
- line: typeof(line) === 'number' ? line + 1 : null,
319
- callLine: e.call && (getLine(e.call) + 1),
320
- callExtract: lines[getLine(e.call)],
321
- stack: e.stack,
322
- column: column,
323
- extract: [
324
- lines[line - 1],
325
- lines[line],
326
- lines[line + 1]
327
- ]
328
- };
329
- }
330
- if (options.compress) {
331
- return css.replace(/(\s)+/g, "$1");
332
- } else {
333
- return css;
604
+ throw new(LessError)(e, env);
334
605
  }
335
606
 
336
- function getLine(index) {
337
- return index ? (input.slice(0, index).match(/\n/g) || "").length : null;
607
+ if (options.cleancss && less.mode === 'node') {
608
+ var CleanCSS = require('clean-css'),
609
+ cleancssOptions = options.cleancssOptions || {};
610
+
611
+ if (cleancssOptions.keepSpecialComments === undefined) {
612
+ cleancssOptions.keepSpecialComments = "*";
613
+ }
614
+ cleancssOptions.processImport = false;
615
+ cleancssOptions.noRebase = true;
616
+ if (cleancssOptions.noAdvanced === undefined) {
617
+ cleancssOptions.noAdvanced = true;
618
+ }
619
+
620
+ return new CleanCSS(cleancssOptions).minify(css);
621
+ } else if (options.compress) {
622
+ return css.replace(/(^(\s)+)|((\s)+$)/g, "");
623
+ } else {
624
+ return css;
338
625
  }
339
626
  };
340
627
  })(root.eval);
@@ -349,18 +636,17 @@ less.Parser = function Parser(env) {
349
636
  // and the part which didn't), so we can color them differently.
350
637
  if (i < input.length - 1) {
351
638
  i = furthest;
639
+ var loc = getLocation(i, input);
352
640
  lines = input.split('\n');
353
- line = (input.slice(0, i).match(/\n/g) || "").length + 1;
354
-
355
- for (var n = i, column = -1; n >= 0 && input.charAt(n) !== '\n'; n--) { column++ }
641
+ line = loc.line + 1;
356
642
 
357
643
  error = {
358
- name: "ParseError",
359
- message: "Syntax Error on line " + line,
644
+ type: "Parse",
645
+ message: "Unrecognised input",
360
646
  index: i,
361
- filename: env.filename,
647
+ filename: env.currentFileInfo.filename,
362
648
  line: line,
363
- column: column,
649
+ column: loc.column,
364
650
  extract: [
365
651
  lines[line - 2],
366
652
  lines[line - 1],
@@ -369,10 +655,26 @@ less.Parser = function Parser(env) {
369
655
  };
370
656
  }
371
657
 
372
- if (this.imports.queue.length > 0) {
373
- 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);
374
676
  } else {
375
- callback(error, root);
677
+ return finish();
376
678
  }
377
679
  },
378
680
 
@@ -405,7 +707,7 @@ less.Parser = function Parser(env) {
405
707
  // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
406
708
  // first, before parsing, that's when we use `peek()`.
407
709
  //
408
- parsers: {
710
+ parsers: parsers = {
409
711
  //
410
712
  // The `primary` rule is the *entry* and *exit* point of the parser.
411
713
  // The rules here can appear at any level of the parse tree.
@@ -422,13 +724,24 @@ less.Parser = function Parser(env) {
422
724
  // block rule: at the root level.
423
725
  //
424
726
  primary: function () {
425
- var node, root = [];
426
-
427
- while ((node = $(this.mixin.definition) || $(this.rule) || $(this.ruleset) ||
428
- $(this.mixin.call) || $(this.comment) || $(this.directive))
429
- || $(/^[\s\n]+/)) {
430
- 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
+ }
431
743
  }
744
+
432
745
  return root;
433
746
  },
434
747
 
@@ -438,15 +751,31 @@ less.Parser = function Parser(env) {
438
751
  comment: function () {
439
752
  var comment;
440
753
 
441
- if (input.charAt(i) !== '/') return;
754
+ if (input.charAt(i) !== '/') { return; }
442
755
 
443
756
  if (input.charAt(i + 1) === '/') {
444
- return new(tree.Comment)($(/^\/\/.*/), true);
445
- } else if (comment = $(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/)) {
446
- 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);
447
762
  }
448
763
  },
449
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);
774
+ }
775
+
776
+ return comments;
777
+ },
778
+
450
779
  //
451
780
  // Entities are tokens which can be found inside an Expression
452
781
  //
@@ -457,15 +786,16 @@ less.Parser = function Parser(env) {
457
786
  // "milky way" 'he\'s the one!'
458
787
  //
459
788
  quoted: function () {
460
- var str, j = i, e;
789
+ var str, j = i, e, index = i;
461
790
 
462
- if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
463
- 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; }
464
793
 
465
- e && $('~');
794
+ if (e) { $char('~'); }
466
795
 
467
- if (str = $(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/)) {
468
- 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);
469
799
  }
470
800
  },
471
801
 
@@ -476,7 +806,15 @@ less.Parser = function Parser(env) {
476
806
  //
477
807
  keyword: function () {
478
808
  var k;
479
- if (k = $(/^[A-Za-z-]+/)) { return new(tree.Keyword)(k) }
809
+
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;
815
+ }
816
+ return new(tree.Keyword)(k);
817
+ }
480
818
  },
481
819
 
482
820
  //
@@ -490,38 +828,77 @@ less.Parser = function Parser(env) {
490
828
  // The arguments are parsed with the `entities.arguments` parser.
491
829
  //
492
830
  call: function () {
493
- var name, args, index = i;
831
+ var name, nameLC, args, alpha_ret, index = i;
494
832
 
495
- if (! (name = /^([\w-]+|%)\(/.exec(chunks[j]))) return;
833
+ name = /^([\w-]+|%|progid:[\w\.]+)\(/.exec(current);
834
+ if (!name) { return; }
496
835
 
497
- name = name[1].toLowerCase();
836
+ name = name[1];
837
+ nameLC = name.toLowerCase();
838
+ if (nameLC === 'url') {
839
+ return null;
840
+ }
498
841
 
499
- if (name === 'url') { return null }
500
- else { i += name.length }
842
+ i += name.length;
501
843
 
502
- 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
+ }
503
850
 
504
- $('('); // Parse the '(' and consume whitespace.
851
+ $char('('); // Parse the '(' and consume whitespace.
505
852
 
506
- args = $(this.entities.arguments);
853
+ args = this.arguments();
507
854
 
508
- if (! $(')')) return;
855
+ if (! $char(')')) {
856
+ return;
857
+ }
509
858
 
510
- if (name) { return new(tree.Call)(name, args, index) }
859
+ if (name) { return new(tree.Call)(name, args, index, env.currentFileInfo); }
511
860
  },
512
861
  arguments: function () {
513
862
  var args = [], arg;
514
863
 
515
- while (arg = $(this.expression)) {
864
+ while (true) {
865
+ arg = this.assignment() || parsers.expression();
866
+ if (!arg) {
867
+ break;
868
+ }
516
869
  args.push(arg);
517
- if (! $(',')) { break }
870
+ if (! $char(',')) {
871
+ break;
872
+ }
518
873
  }
519
874
  return args;
520
875
  },
521
876
  literal: function () {
522
- return $(this.entities.dimension) ||
523
- $(this.entities.color) ||
524
- $(this.entities.quoted);
877
+ return this.dimension() ||
878
+ this.color() ||
879
+ this.quoted() ||
880
+ this.unicodeDescriptor();
881
+ },
882
+
883
+ // Assignments are argument entities for calls.
884
+ // They are present in ie filter properties as shown below.
885
+ //
886
+ // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
887
+ //
888
+
889
+ assignment: function () {
890
+ var key, value;
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) {
900
+ return new(tree.Assignment)(key, value);
901
+ }
525
902
  },
526
903
 
527
904
  //
@@ -534,27 +911,17 @@ less.Parser = function Parser(env) {
534
911
  url: function () {
535
912
  var value;
536
913
 
537
- if (input.charAt(i) !== 'u' || !$(/^url\(/)) return;
538
- value = $(this.entities.quoted) || $(this.entities.variable) ||
539
- $(this.entities.dataURI) || $(/^[-\w%@$\/.&=:;#+?~]+/) || "";
540
- if (! $(')')) throw new(Error)("missing closing ) for url()");
541
-
542
- return new(tree.URL)((value.value || value.data || value instanceof tree.Variable)
543
- ? value : new(tree.Anonymous)(value), imports.paths);
544
- },
914
+ if (input.charAt(i) !== 'u' || !$re(/^url\(/)) {
915
+ return;
916
+ }
545
917
 
546
- dataURI: function () {
547
- var obj;
918
+ value = this.quoted() || this.variable() ||
919
+ $re(/^(?:(?:\\[\(\)'"])|[^\(\)'"])+/) || "";
548
920
 
549
- if ($(/^data:/)) {
550
- obj = {};
551
- obj.mime = $(/^[^\/]+\/[^,;)]+/) || '';
552
- obj.charset = $(/^;\s*charset=[^,;)]+/) || '';
553
- obj.base64 = $(/^;\s*base64/) || '';
554
- obj.data = $(/^,\s*[^)]+/);
921
+ expectChar(')');
555
922
 
556
- if (obj.data) { return obj }
557
- }
923
+ return new(tree.URL)((value.value != null || value instanceof tree.Variable)
924
+ ? value : new(tree.Anonymous)(value), env.currentFileInfo);
558
925
  },
559
926
 
560
927
  //
@@ -568,8 +935,17 @@ less.Parser = function Parser(env) {
568
935
  variable: function () {
569
936
  var name, index = i;
570
937
 
571
- if (input.charAt(i) === '@' && (name = $(/^@@?[\w-]+/))) {
572
- return new(tree.Variable)(name, index);
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);
573
949
  }
574
950
  },
575
951
 
@@ -583,7 +959,7 @@ less.Parser = function Parser(env) {
583
959
  color: function () {
584
960
  var rgb;
585
961
 
586
- 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})/))) {
587
963
  return new(tree.Color)(rgb[1]);
588
964
  }
589
965
  },
@@ -595,13 +971,31 @@ less.Parser = function Parser(env) {
595
971
  //
596
972
  dimension: function () {
597
973
  var value, c = input.charCodeAt(i);
598
- 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
+ }
599
978
 
600
- if (value = $(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/)) {
979
+ value = $re(/^([+-]?\d*\.?\d+)(%|[a-z]+)?/);
980
+ if (value) {
601
981
  return new(tree.Dimension)(value[1], value[2]);
602
982
  }
603
983
  },
604
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
+
605
999
  //
606
1000
  // JavaScript code to be evaluated
607
1001
  //
@@ -610,12 +1004,16 @@ less.Parser = function Parser(env) {
610
1004
  javascript: function () {
611
1005
  var str, j = i, e;
612
1006
 
613
- if (input.charAt(j) === '~') { j++, e = true } // Escaped strings
614
- 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
+ }
615
1012
 
616
- e && $('~');
1013
+ if (e) { $char('~'); }
617
1014
 
618
- if (str = $(/^`([^`]*)`/)) {
1015
+ str = $re(/^`([^`]*)`/);
1016
+ if (str) {
619
1017
  return new(tree.JavaScript)(str[1], i, e);
620
1018
  }
621
1019
  }
@@ -629,26 +1027,62 @@ less.Parser = function Parser(env) {
629
1027
  variable: function () {
630
1028
  var name;
631
1029
 
632
- if (input.charAt(i) === '@' && (name = $(/^(@[\w-]+)\s*:/))) { return name[1] }
1030
+ if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*:/))) { return name[1]; }
633
1031
  },
634
1032
 
635
1033
  //
636
- // A font size/line-height shorthand
1034
+ // The variable part of a variable definition. Used in the `rule` parser
1035
+ //
1036
+ // @fink();
637
1037
  //
638
- // 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
+
639
1046
  //
640
- // We need to peek first, or we'll match on keywords and dimensions
1047
+ // extend syntax - used to extend selectors
641
1048
  //
642
- shorthand: function () {
643
- 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];
644
1064
 
645
- 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 ]; }
646
1067
 
647
- if ((a = $(this.entity)) && $('/') && (b = $(this.entity))) {
648
- return new(tree.Shorthand)(a, b);
1068
+ } while($char(","));
1069
+
1070
+ expect(/^\)/);
1071
+
1072
+ if (isRule) {
1073
+ expect(/^;/);
649
1074
  }
1075
+
1076
+ return extendList;
650
1077
  },
651
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
+
652
1086
  //
653
1087
  // Mixins
654
1088
  //
@@ -665,21 +1099,158 @@ less.Parser = function Parser(env) {
665
1099
  // selector for now.
666
1100
  //
667
1101
  call: function () {
668
- var elements = [], e, c, args, index = i, s = input.charAt(i);
1102
+ var s = input.charAt(i), important = false, index = i, elemIndex,
1103
+ elements, elem, e, c, args;
669
1104
 
670
- if (s !== '.' && s !== '#') { return }
1105
+ if (s !== '.' && s !== '#') { return; }
671
1106
 
672
- while (e = $(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)) {
673
- elements.push(new(tree.Element)(c, e));
674
- c = $('>');
1107
+ save(); // stop us absorbing part of an invalid selector
1108
+
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('>');
675
1118
  }
676
- $('(') && (args = $(this.entities.arguments)) && $(')');
677
1119
 
678
- if (elements.length > 0 && ($(';') || peek('}'))) {
679
- return new(tree.mixin.Call)(elements, args, index);
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
+ }
680
1134
  }
1135
+
1136
+ restore();
681
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;
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();
682
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
+ },
683
1254
  //
684
1255
  // A Mixin definition, with a list of parameters
685
1256
  //
@@ -700,39 +1271,49 @@ less.Parser = function Parser(env) {
700
1271
  // the `{...}` block.
701
1272
  //
702
1273
  definition: function () {
703
- var name, params = [], match, ruleset, param, value;
704
-
1274
+ var name, params = [], match, ruleset, cond, variadic = false;
705
1275
  if ((input.charAt(i) !== '.' && input.charAt(i) !== '#') ||
706
- peek(/^[^{]*(;|})/)) return;
1276
+ peek(/^[^{]*\}/)) {
1277
+ return;
1278
+ }
1279
+
1280
+ save();
707
1281
 
708
- 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) {
709
1284
  name = match[1];
710
1285
 
711
- while (param = $(this.entities.variable) || $(this.entities.literal)
712
- || $(this.entities.keyword)) {
713
- // Variable
714
- if (param instanceof tree.Variable) {
715
- if ($(':')) {
716
- if (value = $(this.expression)) {
717
- params.push({ name: param.name, value: value });
718
- } else {
719
- throw new(Error)("Expected value");
720
- }
721
- } else {
722
- params.push({ name: param.name });
723
- }
724
- } else {
725
- params.push({ value: param });
726
- }
727
- if (! $(',')) { break }
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;
728
1299
  }
729
- if (! $(')')) throw new(Error)("Expected )");
1300
+
1301
+ parsers.comments();
730
1302
 
731
- ruleset = $(this.block);
1303
+ if ($re(/^when/)) { // Guard
1304
+ cond = expect(parsers.conditions, 'expected condition');
1305
+ }
1306
+
1307
+ ruleset = parsers.block();
732
1308
 
733
1309
  if (ruleset) {
734
- return new(tree.mixin.Definition)(name, params, ruleset);
1310
+ forget();
1311
+ return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
1312
+ } else {
1313
+ restore();
735
1314
  }
1315
+ } else {
1316
+ forget();
736
1317
  }
737
1318
  }
738
1319
  },
@@ -742,9 +1323,11 @@ less.Parser = function Parser(env) {
742
1323
  // and can be found inside a rule's value.
743
1324
  //
744
1325
  entity: function () {
745
- return $(this.entities.literal) || $(this.entities.variable) || $(this.entities.url) ||
746
- $(this.entities.call) || $(this.entities.keyword) || $(this.entities.javascript) ||
747
- $(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();
748
1331
  },
749
1332
 
750
1333
  //
@@ -753,7 +1336,7 @@ less.Parser = function Parser(env) {
753
1336
  // it's there, if ';' was ommitted.
754
1337
  //
755
1338
  end: function () {
756
- return $(';') || peek('}');
1339
+ return $char(';') || peekChar('}');
757
1340
  },
758
1341
 
759
1342
  //
@@ -764,9 +1347,10 @@ less.Parser = function Parser(env) {
764
1347
  alpha: function () {
765
1348
  var value;
766
1349
 
767
- if (! $(/^\(opacity=/i)) return;
768
- if (value = $(/^\d+/) || $(this.entities.variable)) {
769
- if (! $(')')) throw new(Error)("missing closing ) for alpha()");
1350
+ if (! $re(/^\(opacity=/i)) { return; }
1351
+ value = $re(/^\d+/) || this.entities.variable();
1352
+ if (value) {
1353
+ expectChar(')');
770
1354
  return new(tree.Alpha)(value);
771
1355
  }
772
1356
  },
@@ -784,12 +1368,29 @@ less.Parser = function Parser(env) {
784
1368
  // and an element name, such as a tag a class, or `*`.
785
1369
  //
786
1370
  element: function () {
787
- var e, t, c;
788
-
789
- c = $(this.combinator);
790
- e = $(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/) || $('*') || $(this.attribute) || $(/^\([^)@]+\)/);
1371
+ var e, c, v, index = i;
1372
+
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();
1378
+
1379
+ if (! e) {
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
+ }
1391
+ }
791
1392
 
792
- if (e) { return new(tree.Element)(c, e) }
1393
+ if (e) { return new(tree.Element)(c, e, index, env.currentFileInfo); }
793
1394
  },
794
1395
 
795
1396
  //
@@ -802,23 +1403,29 @@ less.Parser = function Parser(env) {
802
1403
  // we deal with this in *combinator.js*.
803
1404
  //
804
1405
  combinator: function () {
805
- var match, c = input.charAt(i);
806
-
807
- if (c === '>' || c === '&' || c === '+' || c === '~') {
1406
+ var c = input.charAt(i);
1407
+
1408
+ if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') {
808
1409
  i++;
809
- while (input.charAt(i) === ' ') { i++ }
1410
+ if (input.charAt(i) === '^') {
1411
+ c = '^^';
1412
+ i++;
1413
+ }
1414
+ while (isWhitespace(input, i)) { i++; }
810
1415
  return new(tree.Combinator)(c);
811
- } else if (c === ':' && input.charAt(i + 1) === ':') {
812
- i += 2;
813
- while (input.charAt(i) === ' ') { i++ }
814
- return new(tree.Combinator)('::');
815
- } else if (input.charAt(i - 1) === ' ') {
1416
+ } else if (isWhitespace(input, i - 1)) {
816
1417
  return new(tree.Combinator)(" ");
817
1418
  } else {
818
1419
  return new(tree.Combinator)(null);
819
1420
  }
820
1421
  },
821
-
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
+ },
822
1429
  //
823
1430
  // A CSS Selector
824
1431
  //
@@ -827,35 +1434,48 @@ less.Parser = function Parser(env) {
827
1434
  //
828
1435
  // Selectors are made out of one or more Elements, see above.
829
1436
  //
830
- selector: function () {
831
- var sel, e, elements = [], c, match;
832
-
833
- while (e = $(this.element)) {
834
- c = input.charAt(i);
835
- elements.push(e)
836
- 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
+ }
837
1456
  }
838
1457
 
839
- if (elements.length > 0) { return new(tree.Selector)(elements) }
840
- },
841
- tag: function () {
842
- 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"); }
843
1460
  },
844
1461
  attribute: function () {
845
- var attr = '', key, val, op;
1462
+ if (! $char('[')) { return; }
846
1463
 
847
- if (! $('[')) return;
1464
+ var entities = this.entities,
1465
+ key, val, op;
1466
+
1467
+ if (!(key = entities.variableCurly())) {
1468
+ key = expect(/^(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
1469
+ }
848
1470
 
849
- if (key = $(/^[a-zA-Z-]+/) || $(this.entities.quoted)) {
850
- if ((op = $(/^[|~*$^]?=/)) &&
851
- (val = $(this.entities.quoted) || $(/^[\w-]+/))) {
852
- attr = [key, op, val.toCSS ? val.toCSS() : val].join('');
853
- } else { attr = key }
1471
+ op = $re(/^[|~*$^]?=/);
1472
+ if (op) {
1473
+ val = entities.quoted() || $re(/^[0-9]+%/) || $re(/^[\w-]+/) || entities.variableCurly();
854
1474
  }
855
1475
 
856
- if (! $(']')) return;
1476
+ expectChar(']');
857
1477
 
858
- if (attr) { return "[" + attr + "]" }
1478
+ return new(tree.Attribute)(key, op, val);
859
1479
  },
860
1480
 
861
1481
  //
@@ -864,62 +1484,119 @@ less.Parser = function Parser(env) {
864
1484
  //
865
1485
  block: function () {
866
1486
  var content;
867
-
868
- if ($('{') && (content = $(this.primary)) && $('}')) {
1487
+ if ($char('{') && (content = this.primary()) && $char('}')) {
869
1488
  return content;
870
1489
  }
871
1490
  },
872
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
+
873
1508
  //
874
1509
  // div, .class, body > p {...}
875
1510
  //
876
1511
  ruleset: function () {
877
- var selectors = [], s, rules, match;
1512
+ var selectors, s, rules, debugInfo;
1513
+
878
1514
  save();
879
1515
 
880
- if (match = /^([.#: \w-]+)[\s\n]*\{/.exec(chunks[j])) {
881
- i += match[0].length - 1;
882
- selectors = [new(tree.Selector)([new(tree.Element)(null, match[1])])];
883
- } else {
884
- while (s = $(this.selector)) {
885
- selectors.push(s);
886
- $(this.comment);
887
- if (! $(',')) { break }
888
- $(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.");
889
1529
  }
1530
+ if (! $char(',')) { break; }
1531
+ if (s.condition) {
1532
+ error("Guards are only currently allowed on a single selector.");
1533
+ }
1534
+ this.comments();
890
1535
  }
891
1536
 
892
- if (selectors.length > 0 && (rules = $(this.block))) {
893
- return new(tree.Ruleset)(selectors, rules);
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;
894
1544
  } else {
895
1545
  // Backtrack
896
1546
  furthest = i;
897
1547
  restore();
898
1548
  }
899
1549
  },
900
- rule: function () {
901
- var name, value, c = input.charAt(i), important, match;
902
- save();
1550
+ rule: function (tryAnonymous) {
1551
+ var name, value, startOfRule = i, c = input.charAt(startOfRule), important, merge, isVariable;
903
1552
 
904
- if (c === '.' || c === '#' || c === '&') { return }
1553
+ if (c === '.' || c === '#' || c === '&') { return; }
905
1554
 
906
- if (name = $(this.variable) || $(this.property)) {
907
- if ((name.charAt(0) != '@') && (match = /^([^@+\/'"*`(;{}-]*);/.exec(chunks[j]))) {
908
- i += match[0].length - 1;
909
- value = new(tree.Anonymous)(match[1]);
910
- } else if (name === "font") {
911
- value = $(this.font);
912
- } else {
913
- 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;
914
1578
  }
915
- important = $(this.important);
916
1579
 
917
- if (value && $(this.end)) {
918
- 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);
919
1583
  } else {
920
1584
  furthest = i;
921
1585
  restore();
1586
+ if (value && !tryAnonymous) {
1587
+ return this.rule(true);
1588
+ }
922
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]);
923
1600
  }
924
1601
  },
925
1602
 
@@ -934,11 +1611,124 @@ less.Parser = function Parser(env) {
934
1611
  // stored in `import`, which we pass to the Import constructor.
935
1612
  //
936
1613
  "import": function () {
937
- var path;
938
- if ($(/^@import\s+/) &&
939
- (path = $(this.entities.quoted) || $(this.entities.url)) &&
940
- $(';')) {
941
- return new(tree.Import)(path, imports);
1614
+ var path, features, index = i;
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);
1628
+ }
1629
+ }
1630
+
1631
+ restore();
1632
+ },
1633
+
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
+ },
1668
+
1669
+ mediaFeature: function () {
1670
+ var entities = this.entities, nodes = [], e, p;
1671
+ do {
1672
+ e = entities.keyword() || entities.variable();
1673
+ if (e) {
1674
+ nodes.push(e);
1675
+ } else if ($char('(')) {
1676
+ p = this.property();
1677
+ e = this.value();
1678
+ if ($char(')')) {
1679
+ if (p && e) {
1680
+ nodes.push(new(tree.Paren)(new(tree.Rule)(p, e, null, null, i, env.currentFileInfo, true)));
1681
+ } else if (e) {
1682
+ nodes.push(new(tree.Paren)(e));
1683
+ } else {
1684
+ return null;
1685
+ }
1686
+ } else { return null; }
1687
+ }
1688
+ } while (e);
1689
+
1690
+ if (nodes.length > 0) {
1691
+ return new(tree.Expression)(nodes);
1692
+ }
1693
+ },
1694
+
1695
+ mediaFeatures: function () {
1696
+ var entities = this.entities, features = [], e;
1697
+ do {
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
+ }
1709
+ } while (e);
1710
+
1711
+ return features.length > 0 ? features : null;
1712
+ },
1713
+
1714
+ media: function () {
1715
+ var features, rules, media, debugInfo;
1716
+
1717
+ if (env.dumpLineNumbers) {
1718
+ debugInfo = getDebugInfo(i, input, env);
1719
+ }
1720
+
1721
+ if ($re(/^@media/)) {
1722
+ features = this.mediaFeatures();
1723
+
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;
1731
+ }
942
1732
  }
943
1733
  },
944
1734
 
@@ -948,42 +1738,97 @@ less.Parser = function Parser(env) {
948
1738
  // @charset "utf-8";
949
1739
  //
950
1740
  directive: function () {
951
- var name, value, rules, types;
1741
+ var index = i, name, value, rules, nonVendorSpecificName,
1742
+ hasIdentifier, hasExpression, hasUnknown, hasBlock = true;
952
1743
 
953
- if (input.charAt(i) !== '@') return;
1744
+ if (input.charAt(i) !== '@') { return; }
954
1745
 
955
- if (value = $(this['import'])) {
1746
+ value = this['import']() || this.media();
1747
+ if (value) {
956
1748
  return value;
957
- } else if (name = $(/^@media|@page|@-[-a-z]+/)) {
958
- types = ($(/^[^{]+/) || '').trim();
959
- if (rules = $(this.block)) {
960
- return new(tree.Directive)(name + " " + types, rules);
961
- }
962
- } else if (name = $(/^@[-a-z]+/)) {
963
- if (name === '@font-face') {
964
- if (rules = $(this.block)) {
965
- return new(tree.Directive)(name, rules);
966
- }
967
- } else if ((value = $(this.entity)) && $(';')) {
968
- return new(tree.Directive)(name, value);
969
- }
970
1749
  }
971
- },
972
- font: function () {
973
- var value = [], expression = [], weight, shorthand, font, e;
974
1750
 
975
- while (e = $(this.shorthand) || $(this.entity)) {
976
- 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;
977
1802
  }
978
- value.push(new(tree.Expression)(expression));
979
1803
 
980
- if ($(',')) {
981
- while (e = $(this.expression)) {
982
- value.push(e);
983
- if (! $(',')) { break }
1804
+ if (hasIdentifier) {
1805
+ value = this.entity();
1806
+ if (!value) {
1807
+ error("expected " + name + " identifier");
984
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);
985
1829
  }
986
- return new(tree.Value)(value);
1830
+
1831
+ restore();
987
1832
  },
988
1833
 
989
1834
  //
@@ -995,12 +1840,15 @@ less.Parser = function Parser(env) {
995
1840
  // and before the `;`.
996
1841
  //
997
1842
  value: function () {
998
- var e, expressions = [], important;
1843
+ var e, expressions = [];
999
1844
 
1000
- while (e = $(this.expression)) {
1001
- expressions.push(e);
1002
- if (! $(',')) { break }
1003
- }
1845
+ do {
1846
+ e = this.expression();
1847
+ if (e) {
1848
+ expressions.push(e);
1849
+ if (! $char(',')) { break; }
1850
+ }
1851
+ } while(e);
1004
1852
 
1005
1853
  if (expressions.length > 0) {
1006
1854
  return new(tree.Value)(expressions);
@@ -1008,49 +1856,131 @@ less.Parser = function Parser(env) {
1008
1856
  },
1009
1857
  important: function () {
1010
1858
  if (input.charAt(i) === '!') {
1011
- return $(/^! *important/);
1859
+ return $re(/^! *important/);
1012
1860
  }
1013
1861
  },
1014
1862
  sub: function () {
1015
- var e;
1016
-
1017
- if ($('(') && (e = $(this.expression)) && $(')')) {
1018
- 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
+ }
1019
1873
  }
1020
1874
  },
1021
1875
  multiplication: function () {
1022
- var m, a, op, operation;
1023
- if (m = $(this.operand)) {
1024
- while ((op = ($('/') || $('*'))) && (a = $(this.operand))) {
1025
- 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);
1026
1896
  }
1027
1897
  return operation || m;
1028
1898
  }
1029
1899
  },
1030
1900
  addition: function () {
1031
- var m, a, op, operation;
1032
- if (m = $(this.multiplication)) {
1033
- while ((op = $(/^[-+]\s+/) || (input.charAt(i - 1) != ' ' && ($('+') || $('-')))) &&
1034
- (a = $(this.multiplication))) {
1035
- 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);
1036
1919
  }
1037
1920
  return operation || m;
1038
1921
  }
1039
1922
  },
1923
+ conditions: function () {
1924
+ var a, b, index = i, condition;
1925
+
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
+ }
1936
+ condition = new(tree.Condition)('or', condition || a, b, index);
1937
+ }
1938
+ return condition || a;
1939
+ }
1940
+ },
1941
+ condition: function () {
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) {
1953
+ c = new(tree.Condition)(op, a, b, index, negate);
1954
+ } else {
1955
+ error('expected expression');
1956
+ }
1957
+ } else {
1958
+ c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index, negate);
1959
+ }
1960
+ expectChar(')');
1961
+ return $re(/^and/) ? new(tree.Condition)('and', c, this.condition()) : c;
1962
+ }
1963
+ },
1040
1964
 
1041
1965
  //
1042
1966
  // An operand is anything that can be part of an operation,
1043
1967
  // such as a Color, or a Variable
1044
1968
  //
1045
1969
  operand: function () {
1046
- var negate, p = input.charAt(i + 1);
1047
-
1048
- if (input.charAt(i) === '-' && (p === '@' || p === '(')) { negate = $('-') }
1049
- var o = $(this.sub) || $(this.entities.dimension) ||
1050
- $(this.entities.color) || $(this.entities.variable) ||
1051
- $(this.entities.call);
1052
- return negate ? new(tree.Operation)('*', [new(tree.Dimension)(-1), o])
1053
- : 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;
1054
1984
  },
1055
1985
 
1056
1986
  //
@@ -1061,38 +1991,78 @@ less.Parser = function Parser(env) {
1061
1991
  // @var * 2
1062
1992
  //
1063
1993
  expression: function () {
1064
- var e, delim, entities = [], d;
1065
-
1066
- while (e = $(this.addition) || $(this.entity)) {
1067
- entities.push(e);
1068
- }
1994
+ var entities = [], e, delim;
1995
+
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);
1069
2009
  if (entities.length > 0) {
1070
2010
  return new(tree.Expression)(entities);
1071
2011
  }
1072
2012
  },
1073
2013
  property: function () {
1074
- var name;
1075
-
1076
- if (name = $(/^(\*?-?[-a-z_0-9]+)\s*:/)) {
2014
+ var name = $re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);
2015
+ if (name) {
1077
2016
  return name[1];
1078
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
+ }
1079
2051
  }
1080
2052
  }
1081
2053
  };
2054
+ return parser;
1082
2055
  };
1083
-
1084
- if (typeof(window) !== 'undefined') {
1085
- //
1086
- // Used by `@import` directives
1087
- //
1088
- less.Parser.importer = function (path, paths, callback, env) {
1089
- if (path.charAt(0) !== '/' && paths.length > 0) {
1090
- 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) === ';') ? '' : ';');
1091
2064
  }
1092
- // We pass `true` as 3rd argument, to force the reload of the import.
1093
- // This is so we can get the syntax tree as opposed to just the CSS output,
1094
- // as we need this to evaluate the current stylesheet.
1095
- loadStyleSheet({ href: path, title: path, type: env.mime }, callback, true);
1096
- };
1097
- }
2065
+ }
1098
2066
 
2067
+ return s;
2068
+ };