@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
JavaScript
"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;