@extjs/sencha-cmd-linux-32
Version:
Productivity and performance optimization tool for building applications with Sencha Ext JS and Sencha Touch.
671 lines (600 loc) • 18.3 kB
JavaScript
"use strict";
var Fashion = require('../export/Base.js'),
Base = Fashion.Base;
var Tokens = require('./Tokens.js'),
Operator = Tokens.Operator,
Ident = Tokens.Ident,
StringLiteral = Tokens.StringLiteral,
Percentage = Tokens.Percentage,
Length = Tokens.Length,
Time = Tokens.Time,
Angle = Tokens.Angle,
NumberLiteral = Tokens.NumberLiteral,
Class = Tokens.Class,
Hash = Tokens.Hash,
Variable = Tokens.Variable,
Directive = Tokens.Directive,
EOF = Tokens.EOF;
var _limit = 0xFF,
_chars = new Array(_limit),
// _EOF = new EOF(null);
_EOF = null;
/*
1 = alpha
2 = digit
4 = name
8 = hex
*/
for (var c = 0; c < _limit; c++) {
var ch = String.fromCharCode(c),
mask = 0;
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z')) {
mask |= 1;
}
if ((ch >= '0') && (ch <= '9')) {
mask |= 2;
}
if ((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') ||
(ch === '-') || (ch === '_') ||
(c >= 128 && c <= _limit && c !== 215 && c !== 247) ||
ch === '\\') {
mask |= 4;
}
if ((ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'f') ||
(ch >= 'A' && ch <= 'F')) {
mask |= 8;
}
_chars[c] = mask;
}
function isAlpha(ch) {
return _chars[ch.charCodeAt(0)] & 1;
}
function isDigit(ch) {
return _chars[ch.charCodeAt(0)] & 2;
}
// http://en.wikipedia.org/wiki/Latin-1
function isNameChar(ch) {
return _chars[ch.charCodeAt(0)] & 4;
}
function isHexDigit(ch) {
return _chars[ch.charCodeAt(0)] & 8;
}
// px, pt, pc, cm, mm, in, em, rem, ex, vw, vh, vmin, vmax
function isLength(unit) {
var ch1 = unit.charAt(0).toLowerCase(),
ch2 = unit.charAt(1).toLowerCase(),
ch3 = unit.charAt(2).toLowerCase(),
ch4 = unit.charAt(3).toLowerCase();
if (ch1 === 'p') {
return (ch2 === 'x' || ch2 === 't' || ch2 === 'c');
}
if (ch2 === 'm') {
if (ch1 === 'c' || ch1 === 'm' || ch1 === 'e'){
return 2;
}
}
if (ch2 === 'x') {
return ch1 === 'e';
}
if (ch3 === 'm') {
if (ch1 === 'r' && ch2 === 'e') {
// return the length of the unit
return 3;
}
}
if (ch1 === 'x' && isHexDigit(ch2)) {
var len = 1;
while (isHexDigit(unit[len])) {
len++;
}
return len;
}
if (ch1 === 'v') {
if (ch2 === 'w' || ch2 === 'h') {
return 2;
}
if (ch2 === 'm') {
if (ch3 === 'i' && ch4 === 'n') {
return 4;
}
if (ch3 === 'a' && ch4 === 'x') {
return 4;
}
}
}
return false;
}
// s, ms
function isTime(unit) {
if (unit.length === 1) {
return unit === 's';
} else if (unit.length === 2) {
return unit === 'ms';
}
return false;
}
// deg, rad
function isAngle(unit) {
var ch = unit[0];
if (ch === 'd' || ch === 'D') {
return unit.toLowerCase() === 'deg';
}
if (ch === 'r' || ch === 'R') {
return unit.toLowerCase() === 'rad';
}
return false;
}
function debug(message) {
//console.log(message);
}
function info(message) {
//console.log(message);
}
class Scanner extends Base {
constructor(style, file) {
super();
this.style = style;
this.lineNumber = this.style.length ? 1 : 0;
this.currentFile = file || Fashion.currentFile;
this.docs = [];
this.tokenBuff = [];
}
next(advance) {
var me = this,
start = me.index,
startLine = me.lineNumber,
token = !advance && me.tokenBuff.shift();
if (!token) {
token = me._advance();
if (token) {
token.startIdx = start;
token.startLine = startLine;
}
}
return token;
}
// Get the next token and return it.
// Loosely based on http://www.w3.org/TR/CSS2/grammar.html#scanner
// TODO: nonascii, badcomments, escape
_advance() {
var me = this,
style = me.style,
length = style.length,
ch, ch2, ch3, start, str, level, negate, charOffset, value;
// Go past white space, block comment, and single-line comment
while (true) {
ch = style[me.index];
// Skip white space or any other control characters
while (me.index < length && (ch <= ' ' || ch >= 128)) {
if (ch === '\n') {
me.lineNumber += 1;
me.start = me.index;
}
me.index += 1;
ch = style[me.index];
}
ch2 = style[me.index + 1];
// Block comment
if (ch === '/' && ch2 === '*') {
me.index += 1;
start = me.index + 1;
while (me.index < length) {
ch = style[me.index];
ch2 = style[me.index + 1];
if (ch === '\n') {
me.lineNumber += 1;
me.start = me.index;
}
if (ch === '*' && ch2 === '/') {
me.index += 2;
break;
}
me.index += 1;
}
me.docs.push(style.substring(start - 2, me.index));
continue;
}
// Single-line comment
if (ch === '/' && ch2 === '/') {
me.index += 1;
start = me.index;
while (me.index < length) {
ch = style[me.index];
if (ch === '\r' || ch === '\n') {
break;
}
me.index += 1;
}
me.docs.push(style.substring(start - 1, me.index));
continue;
}
break;
}
start = me.index;
if (start >= length) {
return _EOF;
}
ch = style[me.index];
ch2 = style[me.index + 1];
ch3 = style[me.index + 2];
// Identifier
if (
(isNameChar(ch) && !isDigit(ch) && ch !== '-') ||
(ch === '-' && isNameChar(ch2) && !isDigit(ch2)) ||
(ch === '#' && ch2 === '{')
) {
level = 0;
me.index += 1;
if (ch === '#' && ch2 === '{') {
level += 1;
me.index += 1;
}
if (ch === '\\') {
// automatically consume the escaped character
me.index += 1;
}
while (me.index < length) {
ch = style[me.index];
ch2 = style[me.index + 1];
if (isNameChar(ch)) {
me.index += 1;
continue;
}
if (ch === '\\') {
me.index += 2;
continue;
}
if (ch == ">") {
me.index += 1;
//level += 1;
continue;
}
if (ch === '#' && ch2 === '{') {
level += 1;
me.index += 2;
continue;
}
if (level > 0) {
me.index += 1;
if (ch === '}') {
level -= 1;
}
continue;
}
break;
}
str = style.substring(start, me.index).toLowerCase();
if (str === 'or' || str === 'and' || str === 'not') {
return new Operator(me, str);
}
return new Ident(me, style.substring(start, me.index));
}
// String
if ((ch === '\'' || ch === '"') ||
(ch === '\\' && (ch2 === "'" || ch2 === '"'))) { // quotes may be escaped
charOffset = (ch === '\\') ? 2 : 1;
// quotes may be escaped.
me.index += charOffset;
start = me.index;
var openCh = (ch === '\\') ? ch2 : ch;
level = 0;
var buff = '';
while (me.index < length) {
ch = style[me.index];
me.index++;
if (ch === '\\') {
ch2 = style[me.index];
if (ch2 === '\n' || ch2 === "\r") {
me.index++;
continue;
}
buff += ch;
ch = style[me.index];
me.index++;
if (!level && charOffset === 2 && openCh === style[me.index]) {
break;
}
}
else if (ch === '#') {
if (style[me.index] === '{') {
level++;
}
}
else if (ch === '}') {
if (level) {
level--;
}
}
else if (!level && ch === openCh) {
break;
}
buff += ch;
}
return new StringLiteral(me, buff, style[start - 1]);
}
// Number
if (isDigit(ch) || (ch === '.' && isDigit(ch2)) || (ch === '-' && isDigit(ch2)) || (ch === '-' && ch2 === '.' && isDigit(ch3))) {
if (ch === '-') {
me.index += 1;
}
me.index += 1;
while (me.index < length) {
ch = style[me.index];
if (ch < '0' || ch > '9') {
break;
}
me.index += 1;
}
if (ch === '\\') {
me.index += 1;
ch = style[me.index];
}
if (ch === '.') {
me.index += 1;
while (me.index < length) {
ch = style[me.index];
if (ch < '0' || ch > '9') {
break;
}
me.index += 1;
}
}
// Percentage
if (ch === '%') {
me.index += 1;
var pcnt = new Percentage(me, style.substring(start, me.index));
pcnt.start = start;
pcnt.end = me.index;
return pcnt;
}
// Length
if (ch !== ' ') {
var unitLen = isLength(style.substr(me.index, 10));
if (unitLen) {
me.index += (unitLen === true) ? 2 : unitLen;
return new Length(me, style.substring(start, me.index));
}
if (isTime(style.substr(me.index, 1))) {
me.index += 1;
return new Time(me, style.substring(start, me.index));
}
if (isTime(style.substr(me.index, 2))) {
me.index += 2;
return new Time(me, style.substring(start, me.index));
}
if (isAngle(style.substr(me.index, 3))) {
me.index += 3;
return new Angle(me, style.substring(start, me.index));
}
}
return new NumberLiteral(me, style.substring(start, me.index));
}
// Class
if (ch === '.') {
level = 0;
me.index += 1;
ch = style[me.index];
if (ch === '{') {
level += 1;
me.index += 1;
}
while (me.index < length) {
ch = style[me.index];
ch2 = style[me.index + 1];
if (isNameChar(ch)) {
me.index += 1;
continue;
}
if (ch === '#' && ch2 === '{') {
level += 1;
me.index += 2;
continue;
}
if (level > 0) {
me.index += 1;
if (ch === '}') {
level -= 1;
}
continue;
}
break;
}
return new Class(me, style.substring(start, me.index));
}
// Hash
if (ch === '#') {
level = 0;
me.index += 1;
ch = style[me.index];
if (ch === '{') {
level += 1;
me.index += 1;
}
while (me.index < length) {
ch = style[me.index];
ch2 = style[me.index + 1];
if (isNameChar(ch)) {
me.index += 1;
continue;
}
if (ch === '#' && ch2 === '{') {
level += 1;
me.index += 2;
continue;
}
if (level > 0) {
me.index += 1;
if (ch === '}') {
level -= 1;
}
continue;
}
break;
}
return new Hash(me, style.substring(start, me.index));
}
// Variable
if (ch === '$' || (ch === '-' && ch2 === '$')) {
if (ch === '-') {
negate = true;
start += 1;
me.index += 1;
}
me.index += 1;
while (me.index < length) {
ch = style[me.index];
if (isNameChar(ch)) {
me.index += 1;
} else {
break;
}
}
return new Variable(me, style.substring(start, me.index), negate);
}
// Directive, e.g. @import
if (ch === '@') {
me.index += 1;
while (me.index < length) {
ch = style[me.index];
if (!isAlpha(ch) && ch !== '-') {
break;
}
me.index += 1;
}
value = style.substring(start, me.index);
// If the value is not a SASS directive, then treat it as an identifier
// This prevents a parsing error on CSS @-rules like @font-face
// id: "@",
return me.directives[value]
? new Directive(me, value)
: new Ident(me, value);
}
// Fallback to single-character or two-character operator
me.index += 1;
str = ch;
if (ch === '=' && ch2 === '=') {
str = '==';
me.index += 1;
}
if (ch === '~' && ch2 === '=') {
str = '~=';
me.index += 1;
}
if (ch === '|' && ch2 === '=') {
str = '|=';
me.index += 1;
}
if (ch === '^' && ch2 === '=') {
str = '^=';
me.index += 1;
}
if (ch === '$' && ch2 === '=') {
str = '$=';
me.index += 1;
}
if (ch === '*' && ch2 === '=') {
str = '*=';
me.index += 1;
}
if (ch === '!' && ch2 === '=') {
str = '!=';
me.index += 1;
}
if (ch === '<' && ch2 === '=') {
str = '<=';
me.index += 1;
}
if (ch === '>' && ch2 === '=') {
str = '>=';
me.index += 1;
}
return new Operator(me, str);
} // next()
flushDocs() {
if (this.docs.length > 0) {
var docs = this.docs;
this.docs = [];
return docs;
}
return null;
}
// Lookahead the next token (without consuming it).
peek(i) {
var buff = this.tokenBuff,
len = buff.length;
i = i || 1;
for (var x = len; x < i; x++) {
buff.push(this.next(true));
}
return buff[i - 1] || _EOF;
}
setIndex(index) {
this.index = index;
this.tokenBuff = [];
}
// Check if the next token matches the expected operator.
// If not, throw an exception.
expect(op) {
var token = this.next(),
lineNo = this.lineNumber - 1,
fileName = this.currentFile || "sass-content",
message = [
'Expected \'',
op,
'\' but saw \'',
token ? token.value : '(null token)',
'\'',
' => ',
fileName,
':',
lineNo + 1,
':',
this.index - this.start + 1
].join('');
if (!token) {
Fashion.raise(message);
}
if (!token.isOperator || token.value !== op) {
Fashion.raise(message);
}
}
}
Fashion.apply(Scanner.prototype, {
isFashionScanner: true,
// The list of SASS directives. Everything else beginning with "@" will be
// assumed to be a css @-rule, an treated as an identifier. e.g. @font-face
// treated as a normal identifier with no special processing for now.
directives: {
"@charset": true,
"@import": true,
"@extend": true,
"@debug": true,
"@warn": true,
"@error": true,
"@if": true,
"@else": true,
"@for": true,
"@each": true,
"@while": true,
"@mixin": true,
"@include": true,
"@function": true,
"@return": true,
"@debugger": true,
"@elseif": true,
"@content": true,
"@require": true
},
index: 0,
docs: undefined,
start: undefined,
style: undefined,
currentFile: undefined,
lineNumber: undefined
});
Scanner.EOF = _EOF;
module.exports = Scanner;