From 89a4e82095840a69fcc3d9ba32e88e75c5ae6b35 Mon Sep 17 00:00:00 2001 From: Mathias Bynens Date: Fri, 25 Jul 2014 12:03:12 -0300 Subject: [PATCH] Allow running the tests with `npm test` Also, make sure `npm install` installs the test dependencies. --- package.json | 69 +- parse-css.js | 2326 +++++++++++++++++++++++++------------------------- tests.js | 159 ++-- 3 files changed, 1276 insertions(+), 1278 deletions(-) diff --git a/package.json b/package.json index fb6e498..b1e62b1 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,38 @@ -{ - "name": "css-parser", - "version": "0.1.0", - "description": "Standards-based CSS parser, based on the CSS Syntax spec.", - "keywords": ["css", "parser"], - "author": "Tab Atkins ", - "repository": { - "type": "git", - "url": "git://github.com/tabatkins/css-parser.git" - }, - "main": "index", - "homepage": "https://github.com/tabatkins/css-parser", - "contributors": [ - { - "name": "Tab Atkins Jr." - } - ], - "bugs": { - "url": "https://github.com/tabatkins/css-parser/issues", - "email": "jackalmage@gmail.com" - }, - "dependencies": {}, - "devDependencies": {}, - "licenses": [ - { - "type": "CC0", - "url": "http://github.com/tabatkins/css-parser/raw/master/LICENSE" - } - ], - "scripts": {} -} \ No newline at end of file +{ + "name": "parse-css", + "version": "0.1.0", + "description": "Standards-based CSS parser, based on the CSS Syntax spec.", + "keywords": [ + "css", + "parser" + ], + "author": "Tab Atkins ", + "repository": { + "type": "git", + "url": "git://github.com/tabatkins/parse-css.git" + }, + "main": "index", + "homepage": "https://github.com/tabatkins/parse-css", + "contributors": [ + { + "name": "Tab Atkins Jr." + } + ], + "bugs": { + "url": "https://github.com/tabatkins/parse-css/issues", + "email": "jackalmage@gmail.com" + }, + "dependencies": {}, + "devDependencies": { + "ansidiff": "^1.0.0" + }, + "licenses": [ + { + "type": "CC0", + "url": "http://github.com/tabatkins/parse-css/raw/master/LICENSE" + } + ], + "scripts": { + "test": "node tests.js" + } +} diff --git a/parse-css.js b/parse-css.js index b87e18c..e78757f 100644 --- a/parse-css.js +++ b/parse-css.js @@ -1,1163 +1,1163 @@ -(function (root, factory) { - // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, - // Rhino, and plain browser loading. - if (typeof define === 'function' && define.amd) { - define(['exports'], factory); - } else if (typeof exports !== 'undefined') { - factory(exports); - } else { - factory(root); - } -}(this, function (exports) { - -var between = function (num, first, last) { return num >= first && num <= last; } -function digit(code) { return between(code, 0x30,0x39); } -function hexdigit(code) { return digit(code) || between(code, 0x41,0x46) || between(code, 0x61,0x66); } -function uppercaseletter(code) { return between(code, 0x41,0x5a); } -function lowercaseletter(code) { return between(code, 0x61,0x7a); } -function letter(code) { return uppercaseletter(code) || lowercaseletter(code); } -function nonascii(code) { return code >= 0x80; } -function namestartchar(code) { return letter(code) || nonascii(code) || code == 0x5f; } -function namechar(code) { return namestartchar(code) || digit(code) || code == 0x2d; } -function nonprintable(code) { return between(code, 0,8) || code == 0xb || between(code, 0xe,0x1f) || code == 0x7f; } -function newline(code) { return code == 0xa || code == 0xc; } -function whitespace(code) { return newline(code) || code == 9 || code == 0x20; } -function badescape(code) { return newline(code) || isNaN(code); } - -var maximumallowedcodepoint = 0x10ffff; - -function preprocess(str) { - // Turn a string into an array of code points, - // following the preprocessing cleanup rules. - var codepoints = []; - for(var i = 0; i < str.length; i++) { - var code = str.charCodeAt(i); - if(code == 0xd && str.charCodeAt(i+1) == 0xa) { - code = 0xa; i++; - } - if(code == 0xd || code == 0xc) code = 0xa; - if(code == 0x0) code = 0xfffd; - if(between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i+1), 0xdc00, 0xdfff)) { - // Decode a surrogate pair into an astral codepoint. - var lead = code - 0xd800; - var trail = str.charCodeAt(i+1) - 0xdc00; - code = Math.pow(2, 21) + lead * Math.pow(2, 10) + trail; - } - codepoints.push(code); - } - return codepoints; -} - -function stringFromCode(code) { - if(code <= 0xffff) return String.fromCharCode(code); - // Otherwise, encode astral char as surrogate pair. - code -= Math.pow(2, 21); - var lead = Math.floor(code/Math.pow(2, 10)) + 0xd800; - var trail = code % Math.pow(2, 10); + 0xdc00; - return String.fromCharCode(lead) + String.fromCharCode(trail); -} - -function tokenize(str) { - str = preprocess(str); - var i = -1; - var tokens = []; - var code; - - // Line number information. - var line = 0; - var column = 0; - // The only use of lastLineLength is in reconsume(). - var lastLineLength = 0; - var incrLineno = function() { - line += 1; - lastLineLength = column; - column = 0; - }; - var locStart = {line:line, column:column}; - - var codepoint = function(i) { - if(i >= str.length) { - return -1; - } - return str[i]; - } - var next = function(num) { - if(num === undefined) - num = 1; - if(num > 3) - throw "Spec Error: no more than three codepoints of lookahead."; - return codepoint(i+num); - }; - var consume = function(num) { - if(num === undefined) - num = 1; - i += num; - code = codepoint(i); - if(newline(code)) incrLineno(); - else column += num; - //console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16)); - return true; - }; - var reconsume = function() { - i -= 1; - if (newline(code)) { - line -= 1; - column = lastLineLength; - } else { - column -= 1; - } - locStart.line = line; - locStart.column = column; - return true; - }; - var eof = function(codepoint) { - if(codepoint === undefined) codepoint = code; - return codepoint == -1; - }; - var donothing = function() {}; - var parseerror = function() { console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + " in state " + state + ".");return true; }; - - var consumeAToken = function() { - consumeComments(); - consume(); - if(whitespace(code)) { - while(whitespace(next())) consume(); - return new WhitespaceToken; - } - else if(code == 0x22) return consumeAStringToken(); - else if(code == 0x23) { - if(namechar(next()) || areAValidEscape(next(1), next(2))) { - var token = new HashToken(); - if(wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = "id"; - token.value = consumeAName(); - return token; - } else { - return new DelimToken(code); - } - } - else if(code == 0x24) { - if(next() == 0x3d) { - consume(); - return new SuffixMatchToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x27) return consumeAStringToken(); - else if(code == 0x28) return new OpenParenToken(); - else if(code == 0x29) return new CloseParenToken(); - else if(code == 0x2a) { - if(next() == 0x3d) { - consume(); - return new SubstringMatchToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x2b) { - if(startsWithANumber()) { - reconsume(); - return consumeANumericToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x2c) return new CommaToken(); - else if(code == 0x2d) { - if(startsWithANumber()) { - reconsume(); - return consumeANumericToken(); - } else if(next(1) == 0x2d && next(2) == 0x3e) { - consume(2); - return new CDCToken(); - } else if(startsWithAnIdentifier()) { - reconsume(); - return consumeAnIdentlikeToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x2e) { - if(startsWithANumber()) { - reconsume(); - return consumeANumericToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x3a) return new ColonToken; - else if(code == 0x3b) return new SemicolonToken; - else if(code == 0x3c) { - if(next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) { - consume(3); - return new CDOToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x40) { - if(wouldStartAnIdentifier(next(1), next(2), next(3))) { - return new AtKeywordToken(consumeAName()); - } else { - return new DelimToken(code); - } - } - else if(code == 0x5b) return new OpenSquareToken(); - else if(code == 0x5c) { - if(startsWithAValidEscape()) { - reconsume(); - return consumeAnIdentlikeToken(); - } else { - parseerror(); - return new DelimToken(code); - } - } - else if(code == 0x5d) return new CloseSquareToken(); - else if(code == 0x5e) { - if(next() == 0x3d) { - consume(); - return new PrefixMatchToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x7b) return new OpenCurlyToken(); - else if(code == 0x7c) { - if(next() == 0x3d) { - consume(); - return new DashMatchToken(); - } else if(next() == 0x7c) { - consume(); - return new ColumnToken(); - } else { - return new DelimToken(code); - } - } - else if(code == 0x7d) return new CloseCurlyToken(); - else if(code == 0x7e) { - if(next() == 0x3d) { - consume(); - return new IncludeMatchToken(); - } else { - return new DelimToken(code); - } - } - else if(digit(code)) { - reconsume(); - return consumeANumericToken(); - } - else if(namestartchar(code)) { - reconsume(); - return consumeAnIdentlikeToken(); - } - else if(eof()) return new EOFToken(); - else return new DelimToken(code); - }; - - var consumeComments = function() { - while(next(1) == 0x2f && next(2) == 0x2a) { - consume(2); - while(true) { - consume(); - if(code == 0x2a && next() == 0x2f) { - consume(); - break; - } else if(eof()) { - parseerror(); - return; - } - } - } - }; - - var consumeANumericToken = function() { - var num = consumeANumber(); - if(wouldStartAnIdentifier(next(1), next(2), next(3))) { - var token = new DimensionToken(); - console.log(num); - token.value = num.value; - token.repr = num.repr; - token.type = num.type; - token.unit = consumeAName(); - return token; - } else if(next() == 0x25) { - consume(); - var token = new PercentageToken(); - token.value = num.value; - token.repr = num.repr; - return token; - } else { - var token = new NumberToken(); - token.value = num.value; - token.repr = num.repr; - token.type = num.type; - return token; - } - }; - - var consumeAnIdentlikeToken = function() { - var str = consumeAName(); - if(str.toLowerCase() == "url" && next() == 0x28) { - consume(); - while(whitespace(next())) consume(); - if(next() == 0x22 || next() == 0x27) { - reconsume(); - return new FunctionToken(str); - } else { - return consumeAURLToken(); - } - } else if(next() == 0x28) { - consume(); - return new FunctionToken(str); - } else { - return new IdentToken(str); - } - }; - - var consumeAStringToken = function(endingCodePoint) { - if(endingCodePoint === undefined) endingCodePoint = code; - var token = new StringToken(); - while(consume()) { - if(code == endingCodePoint || eof()) { - return token; - } else if(newline(code)) { - parseerror(); - reconsume(); - return new BadStringToken(); - } else if(code == 0x5c) { - if(eof(next())) { - donothing(); - } else if(newline(next())) { - consume(); - } else { - token.value += stringFromCode(consumeEscape()) - } - } else { - token.value += stringFromCode(code); - } - } - }; - - var consumeAURLToken = function() { - var token = new URLToken(); - while(whitespace(next())) consume(); - if(eof(next())) return token; - while(consume()) { - if(code == 0x29 || eof()) { - return token; - } else if(whitespace(code)) { - while(whitespace(next())) consume(); - if(next() == 0x29 || eof(next())) { - consume(); - return token; - } else { - consumeTheRemnantsOfABadURL(); - return new BadURLToken(); - } - } else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) { - parseerror(); - consumeTheRemnantsOfABadURL(); - return new BadURLToken(); - } else if(code == 0x5c) { - if(startsWithAValidEscape()) { - token.value += stringFromCode(consumeEscape()); - } else { - parseerror(); - consumeTheRemnantsOfABadURL(); - return new BadURLToken(); - } - } else { - token.value += stringFromCode(code); - } - } - }; - - var consumeEscape = function() { - // Assume the the current character is the \ - // and the next code point is not a newline. - consume(); - if(hexdigit(code)) { - // Consume 1-6 hex digits - var digits = [code]; - for(var total = 0; total < 5; total++) { - if(hexdigit(next())) { - consume(); - digits.push(code); - } else { - break; - } - } - if(whitespace(next())) consume(); - var value = parseInt(digits.map(String.fromCharCode).join(''), 16); - if( value > maximumallowedcodepoint ) value = 0xfffd; - return value; - } else if(eof()) { - return 0xfffd; - } else { - return code; - } - }; - - var areAValidEscape = function(c1, c2) { - if(c1 != 0x5c) return false; - if(newline(c2)) return false; - return true; - }; - var startsWithAValidEscape = function() { - return areAValidEscape(code, next()); - }; - - var wouldStartAnIdentifier = function(c1, c2, c3) { - if(c1 == 0x2d) { - return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3); - } else if(namestartchar(c1)) { - return true; - } else if(c1 == 0x5c) { - return areAValidEscape(c1, c2); - } else { - return false; - } - }; - var startsWithAnIdentifier = function() { - return wouldStartAnIdentifier(code, next(1), next(2)); - }; - - var wouldStartANumber = function(c1, c2, c3) { - if(c1 == 0x2b || c1 == 0x2d) { - if(digit(c2)) return true; - if(c2 == 0x2e && digit(c3)) return true; - return false; - } else if(c1 == 0x2e) { - if(digit(c2)) return true; - return false; - } else if(digit(c1)) { - return true; - } else { - return false; - } - }; - var startsWithANumber = function() { - return wouldStartANumber(code, next(1), next(2)); - }; - - var consumeAName = function() { - var result = ""; - while(consume()) { - if(namechar(code)) { - result += stringFromCode(code); - } else if(startsWithAValidEscape()) { - result += stringFromCode(consumeEscape()); - } else { - reconsume(); - return result; - } - } - }; - - var consumeANumber = function() { - var repr = []; - var type = "integer"; - if(next() == 0x2b || next() == 0x2d) { - consume(); - repr += stringFromCode(code); - } - while(digit(next())) { - consume(); - repr += stringFromCode(code); - } - if(next(1) == 0x2e && digit(next(2))) { - consume(); - repr += stringFromCode(code); - consume(); - repr += stringFromCode(code); - type = "number"; - while(digit(next())) { - consume(); - repr += stringFromCode(code); - } - } - var c1 = next(1), c2 = next(2), c3 = next(3); - if((c1 == 0x45 || c1 == 0x65) && digit(c2)) { - consume(); - repr += stringFromCode(code); - consume(); - repr += stringFromCode(code); - type = "number"; - while(digit(next())) { - consume(); - repr += stringFromCode(code); - } - } else if((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) { - consume(); - repr += stringFromCode(code); - consume(); - repr += stringFromCode(code); - consume(); - repr += stringFromCode(code); - type = "number"; - while(digit(next())) { - consume(); - repr += stringFromCode(code); - } - } - var value = convertAStringToANumber(repr); - return {type:type, value:value, repr:repr}; - }; - - var convertAStringToANumber = function(string) { - // CSS's number rules are identical to JS, afaik. - return +string; - }; - - var consumeTheRemnantsOfABadURL = function() { - while(consume()) { - if(code == 0x2d || eof()) { - return; - } else if(startsWithAValidEscape()) { - consumeEscape(); - donothing(); - } else { - donothing(); - } - } - }; - - - - var iterationCount = 0; - while(!eof(next())) { - tokens.push(consumeAToken()); - iterationCount++; - if(iterationCount > str.length*2) return "I'm infinite-looping!"; - } - return tokens; -} - -function CSSParserToken() { throw "Abstract Base Class"; } -CSSParserToken.prototype.toJSON = function() { - return {token: this.tokenType}; -} -CSSParserToken.prototype.toString = function() { return this.tokenType; } - -function BadStringToken() { return this; } -BadStringToken.prototype = Object.create(CSSParserToken.prototype); -BadStringToken.prototype.tokenType = "BADSTRING"; - -function BadURLToken() { return this; } -BadURLToken.prototype = Object.create(CSSParserToken.prototype); -BadURLToken.prototype.tokenType = "BADURL"; - -function WhitespaceToken() { return this; } -WhitespaceToken.prototype = Object.create(CSSParserToken.prototype); -WhitespaceToken.prototype.tokenType = "WHITESPACE"; -WhitespaceToken.prototype.toString = function() { return "WS"; } - -function CDOToken() { return this; } -CDOToken.prototype = Object.create(CSSParserToken.prototype); -CDOToken.prototype.tokenType = "CDO"; - -function CDCToken() { return this; } -CDCToken.prototype = Object.create(CSSParserToken.prototype); -CDCToken.prototype.tokenType = "CDC"; - -function ColonToken() { return this; } -ColonToken.prototype = Object.create(CSSParserToken.prototype); -ColonToken.prototype.tokenType = ":"; - -function SemicolonToken() { return this; } -SemicolonToken.prototype = Object.create(CSSParserToken.prototype); -SemicolonToken.prototype.tokenType = ";"; - -function CommaToken() { return this; } -CommaToken.prototype = Object.create(CSSParserToken.prototype); -CommaToken.prototype.tokenType = ","; - -function GroupingToken() { throw "Abstract Base Class"; } -GroupingToken.prototype = Object.create(CSSParserToken.prototype); - -function OpenCurlyToken() { this.value = "{"; this.mirror = "}"; return this; } -OpenCurlyToken.prototype = Object.create(GroupingToken.prototype); -OpenCurlyToken.prototype.tokenType = "{"; - -function CloseCurlyToken() { this.value = "}"; this.mirror = "{"; return this; } -CloseCurlyToken.prototype = Object.create(GroupingToken.prototype); -CloseCurlyToken.prototype.tokenType = "}"; - -function OpenSquareToken() { this.value = "["; this.mirror = "]"; return this; } -OpenSquareToken.prototype = Object.create(GroupingToken.prototype); -OpenSquareToken.prototype.tokenType = "["; - -function CloseSquareToken() { this.value = "]"; this.mirror = "["; return this; } -CloseSquareToken.prototype = Object.create(GroupingToken.prototype); -CloseSquareToken.prototype.tokenType = "]"; - -function OpenParenToken() { this.value = "("; this.mirror = ")"; return this; } -OpenParenToken.prototype = Object.create(GroupingToken.prototype); -OpenParenToken.prototype.tokenType = "("; - -function CloseParenToken() { this.value = ")"; this.mirror = "("; return this; } -CloseParenToken.prototype = Object.create(GroupingToken.prototype); -CloseParenToken.prototype.tokenType = ")"; - -function IncludeMatchToken() { return this; } -IncludeMatchToken.prototype = Object.create(CSSParserToken.prototype); -IncludeMatchToken.prototype.tokenType = "~="; - -function DashMatchToken() { return this; } -DashMatchToken.prototype = Object.create(CSSParserToken.prototype); -DashMatchToken.prototype.tokenType = "|="; - -function PrefixMatchToken() { return this; } -PrefixMatchToken.prototype = Object.create(CSSParserToken.prototype); -PrefixMatchToken.prototype.tokenType = "^="; - -function SuffixMatchToken() { return this; } -SuffixMatchToken.prototype = Object.create(CSSParserToken.prototype); -SuffixMatchToken.prototype.tokenType = "$="; - -function SubstringMatchToken() { return this; } -SubstringMatchToken.prototype = Object.create(CSSParserToken.prototype); -SubstringMatchToken.prototype.tokenType = "*="; - -function ColumnToken() { return this; } -ColumnToken.prototype = Object.create(CSSParserToken.prototype); -ColumnToken.prototype.tokenType = "||"; - -function EOFToken() { return this; } -EOFToken.prototype = Object.create(CSSParserToken.prototype); -EOFToken.prototype.tokenType = "EOF"; - -function DelimToken(code) { - this.value = stringFromCode(code); - return this; -} -DelimToken.prototype = Object.create(CSSParserToken.prototype); -DelimToken.prototype.tokenType = "DELIM"; -DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; } -DelimToken.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.value = this.value; - return json; -} - -function StringValuedToken() { throw "Abstract Base Class"; } -StringValuedToken.prototype = Object.create(CSSParserToken.prototype); -StringValuedToken.prototype.ASCIImatch = function(str) { - return this.value.toLowerCase() == str.toLowerCase(); -} -StringValuedToken.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.value = this.value; - return json; -} - -function IdentToken(val) { - this.value = val; -} -IdentToken.prototype = Object.create(StringValuedToken.prototype); -IdentToken.prototype.tokenType = "IDENT"; -IdentToken.prototype.toString = function() { return "IDENT("+this.value+")"; } - -function FunctionToken(val) { - this.value = val; - this.mirror = ")"; -} -FunctionToken.prototype = Object.create(StringValuedToken.prototype); -FunctionToken.prototype.tokenType = "FUNCTION"; -FunctionToken.prototype.toString = function() { return "FUNCTION("+this.value+")"; } - -function AtKeywordToken(val) { - this.value = val; -} -AtKeywordToken.prototype = Object.create(StringValuedToken.prototype); -AtKeywordToken.prototype.tokenType = "AT-KEYWORD"; -AtKeywordToken.prototype.toString = function() { return "AT("+this.value+")"; } - -function HashToken(val) { - this.value = val; - this.type = "unrestricted"; -} -HashToken.prototype = Object.create(StringValuedToken.prototype); -HashToken.prototype.tokenType = "HASH"; -HashToken.prototype.toString = function() { return "HASH("+this.value+")"; } -HashToken.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.value = this.value; - json.type = this.type; - return json; -} - -function StringToken(val) { - this.value = val; -} -StringToken.prototype = Object.create(StringValuedToken.prototype); -StringToken.prototype.tokenType = "STRING"; -StringToken.prototype.toString = function() { return "\""+this.value+"\""; } - -function URLToken(val) { - this.value = val; -} -URLToken.prototype = Object.create(StringValuedToken.prototype); -URLToken.prototype.tokenType = "URL"; -URLToken.prototype.toString = function() { return "URL("+this.value+")"; } - -function NumberToken() { - this.value = null; - this.type = "integer"; - this.repr = ""; -} -NumberToken.prototype = Object.create(CSSParserToken.prototype); -NumberToken.prototype.tokenType = "NUMBER"; -NumberToken.prototype.toString = function() { - if(this.type == "integer") - return "INT("+this.value+")"; - return "NUMBER("+this.value+")"; -} -NumberToken.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.value = this.value; - json.type = this.type; - json.repr = this.repr; - return json; -} - -function PercentageToken() { - this.value = null; - this.repr = ""; -} -PercentageToken.prototype = Object.create(CSSParserToken.prototype); -PercentageToken.prototype.tokenType = "PERCENTAGE"; -PercentageToken.prototype.toString = function() { return "PERCENTAGE("+this.value+")"; } -NumberToken.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.value = this.value; - json.repr = this.repr; - return json; -} - -function DimensionToken() { - this.value = null; - this.type = "integer"; - this.repr = ""; - this.unit = ""; -} -DimensionToken.prototype = Object.create(CSSParserToken.prototype); -DimensionToken.prototype.tokenType = "DIMENSION"; -DimensionToken.prototype.toString = function() { return "DIM("+this.value+","+this.unit+")"; } -NumberToken.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.value = this.value; - json.type = this.type; - json.repr = this.repr; - json.unit = this.unit; - return json; -} - -// Exportation. -exports.tokenize = tokenize; -exports.IdentToken = IdentToken; -exports.FunctionToken = FunctionToken; -exports.AtKeywordToken = AtKeywordToken; -exports.HashToken = HashToken; -exports.StringToken = StringToken; -exports.BadStringToken = BadStringToken; -exports.URLToken = URLToken; -exports.BadURLToken = BadURLToken; -exports.DelimToken = DelimToken; -exports.NumberToken = NumberToken; -exports.PercentageToken = PercentageToken; -exports.DimensionToken = DimensionToken; -exports.IncludeMatchToken = IncludeMatchToken; -exports.DashMatchToken = DashMatchToken; -exports.PrefixMatchToken = PrefixMatchToken; -exports.SuffixMatchToken = SuffixMatchToken; -exports.SubstringMatchToken = SubstringMatchToken; -exports.ColumnToken = ColumnToken; -exports.WhitespaceToken = WhitespaceToken; -exports.CDOToken = CDOToken; -exports.CDCToken = CDCToken; -exports.ColonToken = ColonToken; -exports.SemicolonToken = SemicolonToken; -exports.CommaToken = CommaToken; -exports.OpenParenToken = OpenParenToken; -exports.CloseParenToken = CloseParenToken; -exports.OpenSquareToken = OpenSquareToken; -exports.CloseSquareToken = CloseSquareToken; -exports.OpenCurlyToken = OpenCurlyToken; -exports.CloseCurlyToken = CloseCurlyToken; -exports.EOFToken = EOFToken; -exports.CSSParserToken = CSSParserToken; -exports.GroupingToken = GroupingToken; - -function TokenStream(tokens) { - // Assume that tokens is an array. - this.tokens = tokens; - this.i = -1; -} -TokenStream.prototype.tokenAt = function(i) { - if(i < this.tokens.length) - return this.tokens[i]; - return new EOFToken(); -} -TokenStream.prototype.consume = function(num) { - if(num === undefined) num = 1; - this.i += num; - this.token = this.tokenAt(this.i); - //console.log(this.i, this.token); - return true; -} -TokenStream.prototype.next = function() { - return this.tokenAt(this.i+1); -} -TokenStream.prototype.reconsume = function() { - this.i--; -} - -function parseerror(s, msg) { - console.log("Parse error at token " + s.i + ": " + s.token + ".\n" + msg); - return true; -} -function donothing(){ return true; }; - -function consumeAListOfRules(s, topLevel) { - var rules = []; - var rule; - while(s.consume()) { - if(s.token instanceof WhitespaceToken) { - continue; - } else if(s.token instanceof EOFToken) { - return rules; - } else if(s.token instanceof CDOToken || s.token instanceof CDCToken) { - if(topLevel == "top-level") continue; - s.reconsume(); - if(rule = consumeAQualifiedRule(s)) rules.push(rule); - } else if(s.token instanceof AtKeywordToken) { - s.reconsume(); - if(rule = consumeAnAtRule(s)) rules.push(rule); - } else { - s.reconsume(); - if(rule = consumeAQualifiedRule(s)) rules.push(rule); - } - } -} - -function consumeAnAtRule(s) { - s.consume(); - var rule = new AtRule(s.token.value); - while(s.consume()) { - if(s.token instanceof SemicolonToken || s.token instanceof EOFToken) { - return rule; - } else if(s.token instanceof OpenCurlyToken) { - rule.value = consumeASimpleBlock(s); - return rule; - } else if(s.token instanceof SimpleBlock && s.token.name == "{") { - rule.value = s.token; - return rule; - } else { - s.reconsume(); - rule.prelude.push(consumeAComponentValue(s)); - } - } -} - -function consumeAQualifiedRule(s) { - var rule = new QualifiedRule(); - while(s.consume()) { - if(s.token instanceof EOFToken) { - parseerror(s, "Hit EOF when trying to parse the prelude of a qualified rule."); - return; - } else if(s.token instanceof OpenCurlyToken) { - rule.value = consumeASimpleBlock(s); - return rule; - } else if(s.token instanceof SimpleBlock && s.token.name == "{") { - rule.value = token; - return rule; - } else { - s.reconsume(); - rule.prelude.push(consumeAComponentValue(s)); - } - } -} - -function consumeAListOfDeclarations(s) { - var decls = []; - while(s.consume()) { - if(s.token instanceof WhitespaceToken || s.token instanceof SemicolonToken) { - donothing(); - } else if(s.token instanceof EOFToken) { - return decls; - } else if(s.token instanceof AtKeywordToken) { - s.reconsume(); - decls.push(consumeAnAtRule(s)); - } else if(s.token instanceof IdentToken) { - var temp = [token]; - while(!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) - temp.push(consumeAComponentValue(s)); - var decl; - if(decl = consumeADeclaration(new TokenStream(temp))) decls.push(decl); - } else { - parseerror(s); - reconsume(); - while(!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) - consumeAComponentValue(s); - } - } -} - -function consumeADeclaration(s) { - // Assumes that the next input token will be an ident token. - s.consume(); - var decl = new Declaration(s.token.value); - while(s.next() instanceof WhitespaceToken) s.consume(); - if(!(s.next() instanceof ColonToken)) { - parseerror(s); - return; - } else { - s.consume(); - } - while(!(s.next() instanceof EOFToken)) { - decl.value.push(consumeAComponentValue(s)); - } - var foundImportant = false; - for(var i = decl.value.length - 1; i >= 0; i--) { - if(decl.value[i] instanceof WhitespaceToken) { - continue; - } else if(decl.value[i] instanceof IdentToken && decl.value[i].ASCIIMatch("important")) { - foundImportant = true; - } else if(foundImportant && decl.value[i] instanceof DelimToken && decl.value[i].value == "!") { - decl.value.splice(i, decl.value.length); - decl.important = true; - break; - } else { - break; - } - } - return decl; -} - -function consumeAComponentValue(s) { - s.consume(); - if(s.token instanceof OpenCurlyToken || s.token instanceof OpenSquareToken || s.token instanceof OpenParenToken) - return consumeASimpleBlock(); - if(s.token instanceof FunctionToken) - return consumeAFunction(); - return s.token; -} - -function consumeASimpleBlock(s) { - var mirror = s.token.mirror; - var block = new SimpleBlock(s.token.value); - while(s.consume()) { - if(s.token instanceof EOFToken || (s.token instanceof GroupingToken && s.token.value == mirror)) - return block; - else { - s.reconsume(); - block.value.push(consumeAComponentValue(s)); - } - } -} - -function consumeAFunction(s) { - var func = new Func(s.token.value); - while(s.consume()) { - if(s.token instanceof EOFToken || s.token instanceof CloseParenToken) - return func; - else { - s.reconsume(); - func.value.push(consumeAComponentValue(s)); - } - } -} - -function normalizeInput(input) { - if(typeof input == "string") - return new TokenStream(tokenize(input)); - if(input instanceof TokenStream) - return input; - if(input.length !== undefined) - return new TokenStream(input); - else throw SyntaxError(input); -} - -function parseAStylesheet(s) { - s = normalizeInput(s); - var sheet = new Stylesheet(); - sheet.value = consumeAListOfRules(s, "top-level"); - return sheet; -} - -function parseAListOfRules(s) { - s = normalizeInput(s); - return consumeAListOfRules(s); -} - -function parseARule(s) { - s = normalizeInput(s); - while(s.next() instanceof WhitespaceToken) s.consume(); - if(s.next() instanceof EOFToken) throw SyntaxError(); - if(s.next() instanceof AtKeywordToken) { - var rule = consumeAnAtRule(s); - } else { - var rule = consumeAQualifiedRule(s); - if(!rule) throw SyntaxError(); - } - while(s.next() instanceof WhitespaceToken) s.consume(); - if(s.next() instanceof EOFToken) - return rule; - throw SyntaxError(); -} - -function parseADeclaration(s) { - s = normalizeInput(s); - while(s.next() instanceof WhitespaceToken) s.consume(); - if(!(s.next() instanceof IdentToken)) throw SyntaxError(); - var decl = consumeADeclaration(); - if(decl) - return decl - else - throw SyntaxError(); -} - -function parseAListOfDeclarations(s) { - s = normalizeInput(s); - return consumeAListOfDeclarations(s); -} - -function parseAComponentValue(s) { - s = normalizeInput(s); - while(s.next() instanceof WhitespaceToken) s.consume(); - if(s.next() instanceof EOFToken) throw SyntaxError(); - var val = consumeAComponentValue(s); - if(!val) throw SyntaxError(); - while(s.next() instanceof WhitespaceToken) s.consume(); - if(s.next() instanceof EOFToken) - return val; - throw SyntaxError(); -} - -function parseAListOfComponentValues(s) { - var vals = []; - while(true) { - var val = consumeAComponentValue(s); - if(val instanceof EOFToken) - return vals - else - vals.push(val); - } -} - -function parseACommaSeparatedListOfComponentValues(s) { - var listOfCVLs = []; - while(true) { - var vals = []; - while(true) { - var val = consumeAComponentValue(s); - if(val instanceof EOFToken) { - return listOfCVLS; - } else if(val instanceof CommaToken) { - listOfCVLS.push(vals); - break; - } else { - vals.push(val); - } - } - } -} - - -function CSSParserRule() { throw "Abstract Base Class"; } -CSSParserRule.prototype.toString = function(indent) { - return JSON.stringify(this,null,indent); -} -CSSParserRule.prototype.toJSON = function() { - return {type:this.type, value:this.value}; -} - -function Stylesheet() { - this.value = []; - return this; -} -Stylesheet.prototype = Object.create(CSSParserRule.prototype); -Stylesheet.prototype.type = "STYLESHEET"; - -function AtRule(name) { - this.name = name; - this.prelude = []; - this.value = null; - return this; -} -AtRule.prototype = Object.create(CSSParserRule.prototype); -AtRule.prototype.type = "AT-RULE"; -AtRule.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.name = this.name; - json.prelude = this.prelude; - return json; -} - -function QualifiedRule() { - this.prelude = []; - this.value = []; - return this; -} -QualifiedRule.prototype = Object.create(CSSParserRule.prototype); -QualifiedRule.prototype.type = "QUALIFIED-RULE"; -QualifiedRule.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.prelude = this.prelude; - return json; -} - -function Declaration(name) { - this.name = name; - this.value = []; - this.important = false; - return this; -} -Declaration.prototype = Object.create(CSSParserRule.prototype); -Declaration.prototype.type = "DECLARATION"; - -function SimpleBlock(type) { - this.name = type; - this.value = []; - return this; -} -SimpleBlock.prototype = Object.create(CSSParserRule.prototype); -SimpleBlock.prototype.type = "BLOCK"; -SimpleBlock.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.name = this.name; - return json; -} - -function Func(name) { - this.name = name; - this.value = []; - return this; -} -Func.prototype = Object.create(CSSParserRule.prototype); -Func.prototype.type = "FUNCTION"; -Func.prototype.toJSON = function() { - var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); - json.name = this.name; - return json; -} - -// Exportation. -exports.CSSParserRule = CSSParserRule; -exports.Stylesheet = Stylesheet; -exports.AtRule = AtRule; -exports.QualifiedRule = QualifiedRule; -exports.Declaration = Declaration; -exports.SimpleBlock = SimpleBlock; -exports.Func = Func; -exports.parseAStylesheet = parseAStylesheet; -exports.parseAListOfRules = parseAListOfRules; -exports.parseARule = parseARule; -exports.parseADeclaration = parseADeclaration; -exports.parseAListOfDeclarations = parseAListOfDeclarations; -exports.parseAComponentValue = parseAComponentValue; -exports.parseAListOfComponentValues = parseAListOfComponentValues; -exports.parseACommaSeparatedListOfComponentValues = parseACommaSeparatedListOfComponentValues; - -})); +(function (root, factory) { + // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, + // Rhino, and plain browser loading. + if (typeof define === 'function' && define.amd) { + define(['exports'], factory); + } else if (typeof exports !== 'undefined') { + factory(exports); + } else { + factory(root); + } +}(this, function (exports) { + +var between = function (num, first, last) { return num >= first && num <= last; } +function digit(code) { return between(code, 0x30,0x39); } +function hexdigit(code) { return digit(code) || between(code, 0x41,0x46) || between(code, 0x61,0x66); } +function uppercaseletter(code) { return between(code, 0x41,0x5a); } +function lowercaseletter(code) { return between(code, 0x61,0x7a); } +function letter(code) { return uppercaseletter(code) || lowercaseletter(code); } +function nonascii(code) { return code >= 0x80; } +function namestartchar(code) { return letter(code) || nonascii(code) || code == 0x5f; } +function namechar(code) { return namestartchar(code) || digit(code) || code == 0x2d; } +function nonprintable(code) { return between(code, 0,8) || code == 0xb || between(code, 0xe,0x1f) || code == 0x7f; } +function newline(code) { return code == 0xa || code == 0xc; } +function whitespace(code) { return newline(code) || code == 9 || code == 0x20; } +function badescape(code) { return newline(code) || isNaN(code); } + +var maximumallowedcodepoint = 0x10ffff; + +function preprocess(str) { + // Turn a string into an array of code points, + // following the preprocessing cleanup rules. + var codepoints = []; + for(var i = 0; i < str.length; i++) { + var code = str.charCodeAt(i); + if(code == 0xd && str.charCodeAt(i+1) == 0xa) { + code = 0xa; i++; + } + if(code == 0xd || code == 0xc) code = 0xa; + if(code == 0x0) code = 0xfffd; + if(between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i+1), 0xdc00, 0xdfff)) { + // Decode a surrogate pair into an astral codepoint. + var lead = code - 0xd800; + var trail = str.charCodeAt(i+1) - 0xdc00; + code = Math.pow(2, 21) + lead * Math.pow(2, 10) + trail; + } + codepoints.push(code); + } + return codepoints; +} + +function stringFromCode(code) { + if(code <= 0xffff) return String.fromCharCode(code); + // Otherwise, encode astral char as surrogate pair. + code -= Math.pow(2, 21); + var lead = Math.floor(code/Math.pow(2, 10)) + 0xd800; + var trail = code % Math.pow(2, 10); + 0xdc00; + return String.fromCharCode(lead) + String.fromCharCode(trail); +} + +function tokenize(str) { + str = preprocess(str); + var i = -1; + var tokens = []; + var code; + + // Line number information. + var line = 0; + var column = 0; + // The only use of lastLineLength is in reconsume(). + var lastLineLength = 0; + var incrLineno = function() { + line += 1; + lastLineLength = column; + column = 0; + }; + var locStart = {line:line, column:column}; + + var codepoint = function(i) { + if(i >= str.length) { + return -1; + } + return str[i]; + } + var next = function(num) { + if(num === undefined) + num = 1; + if(num > 3) + throw "Spec Error: no more than three codepoints of lookahead."; + return codepoint(i+num); + }; + var consume = function(num) { + if(num === undefined) + num = 1; + i += num; + code = codepoint(i); + if(newline(code)) incrLineno(); + else column += num; + //console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16)); + return true; + }; + var reconsume = function() { + i -= 1; + if (newline(code)) { + line -= 1; + column = lastLineLength; + } else { + column -= 1; + } + locStart.line = line; + locStart.column = column; + return true; + }; + var eof = function(codepoint) { + if(codepoint === undefined) codepoint = code; + return codepoint == -1; + }; + var donothing = function() {}; + var parseerror = function() { console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + " in state " + state + ".");return true; }; + + var consumeAToken = function() { + consumeComments(); + consume(); + if(whitespace(code)) { + while(whitespace(next())) consume(); + return new WhitespaceToken; + } + else if(code == 0x22) return consumeAStringToken(); + else if(code == 0x23) { + if(namechar(next()) || areAValidEscape(next(1), next(2))) { + var token = new HashToken(); + if(wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = "id"; + token.value = consumeAName(); + return token; + } else { + return new DelimToken(code); + } + } + else if(code == 0x24) { + if(next() == 0x3d) { + consume(); + return new SuffixMatchToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x27) return consumeAStringToken(); + else if(code == 0x28) return new OpenParenToken(); + else if(code == 0x29) return new CloseParenToken(); + else if(code == 0x2a) { + if(next() == 0x3d) { + consume(); + return new SubstringMatchToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x2b) { + if(startsWithANumber()) { + reconsume(); + return consumeANumericToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x2c) return new CommaToken(); + else if(code == 0x2d) { + if(startsWithANumber()) { + reconsume(); + return consumeANumericToken(); + } else if(next(1) == 0x2d && next(2) == 0x3e) { + consume(2); + return new CDCToken(); + } else if(startsWithAnIdentifier()) { + reconsume(); + return consumeAnIdentlikeToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x2e) { + if(startsWithANumber()) { + reconsume(); + return consumeANumericToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x3a) return new ColonToken; + else if(code == 0x3b) return new SemicolonToken; + else if(code == 0x3c) { + if(next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) { + consume(3); + return new CDOToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x40) { + if(wouldStartAnIdentifier(next(1), next(2), next(3))) { + return new AtKeywordToken(consumeAName()); + } else { + return new DelimToken(code); + } + } + else if(code == 0x5b) return new OpenSquareToken(); + else if(code == 0x5c) { + if(startsWithAValidEscape()) { + reconsume(); + return consumeAnIdentlikeToken(); + } else { + parseerror(); + return new DelimToken(code); + } + } + else if(code == 0x5d) return new CloseSquareToken(); + else if(code == 0x5e) { + if(next() == 0x3d) { + consume(); + return new PrefixMatchToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x7b) return new OpenCurlyToken(); + else if(code == 0x7c) { + if(next() == 0x3d) { + consume(); + return new DashMatchToken(); + } else if(next() == 0x7c) { + consume(); + return new ColumnToken(); + } else { + return new DelimToken(code); + } + } + else if(code == 0x7d) return new CloseCurlyToken(); + else if(code == 0x7e) { + if(next() == 0x3d) { + consume(); + return new IncludeMatchToken(); + } else { + return new DelimToken(code); + } + } + else if(digit(code)) { + reconsume(); + return consumeANumericToken(); + } + else if(namestartchar(code)) { + reconsume(); + return consumeAnIdentlikeToken(); + } + else if(eof()) return new EOFToken(); + else return new DelimToken(code); + }; + + var consumeComments = function() { + while(next(1) == 0x2f && next(2) == 0x2a) { + consume(2); + while(true) { + consume(); + if(code == 0x2a && next() == 0x2f) { + consume(); + break; + } else if(eof()) { + parseerror(); + return; + } + } + } + }; + + var consumeANumericToken = function() { + var num = consumeANumber(); + if(wouldStartAnIdentifier(next(1), next(2), next(3))) { + var token = new DimensionToken(); + console.log(num); + token.value = num.value; + token.repr = num.repr; + token.type = num.type; + token.unit = consumeAName(); + return token; + } else if(next() == 0x25) { + consume(); + var token = new PercentageToken(); + token.value = num.value; + token.repr = num.repr; + return token; + } else { + var token = new NumberToken(); + token.value = num.value; + token.repr = num.repr; + token.type = num.type; + return token; + } + }; + + var consumeAnIdentlikeToken = function() { + var str = consumeAName(); + if(str.toLowerCase() == "url" && next() == 0x28) { + consume(); + while(whitespace(next())) consume(); + if(next() == 0x22 || next() == 0x27) { + reconsume(); + return new FunctionToken(str); + } else { + return consumeAURLToken(); + } + } else if(next() == 0x28) { + consume(); + return new FunctionToken(str); + } else { + return new IdentToken(str); + } + }; + + var consumeAStringToken = function(endingCodePoint) { + if(endingCodePoint === undefined) endingCodePoint = code; + var token = new StringToken(); + while(consume()) { + if(code == endingCodePoint || eof()) { + return token; + } else if(newline(code)) { + parseerror(); + reconsume(); + return new BadStringToken(); + } else if(code == 0x5c) { + if(eof(next())) { + donothing(); + } else if(newline(next())) { + consume(); + } else { + token.value += stringFromCode(consumeEscape()) + } + } else { + token.value += stringFromCode(code); + } + } + }; + + var consumeAURLToken = function() { + var token = new URLToken(); + while(whitespace(next())) consume(); + if(eof(next())) return token; + while(consume()) { + if(code == 0x29 || eof()) { + return token; + } else if(whitespace(code)) { + while(whitespace(next())) consume(); + if(next() == 0x29 || eof(next())) { + consume(); + return token; + } else { + consumeTheRemnantsOfABadURL(); + return new BadURLToken(); + } + } else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) { + parseerror(); + consumeTheRemnantsOfABadURL(); + return new BadURLToken(); + } else if(code == 0x5c) { + if(startsWithAValidEscape()) { + token.value += stringFromCode(consumeEscape()); + } else { + parseerror(); + consumeTheRemnantsOfABadURL(); + return new BadURLToken(); + } + } else { + token.value += stringFromCode(code); + } + } + }; + + var consumeEscape = function() { + // Assume the the current character is the \ + // and the next code point is not a newline. + consume(); + if(hexdigit(code)) { + // Consume 1-6 hex digits + var digits = [code]; + for(var total = 0; total < 5; total++) { + if(hexdigit(next())) { + consume(); + digits.push(code); + } else { + break; + } + } + if(whitespace(next())) consume(); + var value = parseInt(digits.map(String.fromCharCode).join(''), 16); + if( value > maximumallowedcodepoint ) value = 0xfffd; + return value; + } else if(eof()) { + return 0xfffd; + } else { + return code; + } + }; + + var areAValidEscape = function(c1, c2) { + if(c1 != 0x5c) return false; + if(newline(c2)) return false; + return true; + }; + var startsWithAValidEscape = function() { + return areAValidEscape(code, next()); + }; + + var wouldStartAnIdentifier = function(c1, c2, c3) { + if(c1 == 0x2d) { + return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3); + } else if(namestartchar(c1)) { + return true; + } else if(c1 == 0x5c) { + return areAValidEscape(c1, c2); + } else { + return false; + } + }; + var startsWithAnIdentifier = function() { + return wouldStartAnIdentifier(code, next(1), next(2)); + }; + + var wouldStartANumber = function(c1, c2, c3) { + if(c1 == 0x2b || c1 == 0x2d) { + if(digit(c2)) return true; + if(c2 == 0x2e && digit(c3)) return true; + return false; + } else if(c1 == 0x2e) { + if(digit(c2)) return true; + return false; + } else if(digit(c1)) { + return true; + } else { + return false; + } + }; + var startsWithANumber = function() { + return wouldStartANumber(code, next(1), next(2)); + }; + + var consumeAName = function() { + var result = ""; + while(consume()) { + if(namechar(code)) { + result += stringFromCode(code); + } else if(startsWithAValidEscape()) { + result += stringFromCode(consumeEscape()); + } else { + reconsume(); + return result; + } + } + }; + + var consumeANumber = function() { + var repr = []; + var type = "integer"; + if(next() == 0x2b || next() == 0x2d) { + consume(); + repr += stringFromCode(code); + } + while(digit(next())) { + consume(); + repr += stringFromCode(code); + } + if(next(1) == 0x2e && digit(next(2))) { + consume(); + repr += stringFromCode(code); + consume(); + repr += stringFromCode(code); + type = "number"; + while(digit(next())) { + consume(); + repr += stringFromCode(code); + } + } + var c1 = next(1), c2 = next(2), c3 = next(3); + if((c1 == 0x45 || c1 == 0x65) && digit(c2)) { + consume(); + repr += stringFromCode(code); + consume(); + repr += stringFromCode(code); + type = "number"; + while(digit(next())) { + consume(); + repr += stringFromCode(code); + } + } else if((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) { + consume(); + repr += stringFromCode(code); + consume(); + repr += stringFromCode(code); + consume(); + repr += stringFromCode(code); + type = "number"; + while(digit(next())) { + consume(); + repr += stringFromCode(code); + } + } + var value = convertAStringToANumber(repr); + return {type:type, value:value, repr:repr}; + }; + + var convertAStringToANumber = function(string) { + // CSS's number rules are identical to JS, afaik. + return +string; + }; + + var consumeTheRemnantsOfABadURL = function() { + while(consume()) { + if(code == 0x2d || eof()) { + return; + } else if(startsWithAValidEscape()) { + consumeEscape(); + donothing(); + } else { + donothing(); + } + } + }; + + + + var iterationCount = 0; + while(!eof(next())) { + tokens.push(consumeAToken()); + iterationCount++; + if(iterationCount > str.length*2) return "I'm infinite-looping!"; + } + return tokens; +} + +function CSSParserToken() { throw "Abstract Base Class"; } +CSSParserToken.prototype.toJSON = function() { + return {token: this.tokenType}; +} +CSSParserToken.prototype.toString = function() { return this.tokenType; } + +function BadStringToken() { return this; } +BadStringToken.prototype = Object.create(CSSParserToken.prototype); +BadStringToken.prototype.tokenType = "BADSTRING"; + +function BadURLToken() { return this; } +BadURLToken.prototype = Object.create(CSSParserToken.prototype); +BadURLToken.prototype.tokenType = "BADURL"; + +function WhitespaceToken() { return this; } +WhitespaceToken.prototype = Object.create(CSSParserToken.prototype); +WhitespaceToken.prototype.tokenType = "WHITESPACE"; +WhitespaceToken.prototype.toString = function() { return "WS"; } + +function CDOToken() { return this; } +CDOToken.prototype = Object.create(CSSParserToken.prototype); +CDOToken.prototype.tokenType = "CDO"; + +function CDCToken() { return this; } +CDCToken.prototype = Object.create(CSSParserToken.prototype); +CDCToken.prototype.tokenType = "CDC"; + +function ColonToken() { return this; } +ColonToken.prototype = Object.create(CSSParserToken.prototype); +ColonToken.prototype.tokenType = ":"; + +function SemicolonToken() { return this; } +SemicolonToken.prototype = Object.create(CSSParserToken.prototype); +SemicolonToken.prototype.tokenType = ";"; + +function CommaToken() { return this; } +CommaToken.prototype = Object.create(CSSParserToken.prototype); +CommaToken.prototype.tokenType = ","; + +function GroupingToken() { throw "Abstract Base Class"; } +GroupingToken.prototype = Object.create(CSSParserToken.prototype); + +function OpenCurlyToken() { this.value = "{"; this.mirror = "}"; return this; } +OpenCurlyToken.prototype = Object.create(GroupingToken.prototype); +OpenCurlyToken.prototype.tokenType = "{"; + +function CloseCurlyToken() { this.value = "}"; this.mirror = "{"; return this; } +CloseCurlyToken.prototype = Object.create(GroupingToken.prototype); +CloseCurlyToken.prototype.tokenType = "}"; + +function OpenSquareToken() { this.value = "["; this.mirror = "]"; return this; } +OpenSquareToken.prototype = Object.create(GroupingToken.prototype); +OpenSquareToken.prototype.tokenType = "["; + +function CloseSquareToken() { this.value = "]"; this.mirror = "["; return this; } +CloseSquareToken.prototype = Object.create(GroupingToken.prototype); +CloseSquareToken.prototype.tokenType = "]"; + +function OpenParenToken() { this.value = "("; this.mirror = ")"; return this; } +OpenParenToken.prototype = Object.create(GroupingToken.prototype); +OpenParenToken.prototype.tokenType = "("; + +function CloseParenToken() { this.value = ")"; this.mirror = "("; return this; } +CloseParenToken.prototype = Object.create(GroupingToken.prototype); +CloseParenToken.prototype.tokenType = ")"; + +function IncludeMatchToken() { return this; } +IncludeMatchToken.prototype = Object.create(CSSParserToken.prototype); +IncludeMatchToken.prototype.tokenType = "~="; + +function DashMatchToken() { return this; } +DashMatchToken.prototype = Object.create(CSSParserToken.prototype); +DashMatchToken.prototype.tokenType = "|="; + +function PrefixMatchToken() { return this; } +PrefixMatchToken.prototype = Object.create(CSSParserToken.prototype); +PrefixMatchToken.prototype.tokenType = "^="; + +function SuffixMatchToken() { return this; } +SuffixMatchToken.prototype = Object.create(CSSParserToken.prototype); +SuffixMatchToken.prototype.tokenType = "$="; + +function SubstringMatchToken() { return this; } +SubstringMatchToken.prototype = Object.create(CSSParserToken.prototype); +SubstringMatchToken.prototype.tokenType = "*="; + +function ColumnToken() { return this; } +ColumnToken.prototype = Object.create(CSSParserToken.prototype); +ColumnToken.prototype.tokenType = "||"; + +function EOFToken() { return this; } +EOFToken.prototype = Object.create(CSSParserToken.prototype); +EOFToken.prototype.tokenType = "EOF"; + +function DelimToken(code) { + this.value = stringFromCode(code); + return this; +} +DelimToken.prototype = Object.create(CSSParserToken.prototype); +DelimToken.prototype.tokenType = "DELIM"; +DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; } +DelimToken.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.value = this.value; + return json; +} + +function StringValuedToken() { throw "Abstract Base Class"; } +StringValuedToken.prototype = Object.create(CSSParserToken.prototype); +StringValuedToken.prototype.ASCIImatch = function(str) { + return this.value.toLowerCase() == str.toLowerCase(); +} +StringValuedToken.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.value = this.value; + return json; +} + +function IdentToken(val) { + this.value = val; +} +IdentToken.prototype = Object.create(StringValuedToken.prototype); +IdentToken.prototype.tokenType = "IDENT"; +IdentToken.prototype.toString = function() { return "IDENT("+this.value+")"; } + +function FunctionToken(val) { + this.value = val; + this.mirror = ")"; +} +FunctionToken.prototype = Object.create(StringValuedToken.prototype); +FunctionToken.prototype.tokenType = "FUNCTION"; +FunctionToken.prototype.toString = function() { return "FUNCTION("+this.value+")"; } + +function AtKeywordToken(val) { + this.value = val; +} +AtKeywordToken.prototype = Object.create(StringValuedToken.prototype); +AtKeywordToken.prototype.tokenType = "AT-KEYWORD"; +AtKeywordToken.prototype.toString = function() { return "AT("+this.value+")"; } + +function HashToken(val) { + this.value = val; + this.type = "unrestricted"; +} +HashToken.prototype = Object.create(StringValuedToken.prototype); +HashToken.prototype.tokenType = "HASH"; +HashToken.prototype.toString = function() { return "HASH("+this.value+")"; } +HashToken.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.value = this.value; + json.type = this.type; + return json; +} + +function StringToken(val) { + this.value = val; +} +StringToken.prototype = Object.create(StringValuedToken.prototype); +StringToken.prototype.tokenType = "STRING"; +StringToken.prototype.toString = function() { return "\""+this.value+"\""; } + +function URLToken(val) { + this.value = val; +} +URLToken.prototype = Object.create(StringValuedToken.prototype); +URLToken.prototype.tokenType = "URL"; +URLToken.prototype.toString = function() { return "URL("+this.value+")"; } + +function NumberToken() { + this.value = null; + this.type = "integer"; + this.repr = ""; +} +NumberToken.prototype = Object.create(CSSParserToken.prototype); +NumberToken.prototype.tokenType = "NUMBER"; +NumberToken.prototype.toString = function() { + if(this.type == "integer") + return "INT("+this.value+")"; + return "NUMBER("+this.value+")"; +} +NumberToken.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.value = this.value; + json.type = this.type; + json.repr = this.repr; + return json; +} + +function PercentageToken() { + this.value = null; + this.repr = ""; +} +PercentageToken.prototype = Object.create(CSSParserToken.prototype); +PercentageToken.prototype.tokenType = "PERCENTAGE"; +PercentageToken.prototype.toString = function() { return "PERCENTAGE("+this.value+")"; } +NumberToken.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.value = this.value; + json.repr = this.repr; + return json; +} + +function DimensionToken() { + this.value = null; + this.type = "integer"; + this.repr = ""; + this.unit = ""; +} +DimensionToken.prototype = Object.create(CSSParserToken.prototype); +DimensionToken.prototype.tokenType = "DIMENSION"; +DimensionToken.prototype.toString = function() { return "DIM("+this.value+","+this.unit+")"; } +NumberToken.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.value = this.value; + json.type = this.type; + json.repr = this.repr; + json.unit = this.unit; + return json; +} + +// Exportation. +exports.tokenize = tokenize; +exports.IdentToken = IdentToken; +exports.FunctionToken = FunctionToken; +exports.AtKeywordToken = AtKeywordToken; +exports.HashToken = HashToken; +exports.StringToken = StringToken; +exports.BadStringToken = BadStringToken; +exports.URLToken = URLToken; +exports.BadURLToken = BadURLToken; +exports.DelimToken = DelimToken; +exports.NumberToken = NumberToken; +exports.PercentageToken = PercentageToken; +exports.DimensionToken = DimensionToken; +exports.IncludeMatchToken = IncludeMatchToken; +exports.DashMatchToken = DashMatchToken; +exports.PrefixMatchToken = PrefixMatchToken; +exports.SuffixMatchToken = SuffixMatchToken; +exports.SubstringMatchToken = SubstringMatchToken; +exports.ColumnToken = ColumnToken; +exports.WhitespaceToken = WhitespaceToken; +exports.CDOToken = CDOToken; +exports.CDCToken = CDCToken; +exports.ColonToken = ColonToken; +exports.SemicolonToken = SemicolonToken; +exports.CommaToken = CommaToken; +exports.OpenParenToken = OpenParenToken; +exports.CloseParenToken = CloseParenToken; +exports.OpenSquareToken = OpenSquareToken; +exports.CloseSquareToken = CloseSquareToken; +exports.OpenCurlyToken = OpenCurlyToken; +exports.CloseCurlyToken = CloseCurlyToken; +exports.EOFToken = EOFToken; +exports.CSSParserToken = CSSParserToken; +exports.GroupingToken = GroupingToken; + +function TokenStream(tokens) { + // Assume that tokens is an array. + this.tokens = tokens; + this.i = -1; +} +TokenStream.prototype.tokenAt = function(i) { + if(i < this.tokens.length) + return this.tokens[i]; + return new EOFToken(); +} +TokenStream.prototype.consume = function(num) { + if(num === undefined) num = 1; + this.i += num; + this.token = this.tokenAt(this.i); + //console.log(this.i, this.token); + return true; +} +TokenStream.prototype.next = function() { + return this.tokenAt(this.i+1); +} +TokenStream.prototype.reconsume = function() { + this.i--; +} + +function parseerror(s, msg) { + console.log("Parse error at token " + s.i + ": " + s.token + ".\n" + msg); + return true; +} +function donothing(){ return true; }; + +function consumeAListOfRules(s, topLevel) { + var rules = []; + var rule; + while(s.consume()) { + if(s.token instanceof WhitespaceToken) { + continue; + } else if(s.token instanceof EOFToken) { + return rules; + } else if(s.token instanceof CDOToken || s.token instanceof CDCToken) { + if(topLevel == "top-level") continue; + s.reconsume(); + if(rule = consumeAQualifiedRule(s)) rules.push(rule); + } else if(s.token instanceof AtKeywordToken) { + s.reconsume(); + if(rule = consumeAnAtRule(s)) rules.push(rule); + } else { + s.reconsume(); + if(rule = consumeAQualifiedRule(s)) rules.push(rule); + } + } +} + +function consumeAnAtRule(s) { + s.consume(); + var rule = new AtRule(s.token.value); + while(s.consume()) { + if(s.token instanceof SemicolonToken || s.token instanceof EOFToken) { + return rule; + } else if(s.token instanceof OpenCurlyToken) { + rule.value = consumeASimpleBlock(s); + return rule; + } else if(s.token instanceof SimpleBlock && s.token.name == "{") { + rule.value = s.token; + return rule; + } else { + s.reconsume(); + rule.prelude.push(consumeAComponentValue(s)); + } + } +} + +function consumeAQualifiedRule(s) { + var rule = new QualifiedRule(); + while(s.consume()) { + if(s.token instanceof EOFToken) { + parseerror(s, "Hit EOF when trying to parse the prelude of a qualified rule."); + return; + } else if(s.token instanceof OpenCurlyToken) { + rule.value = consumeASimpleBlock(s); + return rule; + } else if(s.token instanceof SimpleBlock && s.token.name == "{") { + rule.value = token; + return rule; + } else { + s.reconsume(); + rule.prelude.push(consumeAComponentValue(s)); + } + } +} + +function consumeAListOfDeclarations(s) { + var decls = []; + while(s.consume()) { + if(s.token instanceof WhitespaceToken || s.token instanceof SemicolonToken) { + donothing(); + } else if(s.token instanceof EOFToken) { + return decls; + } else if(s.token instanceof AtKeywordToken) { + s.reconsume(); + decls.push(consumeAnAtRule(s)); + } else if(s.token instanceof IdentToken) { + var temp = [token]; + while(!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) + temp.push(consumeAComponentValue(s)); + var decl; + if(decl = consumeADeclaration(new TokenStream(temp))) decls.push(decl); + } else { + parseerror(s); + reconsume(); + while(!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) + consumeAComponentValue(s); + } + } +} + +function consumeADeclaration(s) { + // Assumes that the next input token will be an ident token. + s.consume(); + var decl = new Declaration(s.token.value); + while(s.next() instanceof WhitespaceToken) s.consume(); + if(!(s.next() instanceof ColonToken)) { + parseerror(s); + return; + } else { + s.consume(); + } + while(!(s.next() instanceof EOFToken)) { + decl.value.push(consumeAComponentValue(s)); + } + var foundImportant = false; + for(var i = decl.value.length - 1; i >= 0; i--) { + if(decl.value[i] instanceof WhitespaceToken) { + continue; + } else if(decl.value[i] instanceof IdentToken && decl.value[i].ASCIIMatch("important")) { + foundImportant = true; + } else if(foundImportant && decl.value[i] instanceof DelimToken && decl.value[i].value == "!") { + decl.value.splice(i, decl.value.length); + decl.important = true; + break; + } else { + break; + } + } + return decl; +} + +function consumeAComponentValue(s) { + s.consume(); + if(s.token instanceof OpenCurlyToken || s.token instanceof OpenSquareToken || s.token instanceof OpenParenToken) + return consumeASimpleBlock(); + if(s.token instanceof FunctionToken) + return consumeAFunction(); + return s.token; +} + +function consumeASimpleBlock(s) { + var mirror = s.token.mirror; + var block = new SimpleBlock(s.token.value); + while(s.consume()) { + if(s.token instanceof EOFToken || (s.token instanceof GroupingToken && s.token.value == mirror)) + return block; + else { + s.reconsume(); + block.value.push(consumeAComponentValue(s)); + } + } +} + +function consumeAFunction(s) { + var func = new Func(s.token.value); + while(s.consume()) { + if(s.token instanceof EOFToken || s.token instanceof CloseParenToken) + return func; + else { + s.reconsume(); + func.value.push(consumeAComponentValue(s)); + } + } +} + +function normalizeInput(input) { + if(typeof input == "string") + return new TokenStream(tokenize(input)); + if(input instanceof TokenStream) + return input; + if(input.length !== undefined) + return new TokenStream(input); + else throw SyntaxError(input); +} + +function parseAStylesheet(s) { + s = normalizeInput(s); + var sheet = new Stylesheet(); + sheet.value = consumeAListOfRules(s, "top-level"); + return sheet; +} + +function parseAListOfRules(s) { + s = normalizeInput(s); + return consumeAListOfRules(s); +} + +function parseARule(s) { + s = normalizeInput(s); + while(s.next() instanceof WhitespaceToken) s.consume(); + if(s.next() instanceof EOFToken) throw SyntaxError(); + if(s.next() instanceof AtKeywordToken) { + var rule = consumeAnAtRule(s); + } else { + var rule = consumeAQualifiedRule(s); + if(!rule) throw SyntaxError(); + } + while(s.next() instanceof WhitespaceToken) s.consume(); + if(s.next() instanceof EOFToken) + return rule; + throw SyntaxError(); +} + +function parseADeclaration(s) { + s = normalizeInput(s); + while(s.next() instanceof WhitespaceToken) s.consume(); + if(!(s.next() instanceof IdentToken)) throw SyntaxError(); + var decl = consumeADeclaration(); + if(decl) + return decl + else + throw SyntaxError(); +} + +function parseAListOfDeclarations(s) { + s = normalizeInput(s); + return consumeAListOfDeclarations(s); +} + +function parseAComponentValue(s) { + s = normalizeInput(s); + while(s.next() instanceof WhitespaceToken) s.consume(); + if(s.next() instanceof EOFToken) throw SyntaxError(); + var val = consumeAComponentValue(s); + if(!val) throw SyntaxError(); + while(s.next() instanceof WhitespaceToken) s.consume(); + if(s.next() instanceof EOFToken) + return val; + throw SyntaxError(); +} + +function parseAListOfComponentValues(s) { + var vals = []; + while(true) { + var val = consumeAComponentValue(s); + if(val instanceof EOFToken) + return vals + else + vals.push(val); + } +} + +function parseACommaSeparatedListOfComponentValues(s) { + var listOfCVLs = []; + while(true) { + var vals = []; + while(true) { + var val = consumeAComponentValue(s); + if(val instanceof EOFToken) { + return listOfCVLS; + } else if(val instanceof CommaToken) { + listOfCVLS.push(vals); + break; + } else { + vals.push(val); + } + } + } +} + + +function CSSParserRule() { throw "Abstract Base Class"; } +CSSParserRule.prototype.toString = function(indent) { + return JSON.stringify(this,null,indent); +} +CSSParserRule.prototype.toJSON = function() { + return {type:this.type, value:this.value}; +} + +function Stylesheet() { + this.value = []; + return this; +} +Stylesheet.prototype = Object.create(CSSParserRule.prototype); +Stylesheet.prototype.type = "STYLESHEET"; + +function AtRule(name) { + this.name = name; + this.prelude = []; + this.value = null; + return this; +} +AtRule.prototype = Object.create(CSSParserRule.prototype); +AtRule.prototype.type = "AT-RULE"; +AtRule.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.name = this.name; + json.prelude = this.prelude; + return json; +} + +function QualifiedRule() { + this.prelude = []; + this.value = []; + return this; +} +QualifiedRule.prototype = Object.create(CSSParserRule.prototype); +QualifiedRule.prototype.type = "QUALIFIED-RULE"; +QualifiedRule.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.prelude = this.prelude; + return json; +} + +function Declaration(name) { + this.name = name; + this.value = []; + this.important = false; + return this; +} +Declaration.prototype = Object.create(CSSParserRule.prototype); +Declaration.prototype.type = "DECLARATION"; + +function SimpleBlock(type) { + this.name = type; + this.value = []; + return this; +} +SimpleBlock.prototype = Object.create(CSSParserRule.prototype); +SimpleBlock.prototype.type = "BLOCK"; +SimpleBlock.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.name = this.name; + return json; +} + +function Func(name) { + this.name = name; + this.value = []; + return this; +} +Func.prototype = Object.create(CSSParserRule.prototype); +Func.prototype.type = "FUNCTION"; +Func.prototype.toJSON = function() { + var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); + json.name = this.name; + return json; +} + +// Exportation. +exports.CSSParserRule = CSSParserRule; +exports.Stylesheet = Stylesheet; +exports.AtRule = AtRule; +exports.QualifiedRule = QualifiedRule; +exports.Declaration = Declaration; +exports.SimpleBlock = SimpleBlock; +exports.Func = Func; +exports.parseAStylesheet = parseAStylesheet; +exports.parseAListOfRules = parseAListOfRules; +exports.parseARule = parseARule; +exports.parseADeclaration = parseADeclaration; +exports.parseAListOfDeclarations = parseAListOfDeclarations; +exports.parseAComponentValue = parseAComponentValue; +exports.parseAListOfComponentValues = parseAListOfComponentValues; +exports.parseACommaSeparatedListOfComponentValues = parseACommaSeparatedListOfComponentValues; + +})); diff --git a/tests.js b/tests.js index 3ed8d39..a8ae3e3 100644 --- a/tests.js +++ b/tests.js @@ -1,84 +1,75 @@ -// This test suite runs on NodeJS and uses the 'ansidiff' package. -// Once you have NodeJS, install ansidiff with: -// -// npm install ansidiff -// -// Then run the test suite with: -// -// node tests.js - -var TESTS = [ - { - css: 'foo { bar: baz; }', - expected: {"type": "stylesheet", "value": [ - { - "type": "selector", - "selector": ["IDENT(foo)", "WS"], - "value": [ - { - "type": "declaration", - "name": "bar", - "value": ["WS", "IDENT(baz)"]}]}] - } - }, { - css: 'foo { bar: rgb(255, 0, 127); }', - expected: {"type": "stylesheet", "value": [ - { - "type": "selector", - "selector": ["IDENT(foo)", "WS"], - "value": [ - { - "type": "declaration", - "name": "bar", - "value": ["WS", {"type": "func", "name": "rgb", "value": [ - ["INT(255)"], ["WS", "INT(0)"], ["WS", "INT(127)"]]}]}]}] - } - }, { - css: '#foo {}', - expected: {"type": "stylesheet", "value": [ - { - "type": "selector", - "selector": ["HASH(foo)", "WS"], - "value": []}] - } - }, { - css: '@media{ }', - expected: {"type": "stylesheet", "value": [ - { - "type": "at", "name": "media", - "prelude": [], - "value": []}]} - } -]; - - -var ansidiff = require('ansidiff'), - tokenize = require('./tokenizer').tokenize, - parse = require('./parser').parse; - -var total = TESTS.length, failures = 0, - i, test, tokens, sheet, dump, expected_dump; - -for (i = 0; i < total; i++) { - test = TESTS[i]; - sheet = parseAStylesheet(test.css); - dump = sheet.toString(' '); - expected_dump = JSON.stringify(test.expected, null, ' '); - if (dump == expected_dump) { - console.log('Test %d of %d: PASS', i, total); - } else { - console.log('Test %d of %d: FAIL\nCSS: %s\nTokens: %s', - i, total, test.css, tokens.join(' ')); - console.log(ansidiff.lines(expected_dump, dump)); - failures++; - } -} - -// Abuse the differ to get colored output -if (failures == 0) { - console.log(ansidiff.words('%d tests, ', '%d tests, all passed :)'), - total); -} else { - console.log(ansidiff.words('%d tests, %d failures :(', '%d tests, '), - total, failures); -} +var TESTS = [ + { + css: 'foo { bar: baz; }', + expected: {"type": "stylesheet", "value": [ + { + "type": "selector", + "selector": ["IDENT(foo)", "WS"], + "value": [ + { + "type": "declaration", + "name": "bar", + "value": ["WS", "IDENT(baz)"]}]}] + } + }, { + css: 'foo { bar: rgb(255, 0, 127); }', + expected: {"type": "stylesheet", "value": [ + { + "type": "selector", + "selector": ["IDENT(foo)", "WS"], + "value": [ + { + "type": "declaration", + "name": "bar", + "value": ["WS", {"type": "func", "name": "rgb", "value": [ + ["INT(255)"], ["WS", "INT(0)"], ["WS", "INT(127)"]]}]}]}] + } + }, { + css: '#foo {}', + expected: {"type": "stylesheet", "value": [ + { + "type": "selector", + "selector": ["HASH(foo)", "WS"], + "value": []}] + } + }, { + css: '@media{ }', + expected: {"type": "stylesheet", "value": [ + { + "type": "at", "name": "media", + "prelude": [], + "value": []}]} + } +]; + + +var ansidiff = require('ansidiff'), + tokenize = require('./parse-css').tokenize, + parseAStylesheet = require('./parse-css').parseAStylesheet; + +var total = TESTS.length, failures = 0, + i, test, tokens, sheet, dump, expected_dump; + +for (i = 0; i < total; i++) { + test = TESTS[i]; + sheet = parseAStylesheet(test.css); + dump = sheet.toString(' '); + expected_dump = JSON.stringify(test.expected, null, ' '); + if (dump == expected_dump) { + console.log('Test %d of %d: PASS', i, total); + } else { + console.log('Test %d of %d: FAIL\nCSS: %s\nTokens: %s', + i, total, test.css, tokens.join(' ')); + console.log(ansidiff.lines(expected_dump, dump)); + failures++; + } +} + +// Abuse the differ to get colored output +if (failures == 0) { + console.log(ansidiff.words('%d tests, ', '%d tests, all passed :)'), + total); +} else { + console.log(ansidiff.words('%d tests, %d failures :(', '%d tests, '), + total, failures); +}