diff --git a/lib/parser/const.js b/lib/parser/const.js index 35417241..056065ab 100644 --- a/lib/parser/const.js +++ b/lib/parser/const.js @@ -15,7 +15,7 @@ var SPACE = 32; var TokenType = { Whitespace: WHITESPACE, Identifier: IDENTIFIER, - DecimalNumber: NUMBER, + Number: NUMBER, String: STRING, Comment: COMMENT, Punctuator: PUNCTUATOR, diff --git a/lib/parser/index.js b/lib/parser/index.js index 634951c4..bc998864 100644 --- a/lib/parser/index.js +++ b/lib/parser/index.js @@ -15,7 +15,7 @@ var SPACE_NODE = { type: 'Space' }; var WHITESPACE = TokenType.Whitespace; var IDENTIFIER = TokenType.Identifier; -var DECIMALNUMBER = TokenType.DecimalNumber; +var DECIMALNUMBER = TokenType.Number; var STRING = TokenType.String; var COMMENT = TokenType.Comment; var EXCLAMATIONMARK = TokenType.ExclamationMark; @@ -1456,15 +1456,6 @@ function readNumber() { tokenType = scanner.lookupType(++offset); } - if (tokenType === FULLSTOP) { - tokenType = scanner.lookupType(++offset); - } - - if (tokenType === DECIMALNUMBER) { - wasDigits = true; - offset++; - } - if (wasDigits) { scanner.skip(offset); diff --git a/lib/parser/scanner.js b/lib/parser/scanner.js index 14c01e8c..2ee2ee6c 100644 --- a/lib/parser/scanner.js +++ b/lib/parser/scanner.js @@ -12,7 +12,7 @@ var isHex = require('./utils').isHex; var NULL = 0; var WHITESPACE = TokenType.Whitespace; var IDENTIFIER = TokenType.Identifier; -var NUMBER = TokenType.DecimalNumber; +var NUMBER = TokenType.Number; var STRING = TokenType.String; var COMMENT = TokenType.Comment; var PUNCTUATOR = TokenType.Punctuator; @@ -25,6 +25,10 @@ var SPACE = 32; var STAR = 42; var SLASH = 47; var BACK_SLASH = 92; +var FULLSTOP = TokenType.FullStop; +var E = 'e'.charCodeAt(0); +var PLUSSIGN = TokenType.PlusSign; +var HYPHENMINUS = TokenType.HyphenMinus; var MIN_ARRAY_SIZE = 16 * 1024; var OFFSET_MASK = 0x00FFFFFF; @@ -128,6 +132,38 @@ function findDecimalNumberEnd(source, offset) { return offset; } +function findNumberEnd(source, offset, allowFullstop) { + offset = findDecimalNumberEnd(source, offset); + + if (allowFullstop && offset + 1 < source.length && source.charCodeAt(offset) === FULLSTOP) { + var code = source.charCodeAt(offset + 1); + if (code >= 48 && code <= 57) { + offset = findDecimalNumberEnd(source, offset + 1); + } + } + + if (offset + 1 < source.length) { + if ((source.charCodeAt(offset) | 32) === E) { + var code = source.charCodeAt(offset + 1); + if (code === PLUSSIGN || code === HYPHENMINUS) { + if (offset + 2 >= source.length) { + return offset; + } + code = source.charCodeAt(offset + 2); + if (code >= 48 && code <= 57) { + offset = findDecimalNumberEnd(source, offset + 3); + } + } else { + if (code >= 48 && code <= 57) { + offset = findDecimalNumberEnd(source, offset + 2); + } + } + } + } + + return offset; +} + // skip escaped unicode sequence that can ends with space // [0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? function findEscaseEnd(source, offset) { @@ -188,9 +224,7 @@ function tokenLayout(scanner, source, startPos) { if (code === STAR && prevType === SLASH) { // /* type = COMMENT; offset = findCommentEnd(source, offset + 1); - - // rewrite prev token - tokenCount--; + tokenCount--; // rewrite prev token } else { type = code; offset = offset + 1; @@ -199,7 +233,10 @@ function tokenLayout(scanner, source, startPos) { break; case NUMBER: - offset = findDecimalNumberEnd(source, offset + 1); + offset = findNumberEnd(source, offset + 1, prevType !== FULLSTOP); + if (prevType === FULLSTOP) { + tokenCount--; // rewrite prev token + } break; case STRING: @@ -387,6 +424,10 @@ Scanner.prototype = { location.line, location.column ); + }, + + getTypes: function() { + return Array.prototype.slice.call(this.offsetAndType, 0, this.tokenCount).map(x => TokenName[x]); } }; diff --git a/test/fixture/parse-errors.json b/test/fixture/parse-errors.json index 50974ad6..b5fd5137 100644 --- a/test/fixture/parse-errors.json +++ b/test/fixture/parse-errors.json @@ -32,11 +32,11 @@ } }, { "css": ".1px {}", - "error": "Identifier is expected", + "error": "PercentSign is expected", "position": { - "offset": 1, + "offset": 2, "line": 1, - "column": 2 + "column": 3 } }, { "css": "# {}", @@ -86,6 +86,14 @@ "line": 1, "column": 13 } +}, { + "css": "a { width: 20. }", + "error": "Unexpected input", + "position": { + "offset": 13, + "line": 1, + "column": 14 + } }, { "css": ": {}", "error": "Identifier is expected", diff --git a/test/fixture/parse/value/Number.json b/test/fixture/parse/value/Number.json index e1140fb9..021a2566 100644 --- a/test/fixture/parse/value/Number.json +++ b/test/fixture/parse/value/Number.json @@ -54,5 +54,75 @@ "type": "Number", "value": "1.200000" } + }, + "with exponent #1": { + "source": "1.2e2", + "ast": { + "type": "Number", + "value": "1.2e2" + } + }, + "with exponent #2": { + "source": "1e2", + "ast": { + "type": "Number", + "value": "1e2" + } + }, + "with exponent #3": { + "source": ".2e2", + "ast": { + "type": "Number", + "value": ".2e2" + } + }, + "with exponent #4": { + "source": "1.2E2", + "ast": { + "type": "Number", + "value": "1.2E2" + } + }, + "with exponent #5": { + "source": "1.2e+2", + "ast": { + "type": "Number", + "value": "1.2e+2" + } + }, + "with exponent #6": { + "source": "1.2e-2", + "ast": { + "type": "Number", + "value": "1.2e-2" + } + }, + "with sign #1": { + "source": "-1", + "ast": { + "type": "Number", + "value": "-1" + } + }, + "with sign #2": { + "source": "-1.2", + "ast": { + "type": "Number", + "value": "-1.2" + } + }, + "with sign #3": { + "source": "-.2", + "ast": { + "type": "Number", + "value": "-.2" + } + }, + "with sign #4": { + "source": "-1.2e3", + "ast": { + "type": "Number", + "value": "-1.2e3" + } } } diff --git a/test/fixture/syntax/generic.json b/test/fixture/syntax/generic.json index b37db885..28fa80b5 100644 --- a/test/fixture/syntax/generic.json +++ b/test/fixture/syntax/generic.json @@ -231,7 +231,6 @@ "valid": [ "123", "123.456", - "123.", ".456", "calc(2 * 1.5)" ], @@ -277,7 +276,6 @@ "", "123.456", "-123.456", - "123.", ".456", "foo 123" ] @@ -298,7 +296,6 @@ "-123", "123.456", "-123.456", - "123.", ".456", "foo 123" ]