parser.js 94 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. var tslib_1 = require("tslib");
  4. var less_error_1 = tslib_1.__importDefault(require("../less-error"));
  5. var tree_1 = tslib_1.__importDefault(require("../tree"));
  6. var visitors_1 = tslib_1.__importDefault(require("../visitors"));
  7. var parser_input_1 = tslib_1.__importDefault(require("./parser-input"));
  8. var utils = tslib_1.__importStar(require("../utils"));
  9. var function_registry_1 = tslib_1.__importDefault(require("../functions/function-registry"));
  10. var atrule_syntax_1 = require("../tree/atrule-syntax");
  11. //
  12. // less.js - parser
  13. //
  14. // A relatively straight-forward predictive parser.
  15. // There is no tokenization/lexing stage, the input is parsed
  16. // in one sweep.
  17. //
  18. // To make the parser fast enough to run in the browser, several
  19. // optimization had to be made:
  20. //
  21. // - Matching and slicing on a huge input is often cause of slowdowns.
  22. // The solution is to chunkify the input into smaller strings.
  23. // The chunks are stored in the `chunks` var,
  24. // `j` holds the current chunk index, and `currentPos` holds
  25. // the index of the current chunk in relation to `input`.
  26. // This gives us an almost 4x speed-up.
  27. //
  28. // - In many cases, we don't need to match individual tokens;
  29. // for example, if a value doesn't hold any variables, operations
  30. // or dynamic references, the parser can effectively 'skip' it,
  31. // treating it as a literal.
  32. // An example would be '1px solid #000' - which evaluates to itself,
  33. // we don't need to know what the individual components are.
  34. // The drawback, of course is that you don't get the benefits of
  35. // syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
  36. // and a smaller speed-up in the code-gen.
  37. //
  38. //
  39. // Token matching is done with the `$` function, which either takes
  40. // a terminal string or regexp, or a non-terminal function to call.
  41. // It also takes care of moving all the indices forwards.
  42. //
  43. var Parser = function Parser(context, imports, fileInfo, currentIndex) {
  44. currentIndex = currentIndex || 0;
  45. var parsers;
  46. var parserInput = parser_input_1.default();
  47. function error(msg, type) {
  48. throw new less_error_1.default({
  49. index: parserInput.i,
  50. filename: fileInfo.filename,
  51. type: type || 'Syntax',
  52. message: msg
  53. }, imports);
  54. }
  55. function expect(arg, msg) {
  56. // some older browsers return typeof 'function' for RegExp
  57. var result = (arg instanceof Function) ? arg.call(parsers) : parserInput.$re(arg);
  58. if (result) {
  59. return result;
  60. }
  61. error(msg || (typeof arg === 'string'
  62. ? "expected '" + arg + "' got '" + parserInput.currentChar() + "'"
  63. : 'unexpected token'));
  64. }
  65. // Specialization of expect()
  66. function expectChar(arg, msg) {
  67. if (parserInput.$char(arg)) {
  68. return arg;
  69. }
  70. error(msg || "expected '" + arg + "' got '" + parserInput.currentChar() + "'");
  71. }
  72. function getDebugInfo(index) {
  73. var filename = fileInfo.filename;
  74. return {
  75. lineNumber: utils.getLocation(index, parserInput.getInput()).line + 1,
  76. fileName: filename
  77. };
  78. }
  79. /**
  80. * Used after initial parsing to create nodes on the fly
  81. *
  82. * @param {String} str - string to parse
  83. * @param {Array} parseList - array of parsers to run input through e.g. ["value", "important"]
  84. * @param {Number} currentIndex - start number to begin indexing
  85. * @param {Object} fileInfo - fileInfo to attach to created nodes
  86. */
  87. function parseNode(str, parseList, callback) {
  88. var result;
  89. var returnNodes = [];
  90. var parser = parserInput;
  91. try {
  92. parser.start(str, false, function fail(msg, index) {
  93. callback({
  94. message: msg,
  95. index: index + currentIndex
  96. });
  97. });
  98. for (var x = 0, p = void 0; (p = parseList[x]); x++) {
  99. result = parsers[p]();
  100. returnNodes.push(result || null);
  101. }
  102. var endInfo = parser.end();
  103. if (endInfo.isFinished) {
  104. callback(null, returnNodes);
  105. }
  106. else {
  107. callback(true, null);
  108. }
  109. }
  110. catch (e) {
  111. throw new less_error_1.default({
  112. index: e.index + currentIndex,
  113. message: e.message
  114. }, imports, fileInfo.filename);
  115. }
  116. }
  117. //
  118. // The Parser
  119. //
  120. return {
  121. parserInput: parserInput,
  122. imports: imports,
  123. fileInfo: fileInfo,
  124. parseNode: parseNode,
  125. //
  126. // Parse an input string into an abstract syntax tree,
  127. // @param str A string containing 'less' markup
  128. // @param callback call `callback` when done.
  129. // @param [additionalData] An optional map which can contains vars - a map (key, value) of variables to apply
  130. //
  131. parse: function (str, callback, additionalData) {
  132. var root;
  133. var err = null;
  134. var globalVars;
  135. var modifyVars;
  136. var ignored;
  137. var preText = '';
  138. // Optionally disable @plugin parsing
  139. if (additionalData && additionalData.disablePluginRule) {
  140. parsers.plugin = function () {
  141. var dir = parserInput.$re(/^@plugin?\s+/);
  142. if (dir) {
  143. error('@plugin statements are not allowed when disablePluginRule is set to true');
  144. }
  145. };
  146. }
  147. globalVars = (additionalData && additionalData.globalVars) ? Parser.serializeVars(additionalData.globalVars) + "\n" : '';
  148. modifyVars = (additionalData && additionalData.modifyVars) ? "\n" + Parser.serializeVars(additionalData.modifyVars) : '';
  149. if (context.pluginManager) {
  150. var preProcessors = context.pluginManager.getPreProcessors();
  151. for (var i = 0; i < preProcessors.length; i++) {
  152. str = preProcessors[i].process(str, { context: context, imports: imports, fileInfo: fileInfo });
  153. }
  154. }
  155. if (globalVars || (additionalData && additionalData.banner)) {
  156. preText = ((additionalData && additionalData.banner) ? additionalData.banner : '') + globalVars;
  157. ignored = imports.contentsIgnoredChars;
  158. ignored[fileInfo.filename] = ignored[fileInfo.filename] || 0;
  159. ignored[fileInfo.filename] += preText.length;
  160. }
  161. str = str.replace(/\r\n?/g, '\n');
  162. // Remove potential UTF Byte Order Mark
  163. str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
  164. imports.contents[fileInfo.filename] = str;
  165. // Start with the primary rule.
  166. // The whole syntax tree is held under a Ruleset node,
  167. // with the `root` property set to true, so no `{}` are
  168. // output. The callback is called when the input is parsed.
  169. try {
  170. parserInput.start(str, context.chunkInput, function fail(msg, index) {
  171. throw new less_error_1.default({
  172. index: index,
  173. type: 'Parse',
  174. message: msg,
  175. filename: fileInfo.filename
  176. }, imports);
  177. });
  178. tree_1.default.Node.prototype.parse = this;
  179. root = new tree_1.default.Ruleset(null, this.parsers.primary());
  180. tree_1.default.Node.prototype.rootNode = root;
  181. root.root = true;
  182. root.firstRoot = true;
  183. root.functionRegistry = function_registry_1.default.inherit();
  184. }
  185. catch (e) {
  186. return callback(new less_error_1.default(e, imports, fileInfo.filename));
  187. }
  188. // If `i` is smaller than the `input.length - 1`,
  189. // it means the parser wasn't able to parse the whole
  190. // string, so we've got a parsing error.
  191. //
  192. // We try to extract a \n delimited string,
  193. // showing the line where the parse error occurred.
  194. // We split it up into two parts (the part which parsed,
  195. // and the part which didn't), so we can color them differently.
  196. var endInfo = parserInput.end();
  197. if (!endInfo.isFinished) {
  198. var message = endInfo.furthestPossibleErrorMessage;
  199. if (!message) {
  200. message = 'Unrecognised input';
  201. if (endInfo.furthestChar === '}') {
  202. message += '. Possibly missing opening \'{\'';
  203. }
  204. else if (endInfo.furthestChar === ')') {
  205. message += '. Possibly missing opening \'(\'';
  206. }
  207. else if (endInfo.furthestReachedEnd) {
  208. message += '. Possibly missing something';
  209. }
  210. }
  211. err = new less_error_1.default({
  212. type: 'Parse',
  213. message: message,
  214. index: endInfo.furthest,
  215. filename: fileInfo.filename
  216. }, imports);
  217. }
  218. var finish = function (e) {
  219. e = err || e || imports.error;
  220. if (e) {
  221. if (!(e instanceof less_error_1.default)) {
  222. e = new less_error_1.default(e, imports, fileInfo.filename);
  223. }
  224. return callback(e);
  225. }
  226. else {
  227. return callback(null, root);
  228. }
  229. };
  230. if (context.processImports !== false) {
  231. new visitors_1.default.ImportVisitor(imports, finish)
  232. .run(root);
  233. }
  234. else {
  235. return finish();
  236. }
  237. },
  238. //
  239. // Here in, the parsing rules/functions
  240. //
  241. // The basic structure of the syntax tree generated is as follows:
  242. //
  243. // Ruleset -> Declaration -> Value -> Expression -> Entity
  244. //
  245. // Here's some Less code:
  246. //
  247. // .class {
  248. // color: #fff;
  249. // border: 1px solid #000;
  250. // width: @w + 4px;
  251. // > .child {...}
  252. // }
  253. //
  254. // And here's what the parse tree might look like:
  255. //
  256. // Ruleset (Selector '.class', [
  257. // Declaration ("color", Value ([Expression [Color #fff]]))
  258. // Declaration ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
  259. // Declaration ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
  260. // Ruleset (Selector [Element '>', '.child'], [...])
  261. // ])
  262. //
  263. // In general, most rules will try to parse a token with the `$re()` function, and if the return
  264. // value is truly, will return a new node, of the relevant type. Sometimes, we need to check
  265. // first, before parsing, that's when we use `peek()`.
  266. //
  267. parsers: parsers = {
  268. //
  269. // The `primary` rule is the *entry* and *exit* point of the parser.
  270. // The rules here can appear at any level of the parse tree.
  271. //
  272. // The recursive nature of the grammar is an interplay between the `block`
  273. // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
  274. // as represented by this simplified grammar:
  275. //
  276. // primary → (ruleset | declaration)+
  277. // ruleset → selector+ block
  278. // block → '{' primary '}'
  279. //
  280. // Only at one point is the primary rule not called from the
  281. // block rule: at the root level.
  282. //
  283. primary: function () {
  284. var mixin = this.mixin;
  285. var root = [];
  286. var node;
  287. while (true) {
  288. while (true) {
  289. node = this.comment();
  290. if (!node) {
  291. break;
  292. }
  293. root.push(node);
  294. }
  295. // always process comments before deciding if finished
  296. if (parserInput.finished) {
  297. break;
  298. }
  299. if (parserInput.peek('}')) {
  300. break;
  301. }
  302. node = this.extendRule();
  303. if (node) {
  304. root = root.concat(node);
  305. continue;
  306. }
  307. node = mixin.definition() || this.declaration() || mixin.call(false, false) ||
  308. this.ruleset() || this.variableCall() || this.entities.call() || this.atrule();
  309. if (node) {
  310. root.push(node);
  311. }
  312. else {
  313. var foundSemiColon = false;
  314. while (parserInput.$char(';')) {
  315. foundSemiColon = true;
  316. }
  317. if (!foundSemiColon) {
  318. break;
  319. }
  320. }
  321. }
  322. return root;
  323. },
  324. // comments are collected by the main parsing mechanism and then assigned to nodes
  325. // where the current structure allows it
  326. comment: function () {
  327. if (parserInput.commentStore.length) {
  328. var comment = parserInput.commentStore.shift();
  329. return new (tree_1.default.Comment)(comment.text, comment.isLineComment, comment.index + currentIndex, fileInfo);
  330. }
  331. },
  332. //
  333. // Entities are tokens which can be found inside an Expression
  334. //
  335. entities: {
  336. mixinLookup: function () {
  337. return parsers.mixin.call(true, true);
  338. },
  339. //
  340. // A string, which supports escaping " and '
  341. //
  342. // "milky way" 'he\'s the one!'
  343. //
  344. quoted: function (forceEscaped) {
  345. var str;
  346. var index = parserInput.i;
  347. var isEscaped = false;
  348. parserInput.save();
  349. if (parserInput.$char('~')) {
  350. isEscaped = true;
  351. }
  352. else if (forceEscaped) {
  353. parserInput.restore();
  354. return;
  355. }
  356. str = parserInput.$quoted();
  357. if (!str) {
  358. parserInput.restore();
  359. return;
  360. }
  361. parserInput.forget();
  362. return new (tree_1.default.Quoted)(str.charAt(0), str.substr(1, str.length - 2), isEscaped, index + currentIndex, fileInfo);
  363. },
  364. //
  365. // A catch-all word, such as:
  366. //
  367. // black border-collapse
  368. //
  369. keyword: function () {
  370. var k = parserInput.$char('%') || parserInput.$re(/^\[?(?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+\]?/);
  371. if (k) {
  372. return tree_1.default.Color.fromKeyword(k) || new (tree_1.default.Keyword)(k);
  373. }
  374. },
  375. //
  376. // A function call
  377. //
  378. // rgb(255, 0, 255)
  379. //
  380. // The arguments are parsed with the `entities.arguments` parser.
  381. //
  382. call: function () {
  383. var name;
  384. var args;
  385. var func;
  386. var index = parserInput.i;
  387. // http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
  388. if (parserInput.peek(/^url\(/i)) {
  389. return;
  390. }
  391. parserInput.save();
  392. name = parserInput.$re(/^([\w-]+|%|~|progid:[\w.]+)\(/);
  393. if (!name) {
  394. parserInput.forget();
  395. return;
  396. }
  397. name = name[1];
  398. func = this.customFuncCall(name);
  399. if (func) {
  400. args = func.parse();
  401. if (args && func.stop) {
  402. parserInput.forget();
  403. return args;
  404. }
  405. }
  406. args = this.arguments(args);
  407. if (!parserInput.$char(')')) {
  408. parserInput.restore('Could not parse call arguments or missing \')\'');
  409. return;
  410. }
  411. parserInput.forget();
  412. return new (tree_1.default.Call)(name, args, index + currentIndex, fileInfo);
  413. },
  414. //
  415. // Parsing rules for functions with non-standard args, e.g.:
  416. //
  417. // boolean(not(2 > 1))
  418. //
  419. // This is a quick prototype, to be modified/improved when
  420. // more custom-parsed funcs come (e.g. `selector(...)`)
  421. //
  422. customFuncCall: function (name) {
  423. /* Ideally the table is to be moved out of here for faster perf.,
  424. but it's quite tricky since it relies on all these `parsers`
  425. and `expect` available only here */
  426. return {
  427. alpha: f(parsers.ieAlpha, true),
  428. boolean: f(condition),
  429. 'if': f(condition)
  430. }[name.toLowerCase()];
  431. function f(parse, stop) {
  432. return {
  433. parse: parse,
  434. stop: stop // when true - stop after parse() and return its result,
  435. // otherwise continue for plain args
  436. };
  437. }
  438. function condition() {
  439. return [expect(parsers.condition, 'expected condition')];
  440. }
  441. },
  442. arguments: function (prevArgs) {
  443. var argsComma = prevArgs || [];
  444. var argsSemiColon = [];
  445. var isSemiColonSeparated;
  446. var value;
  447. parserInput.save();
  448. while (true) {
  449. if (prevArgs) {
  450. prevArgs = false;
  451. }
  452. else {
  453. value = parsers.detachedRuleset() || this.assignment() || parsers.expression();
  454. if (!value) {
  455. break;
  456. }
  457. if (value.value && value.value.length == 1) {
  458. value = value.value[0];
  459. }
  460. argsComma.push(value);
  461. }
  462. if (parserInput.$char(',')) {
  463. continue;
  464. }
  465. if (parserInput.$char(';') || isSemiColonSeparated) {
  466. isSemiColonSeparated = true;
  467. value = (argsComma.length < 1) ? argsComma[0]
  468. : new tree_1.default.Value(argsComma);
  469. argsSemiColon.push(value);
  470. argsComma = [];
  471. }
  472. }
  473. parserInput.forget();
  474. return isSemiColonSeparated ? argsSemiColon : argsComma;
  475. },
  476. literal: function () {
  477. return this.dimension() ||
  478. this.color() ||
  479. this.quoted() ||
  480. this.unicodeDescriptor();
  481. },
  482. // Assignments are argument entities for calls.
  483. // They are present in ie filter properties as shown below.
  484. //
  485. // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
  486. //
  487. assignment: function () {
  488. var key;
  489. var value;
  490. parserInput.save();
  491. key = parserInput.$re(/^\w+(?=\s?=)/i);
  492. if (!key) {
  493. parserInput.restore();
  494. return;
  495. }
  496. if (!parserInput.$char('=')) {
  497. parserInput.restore();
  498. return;
  499. }
  500. value = parsers.entity();
  501. if (value) {
  502. parserInput.forget();
  503. return new (tree_1.default.Assignment)(key, value);
  504. }
  505. else {
  506. parserInput.restore();
  507. }
  508. },
  509. //
  510. // Parse url() tokens
  511. //
  512. // We use a specific rule for urls, because they don't really behave like
  513. // standard function calls. The difference is that the argument doesn't have
  514. // to be enclosed within a string, so it can't be parsed as an Expression.
  515. //
  516. url: function () {
  517. var value;
  518. var index = parserInput.i;
  519. parserInput.autoCommentAbsorb = false;
  520. if (!parserInput.$str('url(')) {
  521. parserInput.autoCommentAbsorb = true;
  522. return;
  523. }
  524. value = this.quoted() || this.variable() || this.property() ||
  525. parserInput.$re(/^(?:(?:\\[()'"])|[^()'"])+/) || '';
  526. parserInput.autoCommentAbsorb = true;
  527. expectChar(')');
  528. return new (tree_1.default.URL)((value.value !== undefined ||
  529. value instanceof tree_1.default.Variable ||
  530. value instanceof tree_1.default.Property) ?
  531. value : new (tree_1.default.Anonymous)(value, index), index + currentIndex, fileInfo);
  532. },
  533. //
  534. // A Variable entity, such as `@fink`, in
  535. //
  536. // width: @fink + 2px
  537. //
  538. // We use a different parser for variable definitions,
  539. // see `parsers.variable`.
  540. //
  541. variable: function () {
  542. var ch;
  543. var name;
  544. var index = parserInput.i;
  545. parserInput.save();
  546. if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^@@?[\w-]+/))) {
  547. ch = parserInput.currentChar();
  548. if (ch === '(' || ch === '[' && !parserInput.prevChar().match(/^\s/)) {
  549. // this may be a VariableCall lookup
  550. var result = parsers.variableCall(name);
  551. if (result) {
  552. parserInput.forget();
  553. return result;
  554. }
  555. }
  556. parserInput.forget();
  557. return new (tree_1.default.Variable)(name, index + currentIndex, fileInfo);
  558. }
  559. parserInput.restore();
  560. },
  561. // A variable entity using the protective {} e.g. @{var}
  562. variableCurly: function () {
  563. var curly;
  564. var index = parserInput.i;
  565. if (parserInput.currentChar() === '@' && (curly = parserInput.$re(/^@\{([\w-]+)\}/))) {
  566. return new (tree_1.default.Variable)("@" + curly[1], index + currentIndex, fileInfo);
  567. }
  568. },
  569. //
  570. // A Property accessor, such as `$color`, in
  571. //
  572. // background-color: $color
  573. //
  574. property: function () {
  575. var name;
  576. var index = parserInput.i;
  577. if (parserInput.currentChar() === '$' && (name = parserInput.$re(/^\$[\w-]+/))) {
  578. return new (tree_1.default.Property)(name, index + currentIndex, fileInfo);
  579. }
  580. },
  581. // A property entity useing the protective {} e.g. ${prop}
  582. propertyCurly: function () {
  583. var curly;
  584. var index = parserInput.i;
  585. if (parserInput.currentChar() === '$' && (curly = parserInput.$re(/^\$\{([\w-]+)\}/))) {
  586. return new (tree_1.default.Property)("$" + curly[1], index + currentIndex, fileInfo);
  587. }
  588. },
  589. //
  590. // A Hexadecimal color
  591. //
  592. // #4F3C2F
  593. //
  594. // `rgb` and `hsl` colors are parsed through the `entities.call` parser.
  595. //
  596. color: function () {
  597. var rgb;
  598. parserInput.save();
  599. if (parserInput.currentChar() === '#' && (rgb = parserInput.$re(/^#([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3,4})([\w.#[])?/))) {
  600. if (!rgb[2]) {
  601. parserInput.forget();
  602. return new (tree_1.default.Color)(rgb[1], undefined, rgb[0]);
  603. }
  604. }
  605. parserInput.restore();
  606. },
  607. colorKeyword: function () {
  608. parserInput.save();
  609. var autoCommentAbsorb = parserInput.autoCommentAbsorb;
  610. parserInput.autoCommentAbsorb = false;
  611. var k = parserInput.$re(/^[_A-Za-z-][_A-Za-z0-9-]+/);
  612. parserInput.autoCommentAbsorb = autoCommentAbsorb;
  613. if (!k) {
  614. parserInput.forget();
  615. return;
  616. }
  617. parserInput.restore();
  618. var color = tree_1.default.Color.fromKeyword(k);
  619. if (color) {
  620. parserInput.$str(k);
  621. return color;
  622. }
  623. },
  624. //
  625. // A Dimension, that is, a number and a unit
  626. //
  627. // 0.5em 95%
  628. //
  629. dimension: function () {
  630. if (parserInput.peekNotNumeric()) {
  631. return;
  632. }
  633. var value = parserInput.$re(/^([+-]?\d*\.?\d+)(%|[a-z_]+)?/i);
  634. if (value) {
  635. return new (tree_1.default.Dimension)(value[1], value[2]);
  636. }
  637. },
  638. //
  639. // A unicode descriptor, as is used in unicode-range
  640. //
  641. // U+0?? or U+00A1-00A9
  642. //
  643. unicodeDescriptor: function () {
  644. var ud;
  645. ud = parserInput.$re(/^U\+[0-9a-fA-F?]+(-[0-9a-fA-F?]+)?/);
  646. if (ud) {
  647. return new (tree_1.default.UnicodeDescriptor)(ud[0]);
  648. }
  649. },
  650. //
  651. // JavaScript code to be evaluated
  652. //
  653. // `window.location.href`
  654. //
  655. javascript: function () {
  656. var js;
  657. var index = parserInput.i;
  658. parserInput.save();
  659. var escape = parserInput.$char('~');
  660. var jsQuote = parserInput.$char('`');
  661. if (!jsQuote) {
  662. parserInput.restore();
  663. return;
  664. }
  665. js = parserInput.$re(/^[^`]*`/);
  666. if (js) {
  667. parserInput.forget();
  668. return new (tree_1.default.JavaScript)(js.substr(0, js.length - 1), Boolean(escape), index + currentIndex, fileInfo);
  669. }
  670. parserInput.restore('invalid javascript definition');
  671. }
  672. },
  673. //
  674. // The variable part of a variable definition. Used in the `rule` parser
  675. //
  676. // @fink:
  677. //
  678. variable: function () {
  679. var name;
  680. if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) {
  681. return name[1];
  682. }
  683. },
  684. //
  685. // Call a variable value to retrieve a detached ruleset
  686. // or a value from a detached ruleset's rules.
  687. //
  688. // @fink();
  689. // @fink;
  690. // color: @fink[@color];
  691. //
  692. variableCall: function (parsedName) {
  693. var lookups;
  694. var i = parserInput.i;
  695. var inValue = !!parsedName;
  696. var name = parsedName;
  697. parserInput.save();
  698. if (name || (parserInput.currentChar() === '@'
  699. && (name = parserInput.$re(/^(@[\w-]+)(\(\s*\))?/)))) {
  700. lookups = this.mixin.ruleLookups();
  701. if (!lookups && ((inValue && parserInput.$str('()') !== '()') || (name[2] !== '()'))) {
  702. parserInput.restore('Missing \'[...]\' lookup in variable call');
  703. return;
  704. }
  705. if (!inValue) {
  706. name = name[1];
  707. }
  708. var call = new tree_1.default.VariableCall(name, i, fileInfo);
  709. if (!inValue && parsers.end()) {
  710. parserInput.forget();
  711. return call;
  712. }
  713. else {
  714. parserInput.forget();
  715. return new tree_1.default.NamespaceValue(call, lookups, i, fileInfo);
  716. }
  717. }
  718. parserInput.restore();
  719. },
  720. //
  721. // extend syntax - used to extend selectors
  722. //
  723. extend: function (isRule) {
  724. var elements;
  725. var e;
  726. var index = parserInput.i;
  727. var option;
  728. var extendList;
  729. var extend;
  730. if (!parserInput.$str(isRule ? '&:extend(' : ':extend(')) {
  731. return;
  732. }
  733. do {
  734. option = null;
  735. elements = null;
  736. while (!(option = parserInput.$re(/^(all)(?=\s*(\)|,))/))) {
  737. e = this.element();
  738. if (!e) {
  739. break;
  740. }
  741. if (elements) {
  742. elements.push(e);
  743. }
  744. else {
  745. elements = [e];
  746. }
  747. }
  748. option = option && option[1];
  749. if (!elements) {
  750. error('Missing target selector for :extend().');
  751. }
  752. extend = new (tree_1.default.Extend)(new (tree_1.default.Selector)(elements), option, index + currentIndex, fileInfo);
  753. if (extendList) {
  754. extendList.push(extend);
  755. }
  756. else {
  757. extendList = [extend];
  758. }
  759. } while (parserInput.$char(','));
  760. expect(/^\)/);
  761. if (isRule) {
  762. expect(/^;/);
  763. }
  764. return extendList;
  765. },
  766. //
  767. // extendRule - used in a rule to extend all the parent selectors
  768. //
  769. extendRule: function () {
  770. return this.extend(true);
  771. },
  772. //
  773. // Mixins
  774. //
  775. mixin: {
  776. //
  777. // A Mixin call, with an optional argument list
  778. //
  779. // #mixins > .square(#fff);
  780. // #mixins.square(#fff);
  781. // .rounded(4px, black);
  782. // .button;
  783. //
  784. // We can lookup / return a value using the lookup syntax:
  785. //
  786. // color: #mixin.square(#fff)[@color];
  787. //
  788. // The `while` loop is there because mixins can be
  789. // namespaced, but we only support the child and descendant
  790. // selector for now.
  791. //
  792. call: function (inValue, getLookup) {
  793. var s = parserInput.currentChar();
  794. var important = false;
  795. var lookups;
  796. var index = parserInput.i;
  797. var elements;
  798. var args;
  799. var hasParens;
  800. if (s !== '.' && s !== '#') {
  801. return;
  802. }
  803. parserInput.save(); // stop us absorbing part of an invalid selector
  804. elements = this.elements();
  805. if (elements) {
  806. if (parserInput.$char('(')) {
  807. args = this.args(true).args;
  808. expectChar(')');
  809. hasParens = true;
  810. }
  811. if (getLookup !== false) {
  812. lookups = this.ruleLookups();
  813. }
  814. if (getLookup === true && !lookups) {
  815. parserInput.restore();
  816. return;
  817. }
  818. if (inValue && !lookups && !hasParens) {
  819. // This isn't a valid in-value mixin call
  820. parserInput.restore();
  821. return;
  822. }
  823. if (!inValue && parsers.important()) {
  824. important = true;
  825. }
  826. if (inValue || parsers.end()) {
  827. parserInput.forget();
  828. var mixin = new (tree_1.default.mixin.Call)(elements, args, index + currentIndex, fileInfo, !lookups && important);
  829. if (lookups) {
  830. return new tree_1.default.NamespaceValue(mixin, lookups);
  831. }
  832. else {
  833. return mixin;
  834. }
  835. }
  836. }
  837. parserInput.restore();
  838. },
  839. /**
  840. * Matching elements for mixins
  841. * (Start with . or # and can have > )
  842. */
  843. elements: function () {
  844. var elements;
  845. var e;
  846. var c;
  847. var elem;
  848. var elemIndex;
  849. var re = /^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/;
  850. while (true) {
  851. elemIndex = parserInput.i;
  852. e = parserInput.$re(re);
  853. if (!e) {
  854. break;
  855. }
  856. elem = new (tree_1.default.Element)(c, e, false, elemIndex + currentIndex, fileInfo);
  857. if (elements) {
  858. elements.push(elem);
  859. }
  860. else {
  861. elements = [elem];
  862. }
  863. c = parserInput.$char('>');
  864. }
  865. return elements;
  866. },
  867. args: function (isCall) {
  868. var entities = parsers.entities;
  869. var returner = { args: null, variadic: false };
  870. var expressions = [];
  871. var argsSemiColon = [];
  872. var argsComma = [];
  873. var isSemiColonSeparated;
  874. var expressionContainsNamed;
  875. var name;
  876. var nameLoop;
  877. var value;
  878. var arg;
  879. var expand;
  880. var hasSep = true;
  881. parserInput.save();
  882. while (true) {
  883. if (isCall) {
  884. arg = parsers.detachedRuleset() || parsers.expression();
  885. }
  886. else {
  887. parserInput.commentStore.length = 0;
  888. if (parserInput.$str('...')) {
  889. returner.variadic = true;
  890. if (parserInput.$char(';') && !isSemiColonSeparated) {
  891. isSemiColonSeparated = true;
  892. }
  893. (isSemiColonSeparated ? argsSemiColon : argsComma)
  894. .push({ variadic: true });
  895. break;
  896. }
  897. arg = entities.variable() || entities.property() || entities.literal() || entities.keyword() || this.call(true);
  898. }
  899. if (!arg || !hasSep) {
  900. break;
  901. }
  902. nameLoop = null;
  903. if (arg.throwAwayComments) {
  904. arg.throwAwayComments();
  905. }
  906. value = arg;
  907. var val = null;
  908. if (isCall) {
  909. // Variable
  910. if (arg.value && arg.value.length == 1) {
  911. val = arg.value[0];
  912. }
  913. }
  914. else {
  915. val = arg;
  916. }
  917. if (val && (val instanceof tree_1.default.Variable || val instanceof tree_1.default.Property)) {
  918. if (parserInput.$char(':')) {
  919. if (expressions.length > 0) {
  920. if (isSemiColonSeparated) {
  921. error('Cannot mix ; and , as delimiter types');
  922. }
  923. expressionContainsNamed = true;
  924. }
  925. value = parsers.detachedRuleset() || parsers.expression();
  926. if (!value) {
  927. if (isCall) {
  928. error('could not understand value for named argument');
  929. }
  930. else {
  931. parserInput.restore();
  932. returner.args = [];
  933. return returner;
  934. }
  935. }
  936. nameLoop = (name = val.name);
  937. }
  938. else if (parserInput.$str('...')) {
  939. if (!isCall) {
  940. returner.variadic = true;
  941. if (parserInput.$char(';') && !isSemiColonSeparated) {
  942. isSemiColonSeparated = true;
  943. }
  944. (isSemiColonSeparated ? argsSemiColon : argsComma)
  945. .push({ name: arg.name, variadic: true });
  946. break;
  947. }
  948. else {
  949. expand = true;
  950. }
  951. }
  952. else if (!isCall) {
  953. name = nameLoop = val.name;
  954. value = null;
  955. }
  956. }
  957. if (value) {
  958. expressions.push(value);
  959. }
  960. argsComma.push({ name: nameLoop, value: value, expand: expand });
  961. if (parserInput.$char(',')) {
  962. hasSep = true;
  963. continue;
  964. }
  965. hasSep = parserInput.$char(';') === ';';
  966. if (hasSep || isSemiColonSeparated) {
  967. if (expressionContainsNamed) {
  968. error('Cannot mix ; and , as delimiter types');
  969. }
  970. isSemiColonSeparated = true;
  971. if (expressions.length > 1) {
  972. value = new (tree_1.default.Value)(expressions);
  973. }
  974. argsSemiColon.push({ name: name, value: value, expand: expand });
  975. name = null;
  976. expressions = [];
  977. expressionContainsNamed = false;
  978. }
  979. }
  980. parserInput.forget();
  981. returner.args = isSemiColonSeparated ? argsSemiColon : argsComma;
  982. return returner;
  983. },
  984. //
  985. // A Mixin definition, with a list of parameters
  986. //
  987. // .rounded (@radius: 2px, @color) {
  988. // ...
  989. // }
  990. //
  991. // Until we have a finer grained state-machine, we have to
  992. // do a look-ahead, to make sure we don't have a mixin call.
  993. // See the `rule` function for more information.
  994. //
  995. // We start by matching `.rounded (`, and then proceed on to
  996. // the argument list, which has optional default values.
  997. // We store the parameters in `params`, with a `value` key,
  998. // if there is a value, such as in the case of `@radius`.
  999. //
  1000. // Once we've got our params list, and a closing `)`, we parse
  1001. // the `{...}` block.
  1002. //
  1003. definition: function () {
  1004. var name;
  1005. var params = [];
  1006. var match;
  1007. var ruleset;
  1008. var cond;
  1009. var variadic = false;
  1010. if ((parserInput.currentChar() !== '.' && parserInput.currentChar() !== '#') ||
  1011. parserInput.peek(/^[^{]*\}/)) {
  1012. return;
  1013. }
  1014. parserInput.save();
  1015. match = parserInput.$re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/);
  1016. if (match) {
  1017. name = match[1];
  1018. var argInfo = this.args(false);
  1019. params = argInfo.args;
  1020. variadic = argInfo.variadic;
  1021. // .mixincall("@{a}");
  1022. // looks a bit like a mixin definition..
  1023. // also
  1024. // .mixincall(@a: {rule: set;});
  1025. // so we have to be nice and restore
  1026. if (!parserInput.$char(')')) {
  1027. parserInput.restore('Missing closing \')\'');
  1028. return;
  1029. }
  1030. parserInput.commentStore.length = 0;
  1031. if (parserInput.$str('when')) { // Guard
  1032. cond = expect(parsers.conditions, 'expected condition');
  1033. }
  1034. ruleset = parsers.block();
  1035. if (ruleset) {
  1036. parserInput.forget();
  1037. return new (tree_1.default.mixin.Definition)(name, params, ruleset, cond, variadic);
  1038. }
  1039. else {
  1040. parserInput.restore();
  1041. }
  1042. }
  1043. else {
  1044. parserInput.restore();
  1045. }
  1046. },
  1047. ruleLookups: function () {
  1048. var rule;
  1049. var lookups = [];
  1050. if (parserInput.currentChar() !== '[') {
  1051. return;
  1052. }
  1053. while (true) {
  1054. parserInput.save();
  1055. rule = this.lookupValue();
  1056. if (!rule && rule !== '') {
  1057. parserInput.restore();
  1058. break;
  1059. }
  1060. lookups.push(rule);
  1061. parserInput.forget();
  1062. }
  1063. if (lookups.length > 0) {
  1064. return lookups;
  1065. }
  1066. },
  1067. lookupValue: function () {
  1068. parserInput.save();
  1069. if (!parserInput.$char('[')) {
  1070. parserInput.restore();
  1071. return;
  1072. }
  1073. var name = parserInput.$re(/^(?:[@$]{0,2})[_a-zA-Z0-9-]*/);
  1074. if (!parserInput.$char(']')) {
  1075. parserInput.restore();
  1076. return;
  1077. }
  1078. if (name || name === '') {
  1079. parserInput.forget();
  1080. return name;
  1081. }
  1082. parserInput.restore();
  1083. }
  1084. },
  1085. //
  1086. // Entities are the smallest recognized token,
  1087. // and can be found inside a rule's value.
  1088. //
  1089. entity: function () {
  1090. var entities = this.entities;
  1091. return this.comment() || entities.literal() || entities.variable() || entities.url() ||
  1092. entities.property() || entities.call() || entities.keyword() || this.mixin.call(true) ||
  1093. entities.javascript();
  1094. },
  1095. //
  1096. // A Declaration terminator. Note that we use `peek()` to check for '}',
  1097. // because the `block` rule will be expecting it, but we still need to make sure
  1098. // it's there, if ';' was omitted.
  1099. //
  1100. end: function () {
  1101. return parserInput.$char(';') || parserInput.peek('}');
  1102. },
  1103. //
  1104. // IE's alpha function
  1105. //
  1106. // alpha(opacity=88)
  1107. //
  1108. ieAlpha: function () {
  1109. var value;
  1110. // http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
  1111. if (!parserInput.$re(/^opacity=/i)) {
  1112. return;
  1113. }
  1114. value = parserInput.$re(/^\d+/);
  1115. if (!value) {
  1116. value = expect(parsers.entities.variable, 'Could not parse alpha');
  1117. value = "@{" + value.name.slice(1) + "}";
  1118. }
  1119. expectChar(')');
  1120. return new tree_1.default.Quoted('', "alpha(opacity=" + value + ")");
  1121. },
  1122. //
  1123. // A Selector Element
  1124. //
  1125. // div
  1126. // + h1
  1127. // #socks
  1128. // input[type="text"]
  1129. //
  1130. // Elements are the building blocks for Selectors,
  1131. // they are made out of a `Combinator` (see combinator rule),
  1132. // and an element name, such as a tag a class, or `*`.
  1133. //
  1134. element: function () {
  1135. var e;
  1136. var c;
  1137. var v;
  1138. var index = parserInput.i;
  1139. c = this.combinator();
  1140. e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) ||
  1141. // eslint-disable-next-line no-control-regex
  1142. parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
  1143. parserInput.$char('*') || parserInput.$char('&') || this.attribute() ||
  1144. parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[.#:](?=@)/) ||
  1145. this.entities.variableCurly();
  1146. if (!e) {
  1147. parserInput.save();
  1148. if (parserInput.$char('(')) {
  1149. if ((v = this.selector(false)) && parserInput.$char(')')) {
  1150. e = new (tree_1.default.Paren)(v);
  1151. parserInput.forget();
  1152. }
  1153. else {
  1154. parserInput.restore('Missing closing \')\'');
  1155. }
  1156. }
  1157. else {
  1158. parserInput.forget();
  1159. }
  1160. }
  1161. if (e) {
  1162. return new (tree_1.default.Element)(c, e, e instanceof tree_1.default.Variable, index + currentIndex, fileInfo);
  1163. }
  1164. },
  1165. //
  1166. // Combinators combine elements together, in a Selector.
  1167. //
  1168. // Because our parser isn't white-space sensitive, special care
  1169. // has to be taken, when parsing the descendant combinator, ` `,
  1170. // as it's an empty space. We have to check the previous character
  1171. // in the input, to see if it's a ` ` character. More info on how
  1172. // we deal with this in *combinator.js*.
  1173. //
  1174. combinator: function () {
  1175. var c = parserInput.currentChar();
  1176. if (c === '/') {
  1177. parserInput.save();
  1178. var slashedCombinator = parserInput.$re(/^\/[a-z]+\//i);
  1179. if (slashedCombinator) {
  1180. parserInput.forget();
  1181. return new (tree_1.default.Combinator)(slashedCombinator);
  1182. }
  1183. parserInput.restore();
  1184. }
  1185. if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') {
  1186. parserInput.i++;
  1187. if (c === '^' && parserInput.currentChar() === '^') {
  1188. c = '^^';
  1189. parserInput.i++;
  1190. }
  1191. while (parserInput.isWhitespace()) {
  1192. parserInput.i++;
  1193. }
  1194. return new (tree_1.default.Combinator)(c);
  1195. }
  1196. else if (parserInput.isWhitespace(-1)) {
  1197. return new (tree_1.default.Combinator)(' ');
  1198. }
  1199. else {
  1200. return new (tree_1.default.Combinator)(null);
  1201. }
  1202. },
  1203. //
  1204. // A CSS Selector
  1205. // with less extensions e.g. the ability to extend and guard
  1206. //
  1207. // .class > div + h1
  1208. // li a:hover
  1209. //
  1210. // Selectors are made out of one or more Elements, see above.
  1211. //
  1212. selector: function (isLess) {
  1213. var index = parserInput.i;
  1214. var elements;
  1215. var extendList;
  1216. var c;
  1217. var e;
  1218. var allExtends;
  1219. var when;
  1220. var condition;
  1221. isLess = isLess !== false;
  1222. while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$str('when'))) || (e = this.element())) {
  1223. if (when) {
  1224. condition = expect(this.conditions, 'expected condition');
  1225. }
  1226. else if (condition) {
  1227. error('CSS guard can only be used at the end of selector');
  1228. }
  1229. else if (extendList) {
  1230. if (allExtends) {
  1231. allExtends = allExtends.concat(extendList);
  1232. }
  1233. else {
  1234. allExtends = extendList;
  1235. }
  1236. }
  1237. else {
  1238. if (allExtends) {
  1239. error('Extend can only be used at the end of selector');
  1240. }
  1241. c = parserInput.currentChar();
  1242. if (elements) {
  1243. elements.push(e);
  1244. }
  1245. else {
  1246. elements = [e];
  1247. }
  1248. e = null;
  1249. }
  1250. if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
  1251. break;
  1252. }
  1253. }
  1254. if (elements) {
  1255. return new (tree_1.default.Selector)(elements, allExtends, condition, index + currentIndex, fileInfo);
  1256. }
  1257. if (allExtends) {
  1258. error('Extend must be used to extend a selector, it cannot be used on its own');
  1259. }
  1260. },
  1261. selectors: function () {
  1262. var s;
  1263. var selectors;
  1264. while (true) {
  1265. s = this.selector();
  1266. if (!s) {
  1267. break;
  1268. }
  1269. if (selectors) {
  1270. selectors.push(s);
  1271. }
  1272. else {
  1273. selectors = [s];
  1274. }
  1275. parserInput.commentStore.length = 0;
  1276. if (s.condition && selectors.length > 1) {
  1277. error('Guards are only currently allowed on a single selector.');
  1278. }
  1279. if (!parserInput.$char(',')) {
  1280. break;
  1281. }
  1282. if (s.condition) {
  1283. error('Guards are only currently allowed on a single selector.');
  1284. }
  1285. parserInput.commentStore.length = 0;
  1286. }
  1287. return selectors;
  1288. },
  1289. attribute: function () {
  1290. if (!parserInput.$char('[')) {
  1291. return;
  1292. }
  1293. var entities = this.entities;
  1294. var key;
  1295. var val;
  1296. var op;
  1297. //
  1298. // case-insensitive flag
  1299. // e.g. [attr operator value i]
  1300. //
  1301. var cif;
  1302. if (!(key = entities.variableCurly())) {
  1303. key = expect(/^(?:[_A-Za-z0-9-*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
  1304. }
  1305. op = parserInput.$re(/^[|~*$^]?=/);
  1306. if (op) {
  1307. val = entities.quoted() || parserInput.$re(/^[0-9]+%/) || parserInput.$re(/^[\w-]+/) || entities.variableCurly();
  1308. if (val) {
  1309. cif = parserInput.$re(/^[iIsS]/);
  1310. }
  1311. }
  1312. expectChar(']');
  1313. return new (tree_1.default.Attribute)(key, op, val, cif);
  1314. },
  1315. //
  1316. // The `block` rule is used by `ruleset` and `mixin.definition`.
  1317. // It's a wrapper around the `primary` rule, with added `{}`.
  1318. //
  1319. block: function () {
  1320. var content;
  1321. if (parserInput.$char('{') && (content = this.primary()) && parserInput.$char('}')) {
  1322. return content;
  1323. }
  1324. },
  1325. blockRuleset: function () {
  1326. var block = this.block();
  1327. if (block) {
  1328. block = new tree_1.default.Ruleset(null, block);
  1329. }
  1330. return block;
  1331. },
  1332. detachedRuleset: function () {
  1333. var argInfo;
  1334. var params;
  1335. var variadic;
  1336. parserInput.save();
  1337. if (parserInput.$re(/^[.#]\(/)) {
  1338. /**
  1339. * DR args currently only implemented for each() function, and not
  1340. * yet settable as `@dr: #(@arg) {}`
  1341. * This should be done when DRs are merged with mixins.
  1342. * See: https://github.com/less/less-meta/issues/16
  1343. */
  1344. argInfo = this.mixin.args(false);
  1345. params = argInfo.args;
  1346. variadic = argInfo.variadic;
  1347. if (!parserInput.$char(')')) {
  1348. parserInput.restore();
  1349. return;
  1350. }
  1351. }
  1352. var blockRuleset = this.blockRuleset();
  1353. if (blockRuleset) {
  1354. parserInput.forget();
  1355. if (params) {
  1356. return new tree_1.default.mixin.Definition(null, params, blockRuleset, null, variadic);
  1357. }
  1358. return new tree_1.default.DetachedRuleset(blockRuleset);
  1359. }
  1360. parserInput.restore();
  1361. },
  1362. //
  1363. // div, .class, body > p {...}
  1364. //
  1365. ruleset: function () {
  1366. var selectors;
  1367. var rules;
  1368. var debugInfo;
  1369. parserInput.save();
  1370. if (context.dumpLineNumbers) {
  1371. debugInfo = getDebugInfo(parserInput.i);
  1372. }
  1373. selectors = this.selectors();
  1374. if (selectors && (rules = this.block())) {
  1375. parserInput.forget();
  1376. var ruleset = new (tree_1.default.Ruleset)(selectors, rules, context.strictImports);
  1377. if (context.dumpLineNumbers) {
  1378. ruleset.debugInfo = debugInfo;
  1379. }
  1380. return ruleset;
  1381. }
  1382. else {
  1383. parserInput.restore();
  1384. }
  1385. },
  1386. declaration: function () {
  1387. var name;
  1388. var value;
  1389. var index = parserInput.i;
  1390. var hasDR;
  1391. var c = parserInput.currentChar();
  1392. var important;
  1393. var merge;
  1394. var isVariable;
  1395. if (c === '.' || c === '#' || c === '&' || c === ':') {
  1396. return;
  1397. }
  1398. parserInput.save();
  1399. name = this.variable() || this.ruleProperty();
  1400. if (name) {
  1401. isVariable = typeof name === 'string';
  1402. if (isVariable) {
  1403. value = this.detachedRuleset();
  1404. if (value) {
  1405. hasDR = true;
  1406. }
  1407. }
  1408. parserInput.commentStore.length = 0;
  1409. if (!value) {
  1410. // a name returned by this.ruleProperty() is always an array of the form:
  1411. // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
  1412. // where each item is a tree.Keyword or tree.Variable
  1413. merge = !isVariable && name.length > 1 && name.pop().value;
  1414. // Custom property values get permissive parsing
  1415. if (name[0].value && name[0].value.slice(0, 2) === '--') {
  1416. value = this.permissiveValue(/[;}]/);
  1417. }
  1418. // Try to store values as anonymous
  1419. // If we need the value later we'll re-parse it in ruleset.parseValue
  1420. else {
  1421. value = this.anonymousValue();
  1422. }
  1423. if (value) {
  1424. parserInput.forget();
  1425. // anonymous values absorb the end ';' which is required for them to work
  1426. return new (tree_1.default.Declaration)(name, value, false, merge, index + currentIndex, fileInfo);
  1427. }
  1428. if (!value) {
  1429. value = this.value();
  1430. }
  1431. if (value) {
  1432. important = this.important();
  1433. }
  1434. else if (isVariable) {
  1435. // As a last resort, try permissiveValue
  1436. value = this.permissiveValue();
  1437. }
  1438. }
  1439. if (value && (this.end() || hasDR)) {
  1440. parserInput.forget();
  1441. return new (tree_1.default.Declaration)(name, value, important, merge, index + currentIndex, fileInfo);
  1442. }
  1443. else {
  1444. parserInput.restore();
  1445. }
  1446. }
  1447. else {
  1448. parserInput.restore();
  1449. }
  1450. },
  1451. anonymousValue: function () {
  1452. var index = parserInput.i;
  1453. var match = parserInput.$re(/^([^.#@$+/'"*`(;{}-]*);/);
  1454. if (match) {
  1455. return new (tree_1.default.Anonymous)(match[1], index + currentIndex);
  1456. }
  1457. },
  1458. /**
  1459. * Used for custom properties, at-rules, and variables (as fallback)
  1460. * Parses almost anything inside of {} [] () "" blocks
  1461. * until it reaches outer-most tokens.
  1462. *
  1463. * First, it will try to parse comments and entities to reach
  1464. * the end. This is mostly like the Expression parser except no
  1465. * math is allowed.
  1466. */
  1467. permissiveValue: function (untilTokens) {
  1468. var i;
  1469. var e;
  1470. var done;
  1471. var value;
  1472. var tok = untilTokens || ';';
  1473. var index = parserInput.i;
  1474. var result = [];
  1475. function testCurrentChar() {
  1476. var char = parserInput.currentChar();
  1477. if (typeof tok === 'string') {
  1478. return char === tok;
  1479. }
  1480. else {
  1481. return tok.test(char);
  1482. }
  1483. }
  1484. if (testCurrentChar()) {
  1485. return;
  1486. }
  1487. value = [];
  1488. do {
  1489. e = this.comment();
  1490. if (e) {
  1491. value.push(e);
  1492. continue;
  1493. }
  1494. e = this.entity();
  1495. if (e) {
  1496. value.push(e);
  1497. }
  1498. } while (e);
  1499. done = testCurrentChar();
  1500. if (value.length > 0) {
  1501. value = new (tree_1.default.Expression)(value);
  1502. if (done) {
  1503. return value;
  1504. }
  1505. else {
  1506. result.push(value);
  1507. }
  1508. // Preserve space before $parseUntil as it will not
  1509. if (parserInput.prevChar() === ' ') {
  1510. result.push(new tree_1.default.Anonymous(' ', index));
  1511. }
  1512. }
  1513. parserInput.save();
  1514. value = parserInput.$parseUntil(tok);
  1515. if (value) {
  1516. if (typeof value === 'string') {
  1517. error("Expected '" + value + "'", 'Parse');
  1518. }
  1519. if (value.length === 1 && value[0] === ' ') {
  1520. parserInput.forget();
  1521. return new tree_1.default.Anonymous('', index);
  1522. }
  1523. var item = void 0;
  1524. for (i = 0; i < value.length; i++) {
  1525. item = value[i];
  1526. if (Array.isArray(item)) {
  1527. // Treat actual quotes as normal quoted values
  1528. result.push(new tree_1.default.Quoted(item[0], item[1], true, index, fileInfo));
  1529. }
  1530. else {
  1531. if (i === value.length - 1) {
  1532. item = item.trim();
  1533. }
  1534. // Treat like quoted values, but replace vars like unquoted expressions
  1535. var quote = new tree_1.default.Quoted('\'', item, true, index, fileInfo);
  1536. quote.variableRegex = /@([\w-]+)/g;
  1537. quote.propRegex = /\$([\w-]+)/g;
  1538. result.push(quote);
  1539. }
  1540. }
  1541. parserInput.forget();
  1542. return new tree_1.default.Expression(result, true);
  1543. }
  1544. parserInput.restore();
  1545. },
  1546. //
  1547. // An @import atrule
  1548. //
  1549. // @import "lib";
  1550. //
  1551. // Depending on our environment, importing is done differently:
  1552. // In the browser, it's an XHR request, in Node, it would be a
  1553. // file-system operation. The function used for importing is
  1554. // stored in `import`, which we pass to the Import constructor.
  1555. //
  1556. 'import': function () {
  1557. var path;
  1558. var features;
  1559. var index = parserInput.i;
  1560. var dir = parserInput.$re(/^@import\s+/);
  1561. if (dir) {
  1562. var options = (dir ? this.importOptions() : null) || {};
  1563. if ((path = this.entities.quoted() || this.entities.url())) {
  1564. features = this.mediaFeatures({});
  1565. if (!parserInput.$char(';')) {
  1566. parserInput.i = index;
  1567. error('missing semi-colon or unrecognised media features on import');
  1568. }
  1569. features = features && new (tree_1.default.Value)(features);
  1570. return new (tree_1.default.Import)(path, features, options, index + currentIndex, fileInfo);
  1571. }
  1572. else {
  1573. parserInput.i = index;
  1574. error('malformed import statement');
  1575. }
  1576. }
  1577. },
  1578. importOptions: function () {
  1579. var o;
  1580. var options = {};
  1581. var optionName;
  1582. var value;
  1583. // list of options, surrounded by parens
  1584. if (!parserInput.$char('(')) {
  1585. return null;
  1586. }
  1587. do {
  1588. o = this.importOption();
  1589. if (o) {
  1590. optionName = o;
  1591. value = true;
  1592. switch (optionName) {
  1593. case 'css':
  1594. optionName = 'less';
  1595. value = false;
  1596. break;
  1597. case 'once':
  1598. optionName = 'multiple';
  1599. value = false;
  1600. break;
  1601. }
  1602. options[optionName] = value;
  1603. if (!parserInput.$char(',')) {
  1604. break;
  1605. }
  1606. }
  1607. } while (o);
  1608. expectChar(')');
  1609. return options;
  1610. },
  1611. importOption: function () {
  1612. var opt = parserInput.$re(/^(less|css|multiple|once|inline|reference|optional)/);
  1613. if (opt) {
  1614. return opt[1];
  1615. }
  1616. },
  1617. mediaFeature: function (syntaxOptions) {
  1618. var entities = this.entities;
  1619. var nodes = [];
  1620. var e;
  1621. var p;
  1622. var rangeP;
  1623. parserInput.save();
  1624. do {
  1625. e = entities.keyword() || entities.variable() || entities.mixinLookup();
  1626. if (e) {
  1627. nodes.push(e);
  1628. }
  1629. else if (parserInput.$char('(')) {
  1630. p = this.property();
  1631. parserInput.save();
  1632. if (!p && syntaxOptions.queryInParens && parserInput.$re(/^[0-9a-z-]*\s*([<>]=|<=|>=|[<>]|=)/)) {
  1633. parserInput.restore();
  1634. p = this.condition();
  1635. parserInput.save();
  1636. rangeP = this.atomicCondition(null, p.rvalue);
  1637. if (!rangeP) {
  1638. parserInput.restore();
  1639. }
  1640. }
  1641. else {
  1642. parserInput.restore();
  1643. e = this.value();
  1644. }
  1645. if (parserInput.$char(')')) {
  1646. if (p && !e) {
  1647. nodes.push(new (tree_1.default.Paren)(new (tree_1.default.QueryInParens)(p.op, p.lvalue, p.rvalue, rangeP ? rangeP.op : null, rangeP ? rangeP.rvalue : null, p._index)));
  1648. e = p;
  1649. }
  1650. else if (p && e) {
  1651. nodes.push(new (tree_1.default.Paren)(new (tree_1.default.Declaration)(p, e, null, null, parserInput.i + currentIndex, fileInfo, true)));
  1652. }
  1653. else if (e) {
  1654. nodes.push(new (tree_1.default.Paren)(e));
  1655. }
  1656. else {
  1657. error('badly formed media feature definition');
  1658. }
  1659. }
  1660. else {
  1661. error('Missing closing \')\'', 'Parse');
  1662. }
  1663. }
  1664. } while (e);
  1665. parserInput.forget();
  1666. if (nodes.length > 0) {
  1667. return new (tree_1.default.Expression)(nodes);
  1668. }
  1669. },
  1670. mediaFeatures: function (syntaxOptions) {
  1671. var entities = this.entities;
  1672. var features = [];
  1673. var e;
  1674. do {
  1675. e = this.mediaFeature(syntaxOptions);
  1676. if (e) {
  1677. features.push(e);
  1678. if (!parserInput.$char(',')) {
  1679. break;
  1680. }
  1681. }
  1682. else {
  1683. e = entities.variable() || entities.mixinLookup();
  1684. if (e) {
  1685. features.push(e);
  1686. if (!parserInput.$char(',')) {
  1687. break;
  1688. }
  1689. }
  1690. }
  1691. } while (e);
  1692. return features.length > 0 ? features : null;
  1693. },
  1694. prepareAndGetNestableAtRule: function (treeType, index, debugInfo, syntaxOptions) {
  1695. var features = this.mediaFeatures(syntaxOptions);
  1696. var rules = this.block();
  1697. if (!rules) {
  1698. error('media definitions require block statements after any features');
  1699. }
  1700. parserInput.forget();
  1701. var atRule = new (treeType)(rules, features, index + currentIndex, fileInfo);
  1702. if (context.dumpLineNumbers) {
  1703. atRule.debugInfo = debugInfo;
  1704. }
  1705. return atRule;
  1706. },
  1707. nestableAtRule: function () {
  1708. var debugInfo;
  1709. var index = parserInput.i;
  1710. if (context.dumpLineNumbers) {
  1711. debugInfo = getDebugInfo(index);
  1712. }
  1713. parserInput.save();
  1714. if (parserInput.$peekChar('@')) {
  1715. if (parserInput.$str('@media')) {
  1716. return this.prepareAndGetNestableAtRule(tree_1.default.Media, index, debugInfo, atrule_syntax_1.MediaSyntaxOptions);
  1717. }
  1718. if (parserInput.$str('@container')) {
  1719. return this.prepareAndGetNestableAtRule(tree_1.default.Container, index, debugInfo, atrule_syntax_1.ContainerSyntaxOptions);
  1720. }
  1721. }
  1722. parserInput.restore();
  1723. },
  1724. //
  1725. // A @plugin directive, used to import plugins dynamically.
  1726. //
  1727. // @plugin (args) "lib";
  1728. //
  1729. plugin: function () {
  1730. var path;
  1731. var args;
  1732. var options;
  1733. var index = parserInput.i;
  1734. var dir = parserInput.$re(/^@plugin\s+/);
  1735. if (dir) {
  1736. args = this.pluginArgs();
  1737. if (args) {
  1738. options = {
  1739. pluginArgs: args,
  1740. isPlugin: true
  1741. };
  1742. }
  1743. else {
  1744. options = { isPlugin: true };
  1745. }
  1746. if ((path = this.entities.quoted() || this.entities.url())) {
  1747. if (!parserInput.$char(';')) {
  1748. parserInput.i = index;
  1749. error('missing semi-colon on @plugin');
  1750. }
  1751. return new (tree_1.default.Import)(path, null, options, index + currentIndex, fileInfo);
  1752. }
  1753. else {
  1754. parserInput.i = index;
  1755. error('malformed @plugin statement');
  1756. }
  1757. }
  1758. },
  1759. pluginArgs: function () {
  1760. // list of options, surrounded by parens
  1761. parserInput.save();
  1762. if (!parserInput.$char('(')) {
  1763. parserInput.restore();
  1764. return null;
  1765. }
  1766. var args = parserInput.$re(/^\s*([^);]+)\)\s*/);
  1767. if (args[1]) {
  1768. parserInput.forget();
  1769. return args[1].trim();
  1770. }
  1771. else {
  1772. parserInput.restore();
  1773. return null;
  1774. }
  1775. },
  1776. //
  1777. // A CSS AtRule
  1778. //
  1779. // @charset "utf-8";
  1780. //
  1781. atrule: function () {
  1782. var index = parserInput.i;
  1783. var name;
  1784. var value;
  1785. var rules;
  1786. var nonVendorSpecificName;
  1787. var hasIdentifier;
  1788. var hasExpression;
  1789. var hasUnknown;
  1790. var hasBlock = true;
  1791. var isRooted = true;
  1792. if (parserInput.currentChar() !== '@') {
  1793. return;
  1794. }
  1795. value = this['import']() || this.plugin() || this.nestableAtRule();
  1796. if (value) {
  1797. return value;
  1798. }
  1799. parserInput.save();
  1800. name = parserInput.$re(/^@[a-z-]+/);
  1801. if (!name) {
  1802. return;
  1803. }
  1804. nonVendorSpecificName = name;
  1805. if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
  1806. nonVendorSpecificName = "@" + name.slice(name.indexOf('-', 2) + 1);
  1807. }
  1808. switch (nonVendorSpecificName) {
  1809. case '@charset':
  1810. hasIdentifier = true;
  1811. hasBlock = false;
  1812. break;
  1813. case '@namespace':
  1814. hasExpression = true;
  1815. hasBlock = false;
  1816. break;
  1817. case '@keyframes':
  1818. case '@counter-style':
  1819. hasIdentifier = true;
  1820. break;
  1821. case '@document':
  1822. case '@supports':
  1823. hasUnknown = true;
  1824. isRooted = false;
  1825. break;
  1826. default:
  1827. hasUnknown = true;
  1828. break;
  1829. }
  1830. parserInput.commentStore.length = 0;
  1831. if (hasIdentifier) {
  1832. value = this.entity();
  1833. if (!value) {
  1834. error("expected " + name + " identifier");
  1835. }
  1836. }
  1837. else if (hasExpression) {
  1838. value = this.expression();
  1839. if (!value) {
  1840. error("expected " + name + " expression");
  1841. }
  1842. }
  1843. else if (hasUnknown) {
  1844. value = this.permissiveValue(/^[{;]/);
  1845. hasBlock = (parserInput.currentChar() === '{');
  1846. if (!value) {
  1847. if (!hasBlock && parserInput.currentChar() !== ';') {
  1848. error(name + " rule is missing block or ending semi-colon");
  1849. }
  1850. }
  1851. else if (!value.value) {
  1852. value = null;
  1853. }
  1854. }
  1855. if (hasBlock) {
  1856. rules = this.blockRuleset();
  1857. }
  1858. if (rules || (!hasBlock && value && parserInput.$char(';'))) {
  1859. parserInput.forget();
  1860. return new (tree_1.default.AtRule)(name, value, rules, index + currentIndex, fileInfo, context.dumpLineNumbers ? getDebugInfo(index) : null, isRooted);
  1861. }
  1862. parserInput.restore('at-rule options not recognised');
  1863. },
  1864. //
  1865. // A Value is a comma-delimited list of Expressions
  1866. //
  1867. // font-family: Baskerville, Georgia, serif;
  1868. //
  1869. // In a Rule, a Value represents everything after the `:`,
  1870. // and before the `;`.
  1871. //
  1872. value: function () {
  1873. var e;
  1874. var expressions = [];
  1875. var index = parserInput.i;
  1876. do {
  1877. e = this.expression();
  1878. if (e) {
  1879. expressions.push(e);
  1880. if (!parserInput.$char(',')) {
  1881. break;
  1882. }
  1883. }
  1884. } while (e);
  1885. if (expressions.length > 0) {
  1886. return new (tree_1.default.Value)(expressions, index + currentIndex);
  1887. }
  1888. },
  1889. important: function () {
  1890. if (parserInput.currentChar() === '!') {
  1891. return parserInput.$re(/^! *important/);
  1892. }
  1893. },
  1894. sub: function () {
  1895. var a;
  1896. var e;
  1897. parserInput.save();
  1898. if (parserInput.$char('(')) {
  1899. a = this.addition();
  1900. if (a && parserInput.$char(')')) {
  1901. parserInput.forget();
  1902. e = new (tree_1.default.Expression)([a]);
  1903. e.parens = true;
  1904. return e;
  1905. }
  1906. parserInput.restore('Expected \')\'');
  1907. return;
  1908. }
  1909. parserInput.restore();
  1910. },
  1911. multiplication: function () {
  1912. var m;
  1913. var a;
  1914. var op;
  1915. var operation;
  1916. var isSpaced;
  1917. m = this.operand();
  1918. if (m) {
  1919. isSpaced = parserInput.isWhitespace(-1);
  1920. while (true) {
  1921. if (parserInput.peek(/^\/[*/]/)) {
  1922. break;
  1923. }
  1924. parserInput.save();
  1925. op = parserInput.$char('/') || parserInput.$char('*') || parserInput.$str('./');
  1926. if (!op) {
  1927. parserInput.forget();
  1928. break;
  1929. }
  1930. a = this.operand();
  1931. if (!a) {
  1932. parserInput.restore();
  1933. break;
  1934. }
  1935. parserInput.forget();
  1936. m.parensInOp = true;
  1937. a.parensInOp = true;
  1938. operation = new (tree_1.default.Operation)(op, [operation || m, a], isSpaced);
  1939. isSpaced = parserInput.isWhitespace(-1);
  1940. }
  1941. return operation || m;
  1942. }
  1943. },
  1944. addition: function () {
  1945. var m;
  1946. var a;
  1947. var op;
  1948. var operation;
  1949. var isSpaced;
  1950. m = this.multiplication();
  1951. if (m) {
  1952. isSpaced = parserInput.isWhitespace(-1);
  1953. while (true) {
  1954. op = parserInput.$re(/^[-+]\s+/) || (!isSpaced && (parserInput.$char('+') || parserInput.$char('-')));
  1955. if (!op) {
  1956. break;
  1957. }
  1958. a = this.multiplication();
  1959. if (!a) {
  1960. break;
  1961. }
  1962. m.parensInOp = true;
  1963. a.parensInOp = true;
  1964. operation = new (tree_1.default.Operation)(op, [operation || m, a], isSpaced);
  1965. isSpaced = parserInput.isWhitespace(-1);
  1966. }
  1967. return operation || m;
  1968. }
  1969. },
  1970. conditions: function () {
  1971. var a;
  1972. var b;
  1973. var index = parserInput.i;
  1974. var condition;
  1975. a = this.condition(true);
  1976. if (a) {
  1977. while (true) {
  1978. if (!parserInput.peek(/^,\s*(not\s*)?\(/) || !parserInput.$char(',')) {
  1979. break;
  1980. }
  1981. b = this.condition(true);
  1982. if (!b) {
  1983. break;
  1984. }
  1985. condition = new (tree_1.default.Condition)('or', condition || a, b, index + currentIndex);
  1986. }
  1987. return condition || a;
  1988. }
  1989. },
  1990. condition: function (needsParens) {
  1991. var result;
  1992. var logical;
  1993. var next;
  1994. function or() {
  1995. return parserInput.$str('or');
  1996. }
  1997. result = this.conditionAnd(needsParens);
  1998. if (!result) {
  1999. return;
  2000. }
  2001. logical = or();
  2002. if (logical) {
  2003. next = this.condition(needsParens);
  2004. if (next) {
  2005. result = new (tree_1.default.Condition)(logical, result, next);
  2006. }
  2007. else {
  2008. return;
  2009. }
  2010. }
  2011. return result;
  2012. },
  2013. conditionAnd: function (needsParens) {
  2014. var result;
  2015. var logical;
  2016. var next;
  2017. var self = this;
  2018. function insideCondition() {
  2019. var cond = self.negatedCondition(needsParens) || self.parenthesisCondition(needsParens);
  2020. if (!cond && !needsParens) {
  2021. return self.atomicCondition(needsParens);
  2022. }
  2023. return cond;
  2024. }
  2025. function and() {
  2026. return parserInput.$str('and');
  2027. }
  2028. result = insideCondition();
  2029. if (!result) {
  2030. return;
  2031. }
  2032. logical = and();
  2033. if (logical) {
  2034. next = this.conditionAnd(needsParens);
  2035. if (next) {
  2036. result = new (tree_1.default.Condition)(logical, result, next);
  2037. }
  2038. else {
  2039. return;
  2040. }
  2041. }
  2042. return result;
  2043. },
  2044. negatedCondition: function (needsParens) {
  2045. if (parserInput.$str('not')) {
  2046. var result = this.parenthesisCondition(needsParens);
  2047. if (result) {
  2048. result.negate = !result.negate;
  2049. }
  2050. return result;
  2051. }
  2052. },
  2053. parenthesisCondition: function (needsParens) {
  2054. function tryConditionFollowedByParenthesis(me) {
  2055. var body;
  2056. parserInput.save();
  2057. body = me.condition(needsParens);
  2058. if (!body) {
  2059. parserInput.restore();
  2060. return;
  2061. }
  2062. if (!parserInput.$char(')')) {
  2063. parserInput.restore();
  2064. return;
  2065. }
  2066. parserInput.forget();
  2067. return body;
  2068. }
  2069. var body;
  2070. parserInput.save();
  2071. if (!parserInput.$str('(')) {
  2072. parserInput.restore();
  2073. return;
  2074. }
  2075. body = tryConditionFollowedByParenthesis(this);
  2076. if (body) {
  2077. parserInput.forget();
  2078. return body;
  2079. }
  2080. body = this.atomicCondition(needsParens);
  2081. if (!body) {
  2082. parserInput.restore();
  2083. return;
  2084. }
  2085. if (!parserInput.$char(')')) {
  2086. parserInput.restore("expected ')' got '" + parserInput.currentChar() + "'");
  2087. return;
  2088. }
  2089. parserInput.forget();
  2090. return body;
  2091. },
  2092. atomicCondition: function (needsParens, preparsedCond) {
  2093. var entities = this.entities;
  2094. var index = parserInput.i;
  2095. var a;
  2096. var b;
  2097. var c;
  2098. var op;
  2099. var cond = (function () {
  2100. return this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
  2101. }).bind(this);
  2102. if (preparsedCond) {
  2103. a = preparsedCond;
  2104. }
  2105. else {
  2106. a = cond();
  2107. }
  2108. if (a) {
  2109. if (parserInput.$char('>')) {
  2110. if (parserInput.$char('=')) {
  2111. op = '>=';
  2112. }
  2113. else {
  2114. op = '>';
  2115. }
  2116. }
  2117. else if (parserInput.$char('<')) {
  2118. if (parserInput.$char('=')) {
  2119. op = '<=';
  2120. }
  2121. else {
  2122. op = '<';
  2123. }
  2124. }
  2125. else if (parserInput.$char('=')) {
  2126. if (parserInput.$char('>')) {
  2127. op = '=>';
  2128. }
  2129. else if (parserInput.$char('<')) {
  2130. op = '=<';
  2131. }
  2132. else {
  2133. op = '=';
  2134. }
  2135. }
  2136. if (op) {
  2137. b = cond();
  2138. if (b) {
  2139. c = new (tree_1.default.Condition)(op, a, b, index + currentIndex, false);
  2140. }
  2141. else {
  2142. error('expected expression');
  2143. }
  2144. }
  2145. else if (!preparsedCond) {
  2146. c = new (tree_1.default.Condition)('=', a, new (tree_1.default.Keyword)('true'), index + currentIndex, false);
  2147. }
  2148. return c;
  2149. }
  2150. },
  2151. //
  2152. // An operand is anything that can be part of an operation,
  2153. // such as a Color, or a Variable
  2154. //
  2155. operand: function () {
  2156. var entities = this.entities;
  2157. var negate;
  2158. if (parserInput.peek(/^-[@$(]/)) {
  2159. negate = parserInput.$char('-');
  2160. }
  2161. var o = this.sub() || entities.dimension() ||
  2162. entities.color() || entities.variable() ||
  2163. entities.property() || entities.call() ||
  2164. entities.quoted(true) || entities.colorKeyword() ||
  2165. entities.mixinLookup();
  2166. if (negate) {
  2167. o.parensInOp = true;
  2168. o = new (tree_1.default.Negative)(o);
  2169. }
  2170. return o;
  2171. },
  2172. //
  2173. // Expressions either represent mathematical operations,
  2174. // or white-space delimited Entities.
  2175. //
  2176. // 1px solid black
  2177. // @var * 2
  2178. //
  2179. expression: function () {
  2180. var entities = [];
  2181. var e;
  2182. var delim;
  2183. var index = parserInput.i;
  2184. do {
  2185. e = this.comment();
  2186. if (e) {
  2187. entities.push(e);
  2188. continue;
  2189. }
  2190. e = this.addition() || this.entity();
  2191. if (e instanceof tree_1.default.Comment) {
  2192. e = null;
  2193. }
  2194. if (e) {
  2195. entities.push(e);
  2196. // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
  2197. if (!parserInput.peek(/^\/[/*]/)) {
  2198. delim = parserInput.$char('/');
  2199. if (delim) {
  2200. entities.push(new (tree_1.default.Anonymous)(delim, index + currentIndex));
  2201. }
  2202. }
  2203. }
  2204. } while (e);
  2205. if (entities.length > 0) {
  2206. return new (tree_1.default.Expression)(entities);
  2207. }
  2208. },
  2209. property: function () {
  2210. var name = parserInput.$re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);
  2211. if (name) {
  2212. return name[1];
  2213. }
  2214. },
  2215. ruleProperty: function () {
  2216. var name = [];
  2217. var index = [];
  2218. var s;
  2219. var k;
  2220. parserInput.save();
  2221. var simpleProperty = parserInput.$re(/^([_a-zA-Z0-9-]+)\s*:/);
  2222. if (simpleProperty) {
  2223. name = [new (tree_1.default.Keyword)(simpleProperty[1])];
  2224. parserInput.forget();
  2225. return name;
  2226. }
  2227. function match(re) {
  2228. var i = parserInput.i;
  2229. var chunk = parserInput.$re(re);
  2230. if (chunk) {
  2231. index.push(i);
  2232. return name.push(chunk[1]);
  2233. }
  2234. }
  2235. match(/^(\*?)/);
  2236. while (true) {
  2237. if (!match(/^((?:[\w-]+)|(?:[@$]\{[\w-]+\}))/)) {
  2238. break;
  2239. }
  2240. }
  2241. if ((name.length > 1) && match(/^((?:\+_|\+)?)\s*:/)) {
  2242. parserInput.forget();
  2243. // at last, we have the complete match now. move forward,
  2244. // convert name particles to tree objects and return:
  2245. if (name[0] === '') {
  2246. name.shift();
  2247. index.shift();
  2248. }
  2249. for (k = 0; k < name.length; k++) {
  2250. s = name[k];
  2251. name[k] = (s.charAt(0) !== '@' && s.charAt(0) !== '$') ?
  2252. new (tree_1.default.Keyword)(s) :
  2253. (s.charAt(0) === '@' ?
  2254. new (tree_1.default.Variable)("@" + s.slice(2, -1), index[k] + currentIndex, fileInfo) :
  2255. new (tree_1.default.Property)("$" + s.slice(2, -1), index[k] + currentIndex, fileInfo));
  2256. }
  2257. return name;
  2258. }
  2259. parserInput.restore();
  2260. }
  2261. }
  2262. };
  2263. };
  2264. Parser.serializeVars = function (vars) {
  2265. var s = '';
  2266. for (var name_1 in vars) {
  2267. if (Object.hasOwnProperty.call(vars, name_1)) {
  2268. var value = vars[name_1];
  2269. s += ((name_1[0] === '@') ? '' : '@') + name_1 + ": " + value + ((String(value).slice(-1) === ';') ? '' : ';');
  2270. }
  2271. }
  2272. return s;
  2273. };
  2274. exports.default = Parser;
  2275. //# sourceMappingURL=parser.js.map