UNPKG

@extjs/sencha-cmd-linux-32

Version:

Productivity and performance optimization tool for building applications with Sencha Ext JS and Sencha Touch.

1,695 lines (1,501 loc) 67.5 kB
"use strict"; var Fashion = require('../export/Base.js'), Base = Fashion.Base; var Scanner = require('./Scanner.js'), EOF = Scanner.EOF; var Nodes = require('../parse/ast/Nodes.js'), Each = Nodes.Each, For = Nodes.For, While = Nodes.While, Charset = Nodes.Charset, FunctionNode = Nodes.Function, Ruleset = Nodes.Ruleset, Mixin = Nodes.Mixin, Block = Nodes.Block, Include = Nodes.Include, Assignment = Nodes.Assignment, Declaration = Nodes.Declaration, VariableAssignment = Nodes.VariableAssignment, If = Nodes.If, Else = Nodes.Else, Return = Nodes.Return, Parenthetical = Nodes.Parenthetical, SelectorPart = Nodes.SelectorPart, SelectorProperty = Nodes.SelectorProperty, CompoundSelector = Nodes.CompoundSelector, MultiPartSelector = Nodes.MultiPartSelector, SelectorList = Nodes.SelectorList, BinaryExpression = Nodes.BinaryExpression, UnaryExpression = Nodes.UnaryExpression, Variable = Nodes.Variable, Literal = Nodes.Literal, Number = Nodes.Number, String = Nodes.String, Length = Nodes.Length, Time = Nodes.Time, Angle = Nodes.Angle, Percentage = Nodes.Percentage, Color = Nodes.Color, FunctionCall = Nodes.FunctionCall, Extend = Nodes.Extend, List = Nodes.List, Warn = Nodes.Warn, Error = Nodes.Error, Debug = Nodes.Debug, Import = Nodes.Import, Require = Nodes.Require, Content = Nodes.Content, Debugger = Nodes.Debugger; function debug(message) { //console.log(message); } class Parser extends Base { constructor(lax) { super(); this.lax = lax; Fashion.apply(this, { isSelector: false, isParenthetical: false, isSelectorPart: false }); } nextValueIs (val, offset) { var next = this.scanner.peek(offset); return next && next.value === val; } // Constant ::= Number | // String | // Length | // Time | // Angle | // Percentage | // Color; parseConstant() { var scanner = this.scanner, t = scanner.peek(); if (t !== EOF && t.isOperator) { return undefined; } if (t.isNumber) { t = scanner.next(); if (t.value.indexOf('\\') > -1) { var t2 = scanner.peek(); if (t2.isNumber) { scanner.next(); return new Literal(t, t1.value + t2.value); } } return new Number(t); } if (t.isString) { t = scanner.next(); return new String(t); } if (t.isLength) { t = scanner.next(); return new Length(t); } if (t.isTime) { t = scanner.next(); return new Time(t); } if (t.isAngle) { t = scanner.next(); return new Angle(t); } if (t.isPercentage) { t = scanner.next(); return new Percentage(t); } if (t.isHash) { t = scanner.next(); return new Color(t); } return undefined; } // Stylesheet ::= Statement* parseStylesheet() { var stat, statements = []; while (true) { stat = this.parseStatement(); if (!stat) { break; } statements.push(stat); } return statements; } // Statement ::= Documentation | // VariableAssignment | // Directive | // Directive ';' | // Ruleset parseStatement() { var me = this, scanner = me.scanner, t = scanner.peek(), stat; if (t === EOF) { return undefined; } if (t.isVariable) { return me.parseVariableAssignment(); } if (t.isDirective && t.value[1] !== '-') { stat = me.parseDirective(); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === ';') { scanner.next(); } return stat; } if (t.isIdent) { var start = t.startIdx, line = t.startLine, fn = me.parseFunctionCall(); t = scanner.peek(); if (!!fn && t.value === ';') { scanner.next(); return fn; } else { scanner.lineNumber = line; scanner.setIndex(start); } } return me.parseRuleset(); } // Directive ::= Charset | // Debug | // Each | // For | // Function | // If | // Else | // Extend | // Mixin | // Import | // Include | // While | // Return parseDirective() { var me = this, scanner = me.scanner, t = scanner.peek(); if (t.value === '@charset') { return me.parseCharset(); } if (t.value === '@debug') { return me.parseDebug(); } if (t.value === '@each') { return me.parseEach(); } if (t.value === '@for') { return me.parseFor(); } if (t.value === '@function') { return me.parseFunction(); } if (t.value === '@if') { return me.parseIf(); } if (t.value === '@elseif') { return me.parseElse(); } if (t.value === '@else') { return me.parseElse(); } if (t.value === '@extend') { return me.parseExtend(); } if (t.value === '@import') { return me.parseImport(); } if (t.value === '@require') { return me.parseRequire(); } if (t.value === '@debugger') { return me.parseDebugger(); } if (t.value === '@content') { return me.parseContent(); } if (t.value === '@mixin') { return me.parseMixin(); } if (t.value === '@include') { return me.parseInclude(); } if (t.value === '@return') { return me.parseReturn(); } if (t.value === '@while') { return me.parseWhile(); } if (t.value === '@warn') { return me.parseWarn(); } if (t.value === '@error') { return me.parseError(); } Fashion.raiseAt('Unknown directive ' + t.value, scanner); } // Function ::= '@function' FunctionCall '{' ScopedStatement* '}' parseFunction() { var me = this, scanner = me.scanner, t, func, statements; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@function') { func = me.parseFunctionCall(true); statements = me.parseBlock().statements; return new FunctionNode({ func: func, statements: statements, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return undefined; } // Charset ::= '@charset' String parseCharset() { var me = this, scanner = me.scanner, t, charset; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@charset') { t = scanner.next(); if (t !== EOF && t.isString) { charset = t.value; return new Charset({ charset: charset, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } Fashion.raiseAt('Expected a string after @charset', scanner); } return undefined; } // Debug ::= '@debug' Expression parseDebug() { var me = this, scanner = me.scanner, t, expr; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@debug') { expr = me.parseExpression(); if (expr) { return new Debug({ expr: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } Fashion.raiseAt('Expected an expression after @debug', scanner); } return undefined; } // Warn ::= '@warn' Expression parseWarn() { var me = this, scanner = me.scanner, t, expr; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@warn') { expr = me.parseExpression(); if (expr) { return new Warn({ expr: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } Fashion.raiseAt('Expected an expression after @debug', scanner); } return undefined; } // Warn ::= '@error' Expression parseError() { var me = this, scanner = me.scanner, t, expr; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@error') { expr = me.parseExpression(); if (expr) { return new Error({ expr: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } Fashion.raiseAt('Expected an expression after @debug', scanner); } return undefined; } // Each ::= '@each' Variable 'in' Sequence '{' ScopedStatement* '}' parseEach() { var me = this, scanner = me.scanner, t, id, seq, statements = [], stat, isMap; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@each') { t = scanner.next(); if (t === EOF || !t.isVariable) { Fashion.raiseAt('Expected variable name after @each', scanner); } id = t.value; t = scanner.next(); if (t !== EOF && t.value === ',') { t = scanner.next(); if (t === EOF || !t.isVariable) { Fashion.raiseAt('Expected variable name after "," in @each', scanner); } id = new List(null, [id, t.value], ', '); isMap = true; t = scanner.next(); } if (t === EOF || !t.isIdent || t.value !== 'in') { Fashion.raiseAt('Expected "in" after variable in @each', scanner); } seq = me.parseSequence(); if (seq.items) { seq = seq.items; } if (!seq) { Fashion.raiseAt('Expected value sequence after "in" in @each', scanner); } scanner.expect('{'); while (true) { debug("parsing each"); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === '}') { break; } stat = me.parseScopedStatement(); if (!stat) { break; } statements.push(stat); } scanner.expect('}'); return new Each({ variable: id, list: seq, statements: statements, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile, isMap: isMap }); } return undefined; } // For ::= '@for' Variable 'from' Expression 'to' Expression '{' ScopedStatement* '}' | // '@for' Variable 'from' Expression 'through' Expression '{' ScopedStatement* '}' | parseFor() { var me = this, scanner = me.scanner, t, id, start, end, inclusive, statements = [], stat; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@for') { t = scanner.next(); if (t === EOF || !t.isVariable) { Fashion.raiseAt('Expected variable name after @for', scanner); } id = t.value; t = scanner.next(); if (t === EOF || !t.isIdent || t.value !== 'from') { Fashion.raiseAt('Expected "from" after variable in @for', scanner); } start = me.parseExpression(); if (!start) { Fashion.raiseAt('Expected an expression after "from" in @for', scanner); } t = scanner.next(); if (t === EOF || !t.isIdent || (t.value !== 'to' && t.value !== 'through')) { Fashion.raiseAt('Expected "to" or "through" in @for', scanner); } inclusive = t.value === 'through'; end = me.parseExpression(); if (!end) { Fashion.raiseAt('Expected a terminating expression in @for', scanner); } scanner.expect('{'); while (true) { debug("parse for"); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === '}') { break; } stat = me.parseScopedStatement(); if (!stat) { break; } statements.push(stat); } scanner.expect('}'); return new For({ variable: id, start: start, end: end, inclusive: inclusive, statements: statements, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return undefined; } // While ::= '@while' Expression '{' ScopedStatement* '}' parseWhile() { var me = this, scanner = me.scanner, t, condition, stat, statements = []; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@while') { condition = me.parseExpression(); scanner.expect('{'); while (true) { debug("parse while"); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === '}') { break; } stat = me.parseScopedStatement(); if (!stat) { break; } statements.push(stat); } scanner.expect('}'); return new While({ condition: condition, statements: statements, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return undefined; } // If ::= '@if' Expression '{' ScopedStatement* '}' parseIf() { var me = this, scanner = me.scanner, t, condition, stat, statements = []; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@if') { condition = me.parseSequence(); scanner.expect('{'); while (true) { debug("parse if"); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === '}') { break; } stat = me.parseScopedStatement(); if (!stat) { break; } statements.push(stat); } scanner.expect('}'); return new If({ condition: condition, statements: statements, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return undefined; } // Else ::= '@else' Expression '{' ScopedStatement* '}' | // '@else' If | // '@elseif' Expression '{' ScopedStatement* '}' parseElse() { var me = this, scanner = me.scanner, t, condition, stat, statements = [], isElseIf; t = scanner.next(); if (t !== EOF && t.isDirective && (t.value === '@else' || t.value == '@elseif')) { isElseIf = t.value == '@elseif' t = scanner.peek(); if (isElseIf) { condition = me.parseExpression(); } else if (t.isIdent && t.value === 'if') { scanner.next(); condition = me.parseExpression(); } scanner.expect('{'); while (true) { debug("parse else"); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === '}') { break; } if (t === undefined || t === EOF) { break; } stat = me.parseScopedStatement(); if (stat) { statements.push(stat); } else { break; } } scanner.expect('}'); return new Else({ condition: condition, statements: statements, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return undefined; } // Extend ::= '@extend' Selector parseExtend() { var me = this, scanner = me.scanner, t, selector; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@extend') { selector = me.parseSelectors(); if (selector) { return new Extend({ selector: selector, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { Fashion.raiseAt('Expecting attribute name', scanner); } } } // Import ::= '@import' Argument parseImport() { var scanner = this.scanner, t = scanner.next(), expr, t2; if (t !== EOF && t.isDirective && t.value === '@import') { t = scanner.peek(); t2 = scanner.peek(2); if (t.isString && t2.value == ';') { scanner.next(); return new Import({ source: new Literal(t), lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { expr = this.parseSelectorSequence(); return new Import({ source: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } } return undefined; } // Require ::= '@require' Argument parseRequire() { var scanner = this.scanner, t = scanner.next(), expr, t2; if (t !== EOF && t.isDirective && t.value === '@require') { t = scanner.peek(); t2 = scanner.peek(2); if (t.isString && t2.value == ';') { scanner.next(); return new Require({ source: new Literal(t), lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { expr = this.parseSequence(); return new Require({ source: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } } return undefined; } parseDebugger() { var scanner = this.scanner, t = scanner.next(); return new Debugger({ lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } parseContent() { var scanner = this.scanner, t = scanner.next(); return new Content({ type: "Content", lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } // Mixin ::= '@mixin' FunctionCall '{' ScopedStatements* '}' parseMixin() { var scanner = this.scanner, t, stat, mixin; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@mixin') { mixin = new Mixin({ name: this.parseFunctionCall(true), statements: [], docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); mixin.statements = this.parseBlock().statements; t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === ';') { scanner.next(); } } return mixin; } // Include ::= '@include' Identifier parseInclude() { var scanner = this.scanner, t, inc, block; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@include') { inc = this.parseFunctionCall(true); if (this.nextValueIs('{')) { block = this.parseBlock(); } return new Include({ include: inc, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile, content: block }); } return undefined; } parseBlock() { var scanner = this.scanner, t, stat, statements = []; t = scanner.peek(); if (!t || t === EOF) { return undefined; } if (t.value === ';') { scanner.next(); return undefined; } scanner.expect('{'); while (true) { debug("parse block"); t = scanner.peek(); if (t === null || t === undefined) { break; } if (t.value === ';') { scanner.next(); continue; } if (t === null || t === undefined) { break; } if (t !== EOF && t.isOperator && t.value === '}') { break; } stat = this.parseScopedStatement(); if (stat) { statements.push(stat); } else { break; } } debug("done parsing block"); scanner.expect('}'); return new Block({ statements: statements, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } // Return ::= '@return' Identifier parseReturn() { var scanner = this.scanner, t, expr; t = scanner.next(); if (t !== EOF && t.isDirective && t.value === '@return') { expr = this.parseSequence(); return new Return({ expr: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return undefined; } // VariableAssignment ::= VariableName ':' Expression ';' | // VariableName ':' Expression !default ';' parseVariableAssignment() { var scanner = this.scanner, t, assignment, start, end; t = scanner.next(); assignment = new VariableAssignment(t, t.value); try { scanner.expect(':'); t = scanner.peek(); start = t ? t.startIdx : -1; assignment.value = this.parseValue(); t = scanner.peek(); end = t ? t.startIdx : start; while (t !== EOF && t.isOperator && t.value === '!') { t = scanner.next(); t = scanner.next(); if (t.value === 'default') { assignment['default'] = true; } else if (t.value === 'global') { assignment['global'] = true; } else if (t.value === 'dynamic') { assignment['dynamic'] = true; } end = t.startIdx; t = scanner.peek(); } if (start > -1) { assignment.valueText = scanner.style.substring(start, end); } t = scanner.peek(); if (t !== EOF && t.value === ';') { scanner.expect(';'); } } catch (e) { if (!this.lax) { throw e; } } return assignment; } // Ruleset ::= Selectors '{' ScopedStatement* '}' parseRuleset() { var scanner = this.scanner, t, selectors, statements, block; t = scanner.peek(); selectors = this.parseSelectors(); block = this.parseBlock(); statements = block && block.statements; return new Ruleset({ selectors: selectors, statements: statements, blockDocs: block && block.docs, lineNumber: t.lineNumber, docs: ((selectors && selectors.docs) || []).concat(scanner.flushDocs() || []), token: t, file: scanner.currentFile }); } // Selectors ::= Selector | // Selectors ',' Selector parseSelectors() { var selectors = this.parseSelectorSequence(); return selectors; } // Attempt to parse the incoming tokens as if they form a selector. // Returns the token right after the parse can't move anymore. tryParseSelectors() { var scanner = this.scanner, peek = scanner.peek(), index = peek.startIdx, line = peek.startLine, docs = scanner.docs, token; try { this.parseSelectors(); } catch (e) { this.isSelector = false; } token = scanner.peek(); scanner.lineNumber = line; scanner.setIndex(index); scanner.docs = docs; return token; } // ScopedStatement ::= Ruleset | // Declaration | // VariableAssignment | // Directive parseScopedStatement() { var me = this, scanner = me.scanner, t = scanner.peek(), stat; if (t.isHash || t.isClass) { return me.parseRuleset(); } if (t !== EOF && t.isOperator && (t.value === '&' || t.value === '>' || t.value === '~' || t.value === ':' || t.value === '%')) { return me.parseRuleset(); } if (t.isVariable) { return me.parseVariableAssignment(); } if (t.isDirective) { stat = me.parseDirective(); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === ';') { scanner.next(); } return stat; } // Handle things like '-webkit-foobar: value' if (t !== EOF && t.isOperator && t.value === '-') { return me.parseDeclaration(); } // This could be Declaration or Ruleset if (t.isIdent || t.isNumber || t.isPercentage || (t.isOperator && t.value !== '}')) { //var idx = t.idx; //if(scanner.style.charAt(idx) === ':') { // if(scanner.style.charAt(idx+1) !== ' ') { // return me.parseRuleset(); // } //} var idx = 1, skipRuleset = false, directive = false, tmp = scanner.peek(idx++); while(tmp && !tmp.isEOF && tmp.value !== '{' && tmp.value !== '}' && tmp.value !== ';') { if (tmp.value.indexOf('@') === 0) { directive = true; } if (tmp.value === ':') { if (scanner.style.charAt(tmp.idx) === ' ' && !directive) { skipRuleset = true; } if (this.nextValueIs('{', idx)) { skipRuleset = true; } } tmp = scanner.peek(idx++); } if (tmp && tmp.value === '{' && !skipRuleset) { return me.parseRuleset(); } return me.parseDeclaration(); /* t = me.tryParseSelectors(); if (t !== EOF && t.isOperator && t.value === '{') { //system.print('tryParse: treat as selector'); return me.parseRuleset(); } return me.parseDeclaration(); */ } return undefined; } // Declaration ::= Identifier ':' Value | // Identifier ':' Value '!important' parseDeclaration() { var me = this, scanner = me.scanner, t = scanner.next(), decl = new Declaration({ property: '', docs: scanner.flushDocs(), lineNumber: scanner.lineNumber, token: t, file: scanner.currentFile }); if (t !== EOF && t.isOperator && (t.value === '*' || t.value === '-')) { decl.property = t.value; t = scanner.next(); // special case for property name like '-#{prefix}-box-shadow' if (t !== EOF && t.isHash) { t.type = 'ident'; } } if (t !== EOF && t.isIdent) { decl.property += t.value; scanner.expect(':'); t = scanner.peek(); if (decl.property.indexOf('--') === 0) { var start = t.startIdx, idx = start, end = start, brace = 0, paren = 0, bracket = 0, quote = null, ch; loop: for (idx = start; idx < scanner.style.length; idx++) { ch = scanner.style.charAt(idx); switch(ch) { case '"': case "'": if (!quote) { quote = ch; } else if (quote === ch) { quote = null; } break; case '{': brace++; break; case '(': paren++; break; case '[': bracket++; break; case '}': brace--; break; case ')': paren--; break; case ']': bracket--; break; case ';': if (bracket === 0 && brace === 0 && paren === 0 && !quote) { end = idx; break loop; } break; default: break; } } scanner.setIndex(end); var data = { value: scanner.style.substring(start, end).trim() }; decl.value = new Literal(t, JSON.stringify(data)); decl.value.jsonEncoded = true; } //special hack for IE else if (decl.property === 'filter' || decl.property === '-ms-filter' || decl.property === '_filter') { decl.value = me.parseFilterValue(); } else { decl.value = me.parseValue(); } t = scanner.peek(); if (t !== EOF) { if (t.isOperator && t.value === '!') { scanner.next(); t = scanner.next(); if (t.isIdent && t.value === 'important') { decl.important = true; } } } t = scanner.peek(); if (t !== EOF) { if (t.isOperator && t.value === ';') { scanner.next(); } } return decl; } else { var message = [ 'Property declaration: expected identifier but saw ', JSON.stringify(t), ' instead : ', scanner.lineNumber, ":", scanner.index - scanner.start ].join(''); Fashion.error(message); Fashion.raiseAt(message, scanner); } } // Value ::= Sequence | // Value Sequence parseValue() { var scanner = this.scanner, t, stat, statements = [], sequence, ruleset; sequence = this.parseSequence(); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value == '{') { scanner.next(); while ((stat = this.parseScopedStatement()) !== undefined) { statements.push(stat); } scanner.expect('}'); ruleset = new Ruleset({ statements: statements, selectors: [], lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } if (ruleset) { if (sequence.items) { sequence.items.push(ruleset); } else if (sequence) { sequence = new List(null, [sequence, ruleset], ' '); } else { sequence = ruleset; } } return sequence; } parseFilterFunctionCall() { var scanner = this.scanner, t, peek, pos; t = scanner.peek(); if (t === EOF) { return; } if (t.type == 'ident' && (t.value == 'progid' || t.value == 'chroma')) { pos = t.startIdx; while (true) { peek = scanner.peek(); if (peek && peek.isOperator && peek.value === ')') { t = scanner.next(); break; } if (peek && peek.value === ';') { break; } t = scanner.next(); } return new Literal(t, this.style.substring(pos, t.idx) .replace(/\r/g, '') .replace(/\n/g, '') .replace(/\s+/g, ' ') .trim()); } } // Value ::= Sequence | // Value Sequence parseFilterValue() { var scanner = this.scanner, t, args, value = [], pos, separator = ' '; while (true) { debug("parse filter value"); t = scanner.peek(); if (t.value == ',') { separator = ','; scanner.next(); continue; } if (t === EOF) { break; } if (t.type == 'ident' && (t.value == 'progid' || t.value == 'chroma')) { value.push(this.parseFilterFunctionCall()); continue; } if (t.isOperator) { if (t.value === ';' || t.value === '{' || t.value === '!' || t.value === '}') { break; } } args = this.parseSequence(); if (args.items) { separator = args.separator; args = args.items; } else { args = [args]; } if (args.length === 0) { break; } else if (args.length === 1) { value.push(args[0]); } else { value.push.apply(value, args); } } if (value.length === 0) { return null; } // Simplify if there is only one value in the array while (value.length === 1) { value = value[0]; } if (value.length) { value = new List(null, value, separator); } return value; } // Expression ::= Relational | // Identifier '=' Relational parseExpression() { var scanner = this.scanner, id, t = scanner.peek(); if (t.isIdent) { t = scanner.peek(2); if (t !== EOF && t.isOperator) { switch (t.value) { case '=': case '~=': case '|=': case '^=': case '$=': case '*=': id = scanner.next().value; scanner.expect(t.value); return new Assignment({ id: id, expr: this.parseRelational(), operator: t.value, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); default: break; } } } else if (t !== EOF && t.isOperator && t.value === '!') { var t2 = scanner.peek(2); if (t2 && t2.value === 'important') { t = scanner.next(); t = scanner.next(); return new Literal(t, '!important'); } } return this.parseDisjunction(); } // Disjunction ::= Conjunction | // Disjunction 'or' Conjunction parseDisjunction() { var scanner = this.scanner, factor, or, t; or = this.parseConjunction(); factor = or; while (true) { debug("parse disjunction"); t = scanner.peek(); if (t != EOF && t.isOperator && t.value === 'or' && !this.isSelector) { t = scanner.next(); or = this.parseConjunction(); if (!or) { break; } factor = new BinaryExpression({ operator: 'or', left: factor, right: or, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { break; } } return factor; } // Conjunction ::= LogicalAnd | // Conjunction 'and' LogicalAnd parseConjunction() { var scanner = this.scanner, or, and, t; and = this.parseComplement(); or = and; while (true) { debug("parse conjunction"); t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === 'and' && !this.isSelector) { t = scanner.next(); and = this.parseComplement(); if (!and) { break; } or = new BinaryExpression({ operator: 'and', left: or, right: and, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { break; } } return or; } // Complement ::= Primary | // 'not' Primary parseComplement() { var scanner = this.scanner, t; t = scanner.peek(); if (t !== EOF && t.isOperator && t.value === 'not') { if (this.isSelectorParen) { scanner.next(); return new Literal(t, 'not'); } if (!this.isSelector) { scanner.next(); return new UnaryExpression({ operator: 'not', expr: this.parseRelational(), lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } } return this.parseRelational(); } // Relational ::= Additive | // Relational '==' Additive | // Relational '!=' Additive | // Relational '<' Additive | // Relational '>' Additive | // Relational '<=' Comparison | // Relational '>=' Comparison parseRelational() { var scanner = this.scanner, cmp, expr, t; cmp = this.parseAdditive(); expr = cmp; while (true) { debug("parse relational"); t = scanner.peek(); if (t !== EOF && t.isOperator && (t.value === '==' || t.value === '!=' || t.value === '<' || t.value === '<=' || t.value === '>=' || (t.value === '>' && !this.isSelector))) { t = scanner.next(); cmp = this.parseAdditive(); if (!cmp) { break; } expr = new BinaryExpression({ operator: t.value, left: expr, right: cmp, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { break; } } return expr; } // Additive ::= Multiplicative | // Additive '+' Multiplicative | // Additive '-' Multiplicative parseAdditive() { var scanner = this.scanner, term, cmp, t; term = this.parseMultiplicative(); cmp = term; while (true) { debug("parse additive"); t = scanner.peek(); if (t !== EOF && t.isOperator && (t.value === '+' || t.value === '-') && !this.isSelector) { t = scanner.next(); term = this.parseMultiplicative(); if (!term) { break; } cmp = new BinaryExpression({ operator: t.value, left: cmp, right: term, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } else { break; } } return cmp; } // Multiplicative ::= Disjunction | // Multiplicative '*' Disjunction | // Multiplicative '/' Disjunction | // Multiplicative '%' Disjunction parseMultiplicative() { var term, factor, t; factor = this.parsePrimary(); term = factor; while (true) { debug("parse multiplicative"); t = this.scanner.peek(); if (t !== EOF && t.isOperator && (t.value === '*' || t.value === '/' || (t.value === '%' && !this.isSelector))) { t = this.scanner.next(); factor = this.parsePrimary(); if (!factor) { break; } term = new BinaryExpression({ operator: t.value, left: term, right: factor, docs: this.scanner.flushDocs() }); } else { break; } } return term; } // Primary ::= '(' Value ')' | // FunctionCall | // Variable | // Constant parsePrimary() { var scanner = this.scanner, t, t2, expr; t = scanner.peek(); t2 = scanner.peek(2); if (t === EOF) { return undefined; } if (t !== EOF && t.isOperator && t.value === '(') { return this.parseParenthetical(); } if (t.isIdent) { if (this.keywords[t.value]) { scanner.next(); return new Literal(t); } else if (t.value === 'progid' && t2.value === ':') { return this.parseFilterFunctionCall(); } else { return this.parseFunctionCall(); } } if (t.isVariable) { t = scanner.next(); if (t.negate) { return new BinaryExpression({ operator: '-', right: new Variable(t, t.value), docs: scanner.flushDocs(), token: t, file: scanner.currentFile }); } return new Variable(t, t.value); } t = this.parseConstant(); return t; } parseParenthetical() { var scanner = this.scanner, t = scanner.next(), selWas = this.isSelector, parWas = this.isParenthetical, selParWas = this.isSelectorParen, expr; if (parWas) { this.isSelector = false; } this.isParenthetical = true; this.isSelectorParen = selWas; expr = this.isSelector ? this.parseSelectorSequence(true) : this.parseSequence(); this.isSelector = selWas; this.isParenthetical = parWas; this.isSelectorParen = selParWas; scanner.expect(')'); if (expr && expr.isKVP) { expr = new List(null, [expr], ', '); } return new Parenthetical({ expr: expr, lineNumber: t.lineNumber, docs: scanner.flushDocs(), token: t, file: scanner.currentFile }) } // FunctionCall ::= Identifier '(' Arguments ')' | // Identifier '(' ')' | // Literal parseFunctionCall(allowSpaceBeforeParen) { var scanner = this.scanner, t = scanner.next(), id = t.value, start = t.idx, end, ch = '', prev, line, args = [], passThroughNames = { 'expression': 1, 'calc': 1, '-moz-calc': 1, '-webkit-calc': 1, '-ms-calc': 1 }, temp, twas; twas = t; t = scanner.peek(); if (t !== EOF) { prev = scanner.style.charAt(start); if (t.isOperator && t.value === '(' && (prev !== ' ' || allowSpaceBeforeParen)) { scanner.next(); t = scanner.peek(); if (id in passThroughNames) { // unquoted URL, e.g. url(http://foo.bar.com/baz.png) // just consume everything until we get to ')' start = t.startIdx; end = start; var parenCount = 0;