aboutsummaryrefslogtreecommitdiff
path: root/node_modules/csso/lib/parser
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/csso/lib/parser')
-rw-r--r--node_modules/csso/lib/parser/const.js46
-rw-r--r--node_modules/csso/lib/parser/index.js1870
-rw-r--r--node_modules/csso/lib/parser/scanner.js380
3 files changed, 2296 insertions, 0 deletions
diff --git a/node_modules/csso/lib/parser/const.js b/node_modules/csso/lib/parser/const.js
new file mode 100644
index 00000000..4c2d4000
--- /dev/null
+++ b/node_modules/csso/lib/parser/const.js
@@ -0,0 +1,46 @@
+exports.TokenType = {
+ String: 'String',
+ Comment: 'Comment',
+ Unknown: 'Unknown',
+ Newline: 'Newline',
+ Space: 'Space',
+ Tab: 'Tab',
+ ExclamationMark: 'ExclamationMark', // !
+ QuotationMark: 'QuotationMark', // "
+ NumberSign: 'NumberSign', // #
+ DollarSign: 'DollarSign', // $
+ PercentSign: 'PercentSign', // %
+ Ampersand: 'Ampersand', // &
+ Apostrophe: 'Apostrophe', // '
+ LeftParenthesis: 'LeftParenthesis', // (
+ RightParenthesis: 'RightParenthesis', // )
+ Asterisk: 'Asterisk', // *
+ PlusSign: 'PlusSign', // +
+ Comma: 'Comma', // ,
+ HyphenMinus: 'HyphenMinus', // -
+ FullStop: 'FullStop', // .
+ Solidus: 'Solidus', // /
+ Colon: 'Colon', // :
+ Semicolon: 'Semicolon', // ;
+ LessThanSign: 'LessThanSign', // <
+ EqualsSign: 'EqualsSign', // =
+ GreaterThanSign: 'GreaterThanSign', // >
+ QuestionMark: 'QuestionMark', // ?
+ CommercialAt: 'CommercialAt', // @
+ LeftSquareBracket: 'LeftSquareBracket', // [
+ ReverseSolidus: 'ReverseSolidus', // \
+ RightSquareBracket: 'RightSquareBracket', // ]
+ CircumflexAccent: 'CircumflexAccent', // ^
+ LowLine: 'LowLine', // _
+ LeftCurlyBracket: 'LeftCurlyBracket', // {
+ VerticalLine: 'VerticalLine', // |
+ RightCurlyBracket: 'RightCurlyBracket', // }
+ Tilde: 'Tilde', // ~
+ Identifier: 'Identifier',
+ DecimalNumber: 'DecimalNumber'
+};
+
+// var i = 1;
+// for (var key in exports.TokenType) {
+// exports.TokenType[key] = i++;
+// }
diff --git a/node_modules/csso/lib/parser/index.js b/node_modules/csso/lib/parser/index.js
new file mode 100644
index 00000000..9157decf
--- /dev/null
+++ b/node_modules/csso/lib/parser/index.js
@@ -0,0 +1,1870 @@
+'use strict';
+
+var TokenType = require('./const').TokenType;
+var Scanner = require('./scanner');
+var List = require('../utils/list');
+var needPositions;
+var filename;
+var scanner;
+
+var SCOPE_ATRULE_EXPRESSION = 1;
+var SCOPE_SELECTOR = 2;
+var SCOPE_VALUE = 3;
+
+var specialFunctions = {};
+specialFunctions[SCOPE_ATRULE_EXPRESSION] = {
+ url: getUri
+};
+specialFunctions[SCOPE_SELECTOR] = {
+ url: getUri,
+ not: getNotFunction
+};
+specialFunctions[SCOPE_VALUE] = {
+ url: getUri,
+ expression: getOldIEExpression,
+ var: getVarFunction
+};
+
+var initialContext = {
+ stylesheet: getStylesheet,
+ atrule: getAtrule,
+ atruleExpression: getAtruleExpression,
+ ruleset: getRuleset,
+ selector: getSelector,
+ simpleSelector: getSimpleSelector,
+ block: getBlock,
+ declaration: getDeclaration,
+ value: getValue
+};
+
+var blockMode = {
+ 'declaration': true,
+ 'property': true
+};
+
+function parseError(message) {
+ var error = new Error(message);
+ var offset = 0;
+ var line = 1;
+ var column = 1;
+ var lines;
+
+ if (scanner.token !== null) {
+ offset = scanner.token.offset;
+ line = scanner.token.line;
+ column = scanner.token.column;
+ } else if (scanner.prevToken !== null) {
+ lines = scanner.prevToken.value.trimRight();
+ offset = scanner.prevToken.offset + lines.length;
+ lines = lines.split(/\n|\r\n?|\f/);
+ line = scanner.prevToken.line + lines.length - 1;
+ column = lines.length > 1
+ ? lines[lines.length - 1].length + 1
+ : scanner.prevToken.column + lines[lines.length - 1].length;
+ }
+
+ error.name = 'CssSyntaxError';
+ error.parseError = {
+ offset: offset,
+ line: line,
+ column: column
+ };
+
+ throw error;
+}
+
+function eat(tokenType) {
+ if (scanner.token !== null && scanner.token.type === tokenType) {
+ scanner.next();
+ return true;
+ }
+
+ parseError(tokenType + ' is expected');
+}
+
+function expectIdentifier(name, eat) {
+ if (scanner.token !== null) {
+ if (scanner.token.type === TokenType.Identifier &&
+ scanner.token.value.toLowerCase() === name) {
+ if (eat) {
+ scanner.next();
+ }
+
+ return true;
+ }
+ }
+
+ parseError('Identifier `' + name + '` is expected');
+}
+
+function expectAny(what) {
+ if (scanner.token !== null) {
+ for (var i = 1, type = scanner.token.type; i < arguments.length; i++) {
+ if (type === arguments[i]) {
+ return true;
+ }
+ }
+ }
+
+ parseError(what + ' is expected');
+}
+
+function getInfo() {
+ if (needPositions && scanner.token) {
+ return {
+ source: filename,
+ offset: scanner.token.offset,
+ line: scanner.token.line,
+ column: scanner.token.column
+ };
+ }
+
+ return null;
+
+}
+
+function removeTrailingSpaces(list) {
+ while (list.tail) {
+ if (list.tail.data.type === 'Space') {
+ list.remove(list.tail);
+ } else {
+ break;
+ }
+ }
+}
+
+function getStylesheet(nested) {
+ var child = null;
+ var node = {
+ type: 'StyleSheet',
+ info: getInfo(),
+ rules: new List()
+ };
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.Space:
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.Comment:
+ // ignore comments except exclamation comments on top level
+ if (nested || scanner.token.value.charAt(2) !== '!') {
+ scanner.next();
+ child = null;
+ } else {
+ child = getComment();
+ }
+ break;
+
+ case TokenType.Unknown:
+ child = getUnknown();
+ break;
+
+ case TokenType.CommercialAt:
+ child = getAtrule();
+ break;
+
+ case TokenType.RightCurlyBracket:
+ if (!nested) {
+ parseError('Unexpected right curly brace');
+ }
+
+ break scan;
+
+ default:
+ child = getRuleset();
+ }
+
+ if (child !== null) {
+ node.rules.insert(List.createItem(child));
+ }
+ }
+
+ return node;
+}
+
+// '//' ...
+// TODO: remove it as wrong thing
+function getUnknown() {
+ var info = getInfo();
+ var value = scanner.token.value;
+
+ eat(TokenType.Unknown);
+
+ return {
+ type: 'Unknown',
+ info: info,
+ value: value
+ };
+}
+
+function isBlockAtrule() {
+ for (var offset = 1, cursor; cursor = scanner.lookup(offset); offset++) {
+ var type = cursor.type;
+
+ if (type === TokenType.RightCurlyBracket) {
+ return true;
+ }
+
+ if (type === TokenType.LeftCurlyBracket ||
+ type === TokenType.CommercialAt) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function getAtruleExpression() {
+ var child = null;
+ var node = {
+ type: 'AtruleExpression',
+ info: getInfo(),
+ sequence: new List()
+ };
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.Semicolon:
+ break scan;
+
+ case TokenType.LeftCurlyBracket:
+ break scan;
+
+ case TokenType.Space:
+ if (node.sequence.isEmpty()) {
+ scanner.next(); // ignore spaces in beginning
+ child = null;
+ } else {
+ child = getS();
+ }
+ break;
+
+ case TokenType.Comment: // ignore comments
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.Comma:
+ child = getOperator();
+ break;
+
+ case TokenType.Colon:
+ child = getPseudo();
+ break;
+
+ case TokenType.LeftParenthesis:
+ child = getBraces(SCOPE_ATRULE_EXPRESSION);
+ break;
+
+ default:
+ child = getAny(SCOPE_ATRULE_EXPRESSION);
+ }
+
+ if (child !== null) {
+ node.sequence.insert(List.createItem(child));
+ }
+ }
+
+ removeTrailingSpaces(node.sequence);
+
+ return node;
+}
+
+function getAtrule() {
+ eat(TokenType.CommercialAt);
+
+ var node = {
+ type: 'Atrule',
+ info: getInfo(),
+ name: readIdent(false),
+ expression: getAtruleExpression(),
+ block: null
+ };
+
+ if (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.Semicolon:
+ scanner.next(); // {
+ break;
+
+ case TokenType.LeftCurlyBracket:
+ scanner.next(); // {
+
+ if (isBlockAtrule()) {
+ node.block = getBlock();
+ } else {
+ node.block = getStylesheet(true);
+ }
+
+ eat(TokenType.RightCurlyBracket);
+ break;
+
+ default:
+ parseError('Unexpected input');
+ }
+ }
+
+ return node;
+}
+
+function getRuleset() {
+ return {
+ type: 'Ruleset',
+ info: getInfo(),
+ selector: getSelector(),
+ block: getBlockWithBrackets()
+ };
+}
+
+function getSelector() {
+ var isBadSelector = false;
+ var lastComma = true;
+ var node = {
+ type: 'Selector',
+ info: getInfo(),
+ selectors: new List()
+ };
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.LeftCurlyBracket:
+ break scan;
+
+ case TokenType.Comma:
+ if (lastComma) {
+ isBadSelector = true;
+ }
+
+ lastComma = true;
+ scanner.next();
+ break;
+
+ default:
+ if (!lastComma) {
+ isBadSelector = true;
+ }
+
+ lastComma = false;
+ node.selectors.insert(List.createItem(getSimpleSelector()));
+
+ if (node.selectors.tail.data.sequence.isEmpty()) {
+ isBadSelector = true;
+ }
+ }
+ }
+
+ if (lastComma) {
+ isBadSelector = true;
+ // parseError('Unexpected trailing comma');
+ }
+
+ if (isBadSelector) {
+ node.selectors = new List();
+ }
+
+ return node;
+}
+
+function getSimpleSelector(nested) {
+ var child = null;
+ var combinator = null;
+ var node = {
+ type: 'SimpleSelector',
+ info: getInfo(),
+ sequence: new List()
+ };
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.Comma:
+ break scan;
+
+ case TokenType.LeftCurlyBracket:
+ if (nested) {
+ parseError('Unexpected input');
+ }
+
+ break scan;
+
+ case TokenType.RightParenthesis:
+ if (!nested) {
+ parseError('Unexpected input');
+ }
+
+ break scan;
+
+ case TokenType.Comment:
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.Space:
+ child = null;
+ if (!combinator && node.sequence.head) {
+ combinator = getCombinator();
+ } else {
+ scanner.next();
+ }
+ break;
+
+ case TokenType.PlusSign:
+ case TokenType.GreaterThanSign:
+ case TokenType.Tilde:
+ case TokenType.Solidus:
+ if (combinator && combinator.name !== ' ') {
+ parseError('Unexpected combinator');
+ }
+
+ child = null;
+ combinator = getCombinator();
+ break;
+
+ case TokenType.FullStop:
+ child = getClass();
+ break;
+
+ case TokenType.LeftSquareBracket:
+ child = getAttribute();
+ break;
+
+ case TokenType.NumberSign:
+ child = getShash();
+ break;
+
+ case TokenType.Colon:
+ child = getPseudo();
+ break;
+
+ case TokenType.LowLine:
+ case TokenType.Identifier:
+ case TokenType.Asterisk:
+ child = getNamespacedIdentifier(false);
+ break;
+
+ case TokenType.HyphenMinus:
+ case TokenType.DecimalNumber:
+ child = tryGetPercentage() || getNamespacedIdentifier(false);
+ break;
+
+ default:
+ parseError('Unexpected input');
+ }
+
+ if (child !== null) {
+ if (combinator !== null) {
+ node.sequence.insert(List.createItem(combinator));
+ combinator = null;
+ }
+
+ node.sequence.insert(List.createItem(child));
+ }
+ }
+
+ if (combinator && combinator.name !== ' ') {
+ parseError('Unexpected combinator');
+ }
+
+ return node;
+}
+
+function getDeclarations() {
+ var child = null;
+ var declarations = new List();
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.RightCurlyBracket:
+ break scan;
+
+ case TokenType.Space:
+ case TokenType.Comment:
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.Semicolon: // ;
+ scanner.next();
+ child = null;
+ break;
+
+ default:
+ child = getDeclaration();
+ }
+
+ if (child !== null) {
+ declarations.insert(List.createItem(child));
+ }
+ }
+
+ return declarations;
+}
+
+function getBlockWithBrackets() {
+ var info = getInfo();
+ var node;
+
+ eat(TokenType.LeftCurlyBracket);
+ node = {
+ type: 'Block',
+ info: info,
+ declarations: getDeclarations()
+ };
+ eat(TokenType.RightCurlyBracket);
+
+ return node;
+}
+
+function getBlock() {
+ return {
+ type: 'Block',
+ info: getInfo(),
+ declarations: getDeclarations()
+ };
+}
+
+function getDeclaration(nested) {
+ var info = getInfo();
+ var property = getProperty();
+ var value;
+
+ eat(TokenType.Colon);
+
+ // check it's a filter
+ if (/filter$/.test(property.name.toLowerCase()) && checkProgid()) {
+ value = getFilterValue();
+ } else {
+ value = getValue(nested);
+ }
+
+ return {
+ type: 'Declaration',
+ info: info,
+ property: property,
+ value: value
+ };
+}
+
+function getProperty() {
+ var name = '';
+ var node = {
+ type: 'Property',
+ info: getInfo(),
+ name: null
+ };
+
+ for (; scanner.token !== null; scanner.next()) {
+ var type = scanner.token.type;
+
+ if (type !== TokenType.Solidus &&
+ type !== TokenType.Asterisk &&
+ type !== TokenType.DollarSign) {
+ break;
+ }
+
+ name += scanner.token.value;
+ }
+
+ node.name = name + readIdent(true);
+
+ readSC();
+
+ return node;
+}
+
+function getValue(nested) {
+ var child = null;
+ var node = {
+ type: 'Value',
+ info: getInfo(),
+ important: false,
+ sequence: new List()
+ };
+
+ readSC();
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.RightCurlyBracket:
+ case TokenType.Semicolon:
+ break scan;
+
+ case TokenType.RightParenthesis:
+ if (!nested) {
+ parseError('Unexpected input');
+ }
+ break scan;
+
+ case TokenType.Space:
+ child = getS();
+ break;
+
+ case TokenType.Comment: // ignore comments
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.NumberSign:
+ child = getVhash();
+ break;
+
+ case TokenType.Solidus:
+ case TokenType.Comma:
+ child = getOperator();
+ break;
+
+ case TokenType.LeftParenthesis:
+ case TokenType.LeftSquareBracket:
+ child = getBraces(SCOPE_VALUE);
+ break;
+
+ case TokenType.ExclamationMark:
+ node.important = getImportant();
+ child = null;
+ break;
+
+ default:
+ // check for unicode range: U+0F00, U+0F00-0FFF, u+0F00??
+ if (scanner.token.type === TokenType.Identifier) {
+ var prefix = scanner.token.value;
+ if (prefix === 'U' || prefix === 'u') {
+ if (scanner.lookupType(1, TokenType.PlusSign)) {
+ scanner.next(); // U or u
+ scanner.next(); // +
+
+ child = {
+ type: 'Identifier',
+ info: getInfo(), // FIXME: wrong position
+ name: prefix + '+' + readUnicodeRange(true)
+ };
+ break;
+ }
+ }
+ }
+
+ child = getAny(SCOPE_VALUE);
+ }
+
+ if (child !== null) {
+ node.sequence.insert(List.createItem(child));
+ }
+ }
+
+ removeTrailingSpaces(node.sequence);
+
+ return node;
+}
+
+// any = string | percentage | dimension | number | uri | functionExpression | funktion | unary | operator | ident
+function getAny(scope) {
+ switch (scanner.token.type) {
+ case TokenType.String:
+ return getString();
+
+ case TokenType.LowLine:
+ case TokenType.Identifier:
+ break;
+
+ case TokenType.FullStop:
+ case TokenType.DecimalNumber:
+ case TokenType.HyphenMinus:
+ case TokenType.PlusSign:
+ var number = tryGetNumber();
+
+ if (number !== null) {
+ if (scanner.token !== null) {
+ if (scanner.token.type === TokenType.PercentSign) {
+ return getPercentage(number);
+ } else if (scanner.token.type === TokenType.Identifier) {
+ return getDimension(number.value);
+ }
+ }
+
+ return number;
+ }
+
+ if (scanner.token.type === TokenType.HyphenMinus) {
+ var next = scanner.lookup(1);
+ if (next && (next.type === TokenType.Identifier || next.type === TokenType.HyphenMinus)) {
+ break;
+ }
+ }
+
+ if (scanner.token.type === TokenType.HyphenMinus ||
+ scanner.token.type === TokenType.PlusSign) {
+ return getOperator();
+ }
+
+ parseError('Unexpected input');
+
+ default:
+ parseError('Unexpected input');
+ }
+
+ var ident = getIdentifier(false);
+
+ if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) {
+ return getFunction(scope, ident);
+ }
+
+ return ident;
+}
+
+function readAttrselector() {
+ expectAny('Attribute selector (=, ~=, ^=, $=, *=, |=)',
+ TokenType.EqualsSign, // =
+ TokenType.Tilde, // ~=
+ TokenType.CircumflexAccent, // ^=
+ TokenType.DollarSign, // $=
+ TokenType.Asterisk, // *=
+ TokenType.VerticalLine // |=
+ );
+
+ var name;
+
+ if (scanner.token.type === TokenType.EqualsSign) {
+ name = '=';
+ scanner.next();
+ } else {
+ name = scanner.token.value + '=';
+ scanner.next();
+ eat(TokenType.EqualsSign);
+ }
+
+ return name;
+}
+
+// '[' S* attrib_name ']'
+// '[' S* attrib_name S* attrib_match S* [ IDENT | STRING ] S* attrib_flags? S* ']'
+function getAttribute() {
+ var node = {
+ type: 'Attribute',
+ info: getInfo(),
+ name: null,
+ operator: null,
+ value: null,
+ flags: null
+ };
+
+ eat(TokenType.LeftSquareBracket);
+
+ readSC();
+
+ node.name = getNamespacedIdentifier(true);
+
+ readSC();
+
+ if (scanner.token !== null && scanner.token.type !== TokenType.RightSquareBracket) {
+ // avoid case `[name i]`
+ if (scanner.token.type !== TokenType.Identifier) {
+ node.operator = readAttrselector();
+
+ readSC();
+
+ if (scanner.token !== null && scanner.token.type === TokenType.String) {
+ node.value = getString();
+ } else {
+ node.value = getIdentifier(false);
+ }
+
+ readSC();
+ }
+
+ // attribute flags
+ if (scanner.token !== null && scanner.token.type === TokenType.Identifier) {
+ node.flags = scanner.token.value;
+
+ scanner.next();
+ readSC();
+ }
+ }
+
+ eat(TokenType.RightSquareBracket);
+
+ return node;
+}
+
+function getBraces(scope) {
+ var close;
+ var child = null;
+ var node = {
+ type: 'Braces',
+ info: getInfo(),
+ open: scanner.token.value,
+ close: null,
+ sequence: new List()
+ };
+
+ if (scanner.token.type === TokenType.LeftParenthesis) {
+ close = TokenType.RightParenthesis;
+ } else {
+ close = TokenType.RightSquareBracket;
+ }
+
+ // left brace
+ scanner.next();
+
+ readSC();
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case close:
+ node.close = scanner.token.value;
+ break scan;
+
+ case TokenType.Space:
+ child = getS();
+ break;
+
+ case TokenType.Comment:
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.NumberSign: // ??
+ child = getVhash();
+ break;
+
+ case TokenType.LeftParenthesis:
+ case TokenType.LeftSquareBracket:
+ child = getBraces(scope);
+ break;
+
+ case TokenType.Solidus:
+ case TokenType.Asterisk:
+ case TokenType.Comma:
+ case TokenType.Colon:
+ child = getOperator();
+ break;
+
+ default:
+ child = getAny(scope);
+ }
+
+ if (child !== null) {
+ node.sequence.insert(List.createItem(child));
+ }
+ }
+
+ removeTrailingSpaces(node.sequence);
+
+ // right brace
+ eat(close);
+
+ return node;
+}
+
+// '.' ident
+function getClass() {
+ var info = getInfo();
+
+ eat(TokenType.FullStop);
+
+ return {
+ type: 'Class',
+ info: info,
+ name: readIdent(false)
+ };
+}
+
+// '#' ident
+function getShash() {
+ var info = getInfo();
+
+ eat(TokenType.NumberSign);
+
+ return {
+ type: 'Id',
+ info: info,
+ name: readIdent(false)
+ };
+}
+
+// + | > | ~ | /deep/
+function getCombinator() {
+ var info = getInfo();
+ var combinator;
+
+ switch (scanner.token.type) {
+ case TokenType.Space:
+ combinator = ' ';
+ scanner.next();
+ break;
+
+ case TokenType.PlusSign:
+ case TokenType.GreaterThanSign:
+ case TokenType.Tilde:
+ combinator = scanner.token.value;
+ scanner.next();
+ break;
+
+ case TokenType.Solidus:
+ combinator = '/deep/';
+ scanner.next();
+
+ expectIdentifier('deep', true);
+
+ eat(TokenType.Solidus);
+ break;
+
+ default:
+ parseError('Combinator (+, >, ~, /deep/) is expected');
+ }
+
+ return {
+ type: 'Combinator',
+ info: info,
+ name: combinator
+ };
+}
+
+// '/*' .* '*/'
+function getComment() {
+ var info = getInfo();
+ var value = scanner.token.value;
+ var len = value.length;
+
+ if (len > 4 && value.charAt(len - 2) === '*' && value.charAt(len - 1) === '/') {
+ len -= 2;
+ }
+
+ scanner.next();
+
+ return {
+ type: 'Comment',
+ info: info,
+ value: value.substring(2, len)
+ };
+}
+
+// special reader for units to avoid adjoined IE hacks (i.e. '1px\9')
+function readUnit() {
+ if (scanner.token !== null && scanner.token.type === TokenType.Identifier) {
+ var unit = scanner.token.value;
+ var backSlashPos = unit.indexOf('\\');
+
+ // no backslash in unit name
+ if (backSlashPos === -1) {
+ scanner.next();
+ return unit;
+ }
+
+ // patch token
+ scanner.token.value = unit.substr(backSlashPos);
+ scanner.token.offset += backSlashPos;
+ scanner.token.column += backSlashPos;
+
+ // return unit w/o backslash part
+ return unit.substr(0, backSlashPos);
+ }
+
+ parseError('Identifier is expected');
+}
+
+// number ident
+function getDimension(number) {
+ return {
+ type: 'Dimension',
+ info: getInfo(),
+ value: number || readNumber(),
+ unit: readUnit()
+ };
+}
+
+// number "%"
+function tryGetPercentage() {
+ var number = tryGetNumber();
+
+ if (number && scanner.token !== null && scanner.token.type === TokenType.PercentSign) {
+ return getPercentage(number);
+ }
+
+ return null;
+}
+
+function getPercentage(number) {
+ var info;
+
+ if (!number) {
+ info = getInfo();
+ number = readNumber();
+ } else {
+ info = number.info;
+ number = number.value;
+ }
+
+ eat(TokenType.PercentSign);
+
+ return {
+ type: 'Percentage',
+ info: info,
+ value: number
+ };
+}
+
+// ident '(' functionBody ')' |
+// not '(' <simpleSelector>* ')'
+function getFunction(scope, ident) {
+ var defaultArguments = getFunctionArguments;
+
+ if (!ident) {
+ ident = getIdentifier(false);
+ }
+
+ // parse special functions
+ var name = ident.name.toLowerCase();
+
+ if (specialFunctions.hasOwnProperty(scope)) {
+ if (specialFunctions[scope].hasOwnProperty(name)) {
+ return specialFunctions[scope][name](scope, ident);
+ }
+ }
+
+ return getFunctionInternal(defaultArguments, scope, ident);
+}
+
+function getFunctionInternal(functionArgumentsReader, scope, ident) {
+ var args;
+
+ eat(TokenType.LeftParenthesis);
+ args = functionArgumentsReader(scope);
+ eat(TokenType.RightParenthesis);
+
+ return {
+ type: scope === SCOPE_SELECTOR ? 'FunctionalPseudo' : 'Function',
+ info: ident.info,
+ name: ident.name,
+ arguments: args
+ };
+}
+
+function getFunctionArguments(scope) {
+ var args = new List();
+ var argument = null;
+ var child = null;
+
+ readSC();
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.RightParenthesis:
+ break scan;
+
+ case TokenType.Space:
+ child = getS();
+ break;
+
+ case TokenType.Comment: // ignore comments
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.NumberSign: // TODO: not sure it should be here
+ child = getVhash();
+ break;
+
+ case TokenType.LeftParenthesis:
+ case TokenType.LeftSquareBracket:
+ child = getBraces(scope);
+ break;
+
+ case TokenType.Comma:
+ if (argument) {
+ removeTrailingSpaces(argument.sequence);
+ } else {
+ args.insert(List.createItem({
+ type: 'Argument',
+ sequence: new List()
+ }));
+ }
+ scanner.next();
+ readSC();
+ argument = null;
+ child = null;
+ break;
+
+ case TokenType.Solidus:
+ case TokenType.Asterisk:
+ case TokenType.Colon:
+ case TokenType.EqualsSign:
+ child = getOperator();
+ break;
+
+ default:
+ child = getAny(scope);
+ }
+
+ if (argument === null) {
+ argument = {
+ type: 'Argument',
+ sequence: new List()
+ };
+ args.insert(List.createItem(argument));
+ }
+
+ if (child !== null) {
+ argument.sequence.insert(List.createItem(child));
+ }
+ }
+
+ if (argument !== null) {
+ removeTrailingSpaces(argument.sequence);
+ }
+
+ return args;
+}
+
+function getVarFunction(scope, ident) {
+ return getFunctionInternal(getVarFunctionArguments, scope, ident);
+}
+
+function getNotFunctionArguments() {
+ var args = new List();
+ var wasSelector = false;
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.RightParenthesis:
+ if (!wasSelector) {
+ parseError('Simple selector is expected');
+ }
+
+ break scan;
+
+ case TokenType.Comma:
+ if (!wasSelector) {
+ parseError('Simple selector is expected');
+ }
+
+ wasSelector = false;
+ scanner.next();
+ break;
+
+ default:
+ wasSelector = true;
+ args.insert(List.createItem(getSimpleSelector(true)));
+ }
+ }
+
+ return args;
+}
+
+function getNotFunction(scope, ident) {
+ var args;
+
+ eat(TokenType.LeftParenthesis);
+ args = getNotFunctionArguments(scope);
+ eat(TokenType.RightParenthesis);
+
+ return {
+ type: 'Negation',
+ info: ident.info,
+ // name: ident.name, // TODO: add name?
+ sequence: args // FIXME: -> arguments?
+ };
+}
+
+// var '(' ident (',' <declaration-value>)? ')'
+function getVarFunctionArguments() { // TODO: special type Variable?
+ var args = new List();
+
+ readSC();
+
+ args.insert(List.createItem({
+ type: 'Argument',
+ sequence: new List([getIdentifier(true)])
+ }));
+
+ readSC();
+
+ if (scanner.token !== null && scanner.token.type === TokenType.Comma) {
+ eat(TokenType.Comma);
+ readSC();
+
+ args.insert(List.createItem({
+ type: 'Argument',
+ sequence: new List([getValue(true)])
+ }));
+
+ readSC();
+ }
+
+ return args;
+}
+
+// url '(' ws* (string | raw) ws* ')'
+function getUri(scope, ident) {
+ var node = {
+ type: 'Url',
+ info: ident.info,
+ // name: ident.name,
+ value: null
+ };
+
+ eat(TokenType.LeftParenthesis); // (
+
+ readSC();
+
+ if (scanner.token.type === TokenType.String) {
+ node.value = getString();
+ readSC();
+ } else {
+ var rawInfo = getInfo();
+ var raw = '';
+
+ for (; scanner.token !== null; scanner.next()) {
+ var type = scanner.token.type;
+
+ if (type === TokenType.Space ||
+ type === TokenType.LeftParenthesis ||
+ type === TokenType.RightParenthesis) {
+ break;
+ }
+
+ raw += scanner.token.value;
+ }
+
+ node.value = {
+ type: 'Raw',
+ info: rawInfo,
+ value: raw
+ };
+
+ readSC();
+ }
+
+ eat(TokenType.RightParenthesis); // )
+
+ return node;
+}
+
+// expression '(' raw ')'
+function getOldIEExpression(scope, ident) {
+ var balance = 0;
+ var raw = '';
+
+ eat(TokenType.LeftParenthesis);
+
+ for (; scanner.token !== null; scanner.next()) {
+ if (scanner.token.type === TokenType.RightParenthesis) {
+ if (balance === 0) {
+ break;
+ }
+
+ balance--;
+ } else if (scanner.token.type === TokenType.LeftParenthesis) {
+ balance++;
+ }
+
+ raw += scanner.token.value;
+ }
+
+ eat(TokenType.RightParenthesis);
+
+ return {
+ type: 'Function',
+ info: ident.info,
+ name: ident.name,
+ arguments: new List([{
+ type: 'Argument',
+ sequence: new List([{
+ type: 'Raw',
+ value: raw
+ }])
+ }])
+ };
+}
+
+function readUnicodeRange(tryNext) {
+ var hex = '';
+
+ for (; scanner.token !== null; scanner.next()) {
+ if (scanner.token.type !== TokenType.DecimalNumber &&
+ scanner.token.type !== TokenType.Identifier) {
+ break;
+ }
+
+ hex += scanner.token.value;
+ }
+
+ if (!/^[0-9a-f]{1,6}$/i.test(hex)) {
+ parseError('Unexpected input');
+ }
+
+ // U+abc???
+ if (tryNext) {
+ for (; hex.length < 6 && scanner.token !== null; scanner.next()) {
+ if (scanner.token.type !== TokenType.QuestionMark) {
+ break;
+ }
+
+ hex += scanner.token.value;
+ tryNext = false;
+ }
+ }
+
+ // U+aaa-bbb
+ if (tryNext) {
+ if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
+ scanner.next();
+
+ var next = readUnicodeRange(false);
+
+ if (!next) {
+ parseError('Unexpected input');
+ }
+
+ hex += '-' + next;
+ }
+ }
+
+ return hex;
+}
+
+function readIdent(varAllowed) {
+ var name = '';
+
+ // optional first -
+ if (scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
+ name = '-';
+ scanner.next();
+
+ if (varAllowed && scanner.token !== null && scanner.token.type === TokenType.HyphenMinus) {
+ name = '--';
+ scanner.next();
+ }
+ }
+
+ expectAny('Identifier',
+ TokenType.LowLine,
+ TokenType.Identifier
+ );
+
+ if (scanner.token !== null) {
+ name += scanner.token.value;
+ scanner.next();
+
+ for (; scanner.token !== null; scanner.next()) {
+ var type = scanner.token.type;
+
+ if (type !== TokenType.LowLine &&
+ type !== TokenType.Identifier &&
+ type !== TokenType.DecimalNumber &&
+ type !== TokenType.HyphenMinus) {
+ break;
+ }
+
+ name += scanner.token.value;
+ }
+ }
+
+ return name;
+}
+
+function getNamespacedIdentifier(checkColon) {
+ if (scanner.token === null) {
+ parseError('Unexpected end of input');
+ }
+
+ var info = getInfo();
+ var name;
+
+ if (scanner.token.type === TokenType.Asterisk) {
+ checkColon = false;
+ name = '*';
+ scanner.next();
+ } else {
+ name = readIdent(false);
+ }
+
+ if (scanner.token !== null) {
+ if (scanner.token.type === TokenType.VerticalLine &&
+ scanner.lookupType(1, TokenType.EqualsSign) === false) {
+ name += '|';
+
+ if (scanner.next() !== null) {
+ if (scanner.token.type === TokenType.HyphenMinus ||
+ scanner.token.type === TokenType.Identifier ||
+ scanner.token.type === TokenType.LowLine) {
+ name += readIdent(false);
+ } else if (scanner.token.type === TokenType.Asterisk) {
+ checkColon = false;
+ name += '*';
+ scanner.next();
+ }
+ }
+ }
+ }
+
+ if (checkColon && scanner.token !== null && scanner.token.type === TokenType.Colon) {
+ scanner.next();
+ name += ':' + readIdent(false);
+ }
+
+ return {
+ type: 'Identifier',
+ info: info,
+ name: name
+ };
+}
+
+function getIdentifier(varAllowed) {
+ return {
+ type: 'Identifier',
+ info: getInfo(),
+ name: readIdent(varAllowed)
+ };
+}
+
+// ! ws* important
+function getImportant() { // TODO?
+ // var info = getInfo();
+
+ eat(TokenType.ExclamationMark);
+
+ readSC();
+
+ // return {
+ // type: 'Identifier',
+ // info: info,
+ // name: readIdent(false)
+ // };
+
+ expectIdentifier('important');
+
+ readIdent(false);
+
+ // should return identifier in future for original source restoring as is
+ // returns true for now since it's fit to optimizer purposes
+ return true;
+}
+
+// odd | even | number? n
+function getNth() {
+ expectAny('Number, odd or even',
+ TokenType.Identifier,
+ TokenType.DecimalNumber
+ );
+
+ var info = getInfo();
+ var value = scanner.token.value;
+ var cmpValue;
+
+ if (scanner.token.type === TokenType.DecimalNumber) {
+ var next = scanner.lookup(1);
+ if (next !== null &&
+ next.type === TokenType.Identifier &&
+ next.value.toLowerCase() === 'n') {
+ value += next.value;
+ scanner.next();
+ }
+ } else {
+ var cmpValue = value.toLowerCase();
+ if (cmpValue !== 'odd' && cmpValue !== 'even' && cmpValue !== 'n') {
+ parseError('Unexpected identifier');
+ }
+ }
+
+ scanner.next();
+
+ return {
+ type: 'Nth',
+ info: info,
+ value: value
+ };
+}
+
+function getNthSelector() {
+ var info = getInfo();
+ var sequence = new List();
+ var node;
+ var child = null;
+
+ eat(TokenType.Colon);
+ expectIdentifier('nth', false);
+
+ node = {
+ type: 'FunctionalPseudo',
+ info: info,
+ name: readIdent(false),
+ arguments: new List([{
+ type: 'Argument',
+ sequence: sequence
+ }])
+ };
+
+ eat(TokenType.LeftParenthesis);
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.RightParenthesis:
+ break scan;
+
+ case TokenType.Space:
+ case TokenType.Comment:
+ scanner.next();
+ child = null;
+ break;
+
+ case TokenType.HyphenMinus:
+ case TokenType.PlusSign:
+ child = getOperator();
+ break;
+
+ default:
+ child = getNth();
+ }
+
+ if (child !== null) {
+ sequence.insert(List.createItem(child));
+ }
+ }
+
+ eat(TokenType.RightParenthesis);
+
+ return node;
+}
+
+function readNumber() {
+ var wasDigits = false;
+ var number = '';
+ var offset = 0;
+
+ if (scanner.lookupType(offset, TokenType.HyphenMinus)) {
+ number = '-';
+ offset++;
+ }
+
+ if (scanner.lookupType(offset, TokenType.DecimalNumber)) {
+ wasDigits = true;
+ number += scanner.lookup(offset).value;
+ offset++;
+ }
+
+ if (scanner.lookupType(offset, TokenType.FullStop)) {
+ number += '.';
+ offset++;
+ }
+
+ if (scanner.lookupType(offset, TokenType.DecimalNumber)) {
+ wasDigits = true;
+ number += scanner.lookup(offset).value;
+ offset++;
+ }
+
+ if (wasDigits) {
+ while (offset--) {
+ scanner.next();
+ }
+
+ return number;
+ }
+
+ return null;
+}
+
+function tryGetNumber() {
+ var info = getInfo();
+ var number = readNumber();
+
+ if (number !== null) {
+ return {
+ type: 'Number',
+ info: info,
+ value: number
+ };
+ }
+
+ return null;
+}
+
+// '/' | '*' | ',' | ':' | '=' | '+' | '-'
+// TODO: remove '=' since it's wrong operator, but theat as operator
+// to make old things like `filter: alpha(opacity=0)` works
+function getOperator() {
+ var node = {
+ type: 'Operator',
+ info: getInfo(),
+ value: scanner.token.value
+ };
+
+ scanner.next();
+
+ return node;
+}
+
+function getFilterValue() { // TODO
+ var progid;
+ var node = {
+ type: 'Value',
+ info: getInfo(),
+ important: false,
+ sequence: new List()
+ };
+
+ while (progid = checkProgid()) {
+ node.sequence.insert(List.createItem(getProgid(progid)));
+ }
+
+ readSC(node);
+
+ if (scanner.token !== null && scanner.token.type === TokenType.ExclamationMark) {
+ node.important = getImportant();
+ }
+
+ return node;
+}
+
+// 'progid:' ws* 'DXImageTransform.Microsoft.' ident ws* '(' .* ')'
+function checkProgid() {
+ function checkSC(offset) {
+ for (var cursor; cursor = scanner.lookup(offset); offset++) {
+ if (cursor.type !== TokenType.Space &&
+ cursor.type !== TokenType.Comment) {
+ break;
+ }
+ }
+
+ return offset;
+ }
+
+ var offset = checkSC(0);
+
+ if (scanner.lookup(offset + 1) === null ||
+ scanner.lookup(offset + 0).value.toLowerCase() !== 'progid' ||
+ scanner.lookup(offset + 1).type !== TokenType.Colon) {
+ return false; // fail
+ }
+
+ offset += 2;
+ offset = checkSC(offset);
+
+ if (scanner.lookup(offset + 5) === null ||
+ scanner.lookup(offset + 0).value.toLowerCase() !== 'dximagetransform' ||
+ scanner.lookup(offset + 1).type !== TokenType.FullStop ||
+ scanner.lookup(offset + 2).value.toLowerCase() !== 'microsoft' ||
+ scanner.lookup(offset + 3).type !== TokenType.FullStop ||
+ scanner.lookup(offset + 4).type !== TokenType.Identifier) {
+ return false; // fail
+ }
+
+ offset += 5;
+ offset = checkSC(offset);
+
+ if (scanner.lookupType(offset, TokenType.LeftParenthesis) === false) {
+ return false; // fail
+ }
+
+ for (var cursor; cursor = scanner.lookup(offset); offset++) {
+ if (cursor.type === TokenType.RightParenthesis) {
+ return cursor;
+ }
+ }
+
+ return false;
+}
+
+function getProgid(progidEnd) {
+ var value = '';
+ var node = {
+ type: 'Progid',
+ info: getInfo(),
+ value: null
+ };
+
+ if (!progidEnd) {
+ progidEnd = checkProgid();
+ }
+
+ if (!progidEnd) {
+ parseError('progid is expected');
+ }
+
+ readSC(node);
+
+ var rawInfo = getInfo();
+ for (; scanner.token && scanner.token !== progidEnd; scanner.next()) {
+ value += scanner.token.value;
+ }
+
+ eat(TokenType.RightParenthesis);
+ value += ')';
+
+ node.value = {
+ type: 'Raw',
+ info: rawInfo,
+ value: value
+ };
+
+ readSC(node);
+
+ return node;
+}
+
+// <pseudo-element> | <nth-selector> | <pseudo-class>
+function getPseudo() {
+ var next = scanner.lookup(1);
+
+ if (next === null) {
+ scanner.next();
+ parseError('Colon or identifier is expected');
+ }
+
+ if (next.type === TokenType.Colon) {
+ return getPseudoElement();
+ }
+
+ if (next.type === TokenType.Identifier &&
+ next.value.toLowerCase() === 'nth') {
+ return getNthSelector();
+ }
+
+ return getPseudoClass();
+}
+
+// :: ident
+function getPseudoElement() {
+ var info = getInfo();
+
+ eat(TokenType.Colon);
+ eat(TokenType.Colon);
+
+ return {
+ type: 'PseudoElement',
+ info: info,
+ name: readIdent(false)
+ };
+}
+
+// : ( ident | function )
+function getPseudoClass() {
+ var info = getInfo();
+ var ident = eat(TokenType.Colon) && getIdentifier(false);
+
+ if (scanner.token !== null && scanner.token.type === TokenType.LeftParenthesis) {
+ return getFunction(SCOPE_SELECTOR, ident);
+ }
+
+ return {
+ type: 'PseudoClass',
+ info: info,
+ name: ident.name
+ };
+}
+
+// ws
+function getS() {
+ var node = {
+ type: 'Space'
+ // value: scanner.token.value
+ };
+
+ scanner.next();
+
+ return node;
+}
+
+function readSC() {
+ // var nodes = [];
+
+ scan:
+ while (scanner.token !== null) {
+ switch (scanner.token.type) {
+ case TokenType.Space:
+ scanner.next();
+ // nodes.push(getS());
+ break;
+
+ case TokenType.Comment:
+ scanner.next();
+ // nodes.push(getComment());
+ break;
+
+ default:
+ break scan;
+ }
+ }
+
+ return null;
+
+ // return nodes.length ? new List(nodes) : null;
+}
+
+// node: String
+function getString() {
+ var node = {
+ type: 'String',
+ info: getInfo(),
+ value: scanner.token.value
+ };
+
+ scanner.next();
+
+ return node;
+}
+
+// # ident
+function getVhash() {
+ var info = getInfo();
+ var value;
+
+ eat(TokenType.NumberSign);
+
+ expectAny('Number or identifier',
+ TokenType.DecimalNumber,
+ TokenType.Identifier
+ );
+
+ value = scanner.token.value;
+
+ if (scanner.token.type === TokenType.DecimalNumber &&
+ scanner.lookupType(1, TokenType.Identifier)) {
+ scanner.next();
+ value += scanner.token.value;
+ }
+
+ scanner.next();
+
+ return {
+ type: 'Hash',
+ info: info,
+ value: value
+ };
+}
+
+module.exports = function parse(source, options) {
+ var ast;
+
+ if (!options || typeof options !== 'object') {
+ options = {};
+ }
+
+ var context = options.context || 'stylesheet';
+ needPositions = Boolean(options.positions);
+ filename = options.filename || '<unknown>';
+
+ if (!initialContext.hasOwnProperty(context)) {
+ throw new Error('Unknown context `' + context + '`');
+ }
+
+ scanner = new Scanner(source, blockMode.hasOwnProperty(context), options.line, options.column);
+ scanner.next();
+ ast = initialContext[context]();
+
+ scanner = null;
+
+ // console.log(JSON.stringify(ast, null, 4));
+ return ast;
+};
diff --git a/node_modules/csso/lib/parser/scanner.js b/node_modules/csso/lib/parser/scanner.js
new file mode 100644
index 00000000..066fa8e5
--- /dev/null
+++ b/node_modules/csso/lib/parser/scanner.js
@@ -0,0 +1,380 @@
+'use strict';
+
+var TokenType = require('./const.js').TokenType;
+
+var TAB = 9;
+var N = 10;
+var F = 12;
+var R = 13;
+var SPACE = 32;
+var DOUBLE_QUOTE = 34;
+var QUOTE = 39;
+var RIGHT_PARENTHESIS = 41;
+var STAR = 42;
+var SLASH = 47;
+var BACK_SLASH = 92;
+var UNDERSCORE = 95;
+var LEFT_CURLY_BRACE = 123;
+var RIGHT_CURLY_BRACE = 125;
+
+var WHITESPACE = 1;
+var PUNCTUATOR = 2;
+var DIGIT = 3;
+var STRING = 4;
+
+var PUNCTUATION = {
+ 9: TokenType.Tab, // '\t'
+ 10: TokenType.Newline, // '\n'
+ 13: TokenType.Newline, // '\r'
+ 32: TokenType.Space, // ' '
+ 33: TokenType.ExclamationMark, // '!'
+ 34: TokenType.QuotationMark, // '"'
+ 35: TokenType.NumberSign, // '#'
+ 36: TokenType.DollarSign, // '$'
+ 37: TokenType.PercentSign, // '%'
+ 38: TokenType.Ampersand, // '&'
+ 39: TokenType.Apostrophe, // '\''
+ 40: TokenType.LeftParenthesis, // '('
+ 41: TokenType.RightParenthesis, // ')'
+ 42: TokenType.Asterisk, // '*'
+ 43: TokenType.PlusSign, // '+'
+ 44: TokenType.Comma, // ','
+ 45: TokenType.HyphenMinus, // '-'
+ 46: TokenType.FullStop, // '.'
+ 47: TokenType.Solidus, // '/'
+ 58: TokenType.Colon, // ':'
+ 59: TokenType.Semicolon, // ';'
+ 60: TokenType.LessThanSign, // '<'
+ 61: TokenType.EqualsSign, // '='
+ 62: TokenType.GreaterThanSign, // '>'
+ 63: TokenType.QuestionMark, // '?'
+ 64: TokenType.CommercialAt, // '@'
+ 91: TokenType.LeftSquareBracket, // '['
+ 93: TokenType.RightSquareBracket, // ']'
+ 94: TokenType.CircumflexAccent, // '^'
+ 95: TokenType.LowLine, // '_'
+ 123: TokenType.LeftCurlyBracket, // '{'
+ 124: TokenType.VerticalLine, // '|'
+ 125: TokenType.RightCurlyBracket, // '}'
+ 126: TokenType.Tilde // '~'
+};
+var SYMBOL_CATEGORY_LENGTH = Math.max.apply(null, Object.keys(PUNCTUATION)) + 1;
+var SYMBOL_CATEGORY = new Uint32Array(SYMBOL_CATEGORY_LENGTH);
+var IS_PUNCTUATOR = new Uint32Array(SYMBOL_CATEGORY_LENGTH);
+
+// fill categories
+Object.keys(PUNCTUATION).forEach(function(key) {
+ SYMBOL_CATEGORY[Number(key)] = PUNCTUATOR;
+ IS_PUNCTUATOR[Number(key)] = PUNCTUATOR;
+}, SYMBOL_CATEGORY);
+
+// don't treat as punctuator
+IS_PUNCTUATOR[UNDERSCORE] = 0;
+
+for (var i = 48; i <= 57; i++) {
+ SYMBOL_CATEGORY[i] = DIGIT;
+}
+
+SYMBOL_CATEGORY[SPACE] = WHITESPACE;
+SYMBOL_CATEGORY[TAB] = WHITESPACE;
+SYMBOL_CATEGORY[N] = WHITESPACE;
+SYMBOL_CATEGORY[R] = WHITESPACE;
+SYMBOL_CATEGORY[F] = WHITESPACE;
+
+SYMBOL_CATEGORY[QUOTE] = STRING;
+SYMBOL_CATEGORY[DOUBLE_QUOTE] = STRING;
+
+//
+// scanner
+//
+
+var Scanner = function(source, initBlockMode, initLine, initColumn) {
+ this.source = source;
+
+ this.pos = source.charCodeAt(0) === 0xFEFF ? 1 : 0;
+ this.eof = this.pos === this.source.length;
+ this.line = typeof initLine === 'undefined' ? 1 : initLine;
+ this.lineStartPos = typeof initColumn === 'undefined' ? -1 : -initColumn;
+
+ this.minBlockMode = initBlockMode ? 1 : 0;
+ this.blockMode = this.minBlockMode;
+ this.urlMode = false;
+
+ this.prevToken = null;
+ this.token = null;
+ this.buffer = [];
+};
+
+Scanner.prototype = {
+ lookup: function(offset) {
+ if (offset === 0) {
+ return this.token;
+ }
+
+ for (var i = this.buffer.length; !this.eof && i < offset; i++) {
+ this.buffer.push(this.getToken());
+ }
+
+ return offset <= this.buffer.length ? this.buffer[offset - 1] : null;
+ },
+ lookupType: function(offset, type) {
+ var token = this.lookup(offset);
+
+ return token !== null && token.type === type;
+ },
+ next: function() {
+ var newToken = null;
+
+ if (this.buffer.length !== 0) {
+ newToken = this.buffer.shift();
+ } else if (!this.eof) {
+ newToken = this.getToken();
+ }
+
+ this.prevToken = this.token;
+ this.token = newToken;
+
+ return newToken;
+ },
+
+ tokenize: function() {
+ var tokens = [];
+
+ for (; this.pos < this.source.length; this.pos++) {
+ tokens.push(this.getToken());
+ }
+
+ return tokens;
+ },
+
+ getToken: function() {
+ var code = this.source.charCodeAt(this.pos);
+ var line = this.line;
+ var column = this.pos - this.lineStartPos;
+ var offset = this.pos;
+ var next;
+ var type;
+ var value;
+
+ switch (code < SYMBOL_CATEGORY_LENGTH ? SYMBOL_CATEGORY[code] : 0) {
+ case DIGIT:
+ type = TokenType.DecimalNumber;
+ value = this.readDecimalNumber();
+ break;
+
+ case STRING:
+ type = TokenType.String;
+ value = this.readString(code);
+ break;
+
+ case WHITESPACE:
+ type = TokenType.Space;
+ value = this.readSpaces();
+ break;
+
+ case PUNCTUATOR:
+ if (code === SLASH) {
+ next = this.pos + 1 < this.source.length ? this.source.charCodeAt(this.pos + 1) : 0;
+
+ if (next === STAR) { // /*
+ type = TokenType.Comment;
+ value = this.readComment();
+ break;
+ } else if (next === SLASH && !this.urlMode) { // //
+ if (this.blockMode > 0) {
+ var skip = 2;
+
+ while (this.source.charCodeAt(this.pos + 2) === SLASH) {
+ skip++;
+ }
+
+ type = TokenType.Identifier;
+ value = this.readIdentifier(skip);
+
+ this.urlMode = this.urlMode || value === 'url';
+ } else {
+ type = TokenType.Unknown;
+ value = this.readUnknown();
+ }
+ break;
+ }
+ }
+
+ type = PUNCTUATION[code];
+ value = String.fromCharCode(code);
+ this.pos++;
+
+ if (code === RIGHT_PARENTHESIS) {
+ this.urlMode = false;
+ } else if (code === LEFT_CURLY_BRACE) {
+ this.blockMode++;
+ } else if (code === RIGHT_CURLY_BRACE) {
+ if (this.blockMode > this.minBlockMode) {
+ this.blockMode--;
+ }
+ }
+
+ break;
+
+ default:
+ type = TokenType.Identifier;
+ value = this.readIdentifier(0);
+
+ this.urlMode = this.urlMode || value === 'url';
+ }
+
+ this.eof = this.pos === this.source.length;
+
+ return {
+ type: type,
+ value: value,
+
+ offset: offset,
+ line: line,
+ column: column
+ };
+ },
+
+ isNewline: function(code) {
+ if (code === N || code === F || code === R) {
+ if (code === R && this.pos + 1 < this.source.length && this.source.charCodeAt(this.pos + 1) === N) {
+ this.pos++;
+ }
+
+ this.line++;
+ this.lineStartPos = this.pos;
+ return true;
+ }
+
+ return false;
+ },
+
+ readSpaces: function() {
+ var start = this.pos;
+
+ for (; this.pos < this.source.length; this.pos++) {
+ var code = this.source.charCodeAt(this.pos);
+
+ if (!this.isNewline(code) && code !== SPACE && code !== TAB) {
+ break;
+ }
+ }
+
+ return this.source.substring(start, this.pos);
+ },
+
+ readComment: function() {
+ var start = this.pos;
+
+ for (this.pos += 2; this.pos < this.source.length; this.pos++) {
+ var code = this.source.charCodeAt(this.pos);
+
+ if (code === STAR) { // */
+ if (this.source.charCodeAt(this.pos + 1) === SLASH) {
+ this.pos += 2;
+ break;
+ }
+ } else {
+ this.isNewline(code);
+ }
+ }
+
+ return this.source.substring(start, this.pos);
+ },
+
+ readUnknown: function() {
+ var start = this.pos;
+
+ for (this.pos += 2; this.pos < this.source.length; this.pos++) {
+ if (this.isNewline(this.source.charCodeAt(this.pos), this.source)) {
+ break;
+ }
+ }
+
+ return this.source.substring(start, this.pos);
+ },
+
+ readString: function(quote) {
+ var start = this.pos;
+ var res = '';
+
+ for (this.pos++; this.pos < this.source.length; this.pos++) {
+ var code = this.source.charCodeAt(this.pos);
+
+ if (code === BACK_SLASH) {
+ var end = this.pos++;
+
+ if (this.isNewline(this.source.charCodeAt(this.pos), this.source)) {
+ res += this.source.substring(start, end);
+ start = this.pos + 1;
+ }
+ } else if (code === quote) {
+ this.pos++;
+ break;
+ }
+ }
+
+ return res + this.source.substring(start, this.pos);
+ },
+
+ readDecimalNumber: function() {
+ var start = this.pos;
+ var code;
+
+ for (this.pos++; this.pos < this.source.length; this.pos++) {
+ code = this.source.charCodeAt(this.pos);
+
+ if (code < 48 || code > 57) { // 0 .. 9
+ break;
+ }
+ }
+
+ return this.source.substring(start, this.pos);
+ },
+
+ readIdentifier: function(skip) {
+ var start = this.pos;
+
+ for (this.pos += skip; this.pos < this.source.length; this.pos++) {
+ var code = this.source.charCodeAt(this.pos);
+
+ if (code === BACK_SLASH) {
+ this.pos++;
+
+ // skip escaped unicode sequence that can ends with space
+ // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
+ for (var i = 0; i < 7 && this.pos + i < this.source.length; i++) {
+ code = this.source.charCodeAt(this.pos + i);
+
+ if (i !== 6) {
+ if ((code >= 48 && code <= 57) || // 0 .. 9
+ (code >= 65 && code <= 70) || // A .. F
+ (code >= 97 && code <= 102)) { // a .. f
+ continue;
+ }
+ }
+
+ if (i > 0) {
+ this.pos += i - 1;
+ if (code === SPACE || code === TAB || this.isNewline(code)) {
+ this.pos++;
+ }
+ }
+
+ break;
+ }
+ } else if (code < SYMBOL_CATEGORY_LENGTH &&
+ IS_PUNCTUATOR[code] === PUNCTUATOR) {
+ break;
+ }
+ }
+
+ return this.source.substring(start, this.pos);
+ }
+};
+
+// warm up tokenizer to elimitate code branches that never execute
+// fix soft deoptimizations (insufficient type feedback)
+new Scanner('\n\r\r\n\f//""\'\'/**/1a;.{url(a)}').lookup(1e3);
+
+module.exports = Scanner;