diff options
Diffstat (limited to 'node_modules/csso/lib/parser')
| -rw-r--r-- | node_modules/csso/lib/parser/const.js | 46 | ||||
| -rw-r--r-- | node_modules/csso/lib/parser/index.js | 1870 | ||||
| -rw-r--r-- | node_modules/csso/lib/parser/scanner.js | 380 |
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; |
