diff --git a/src/parsers/directive.js b/src/parsers/directive.js index b565f3a8008..0ee9eea56b7 100644 --- a/src/parsers/directive.js +++ b/src/parsers/directive.js @@ -2,36 +2,173 @@ import { toNumber, stripQuotes } from '../util/index' import Cache from '../cache' const cache = new Cache(1000) -const filterTokenRE = /[^\s'"]+|'[^']*'|"[^"]*"/g const reservedArgRE = /^in$|^-?\d+/ /** * Parser state */ -var str, dir -var c, prev, i, l, lastFilterIndex -var inSingle, inDouble, curly, square, paren +var str, dir, len +var index +var chr +var state +var startState = 0 +var filterState = 1 +var filterNameState = 2 +var filterArgState = 3 + +var doubleChr = 0x22 +var singleChr = 0x27 +var pipeChr = 0x7C +var escapeChr = 0x5C +var spaceChr = 0x20 + +var expStartChr = { 0x5B: 1, 0x7B: 1, 0x28: 1 } +var expChrPair = { 0x5B: 0x5D, 0x7B: 0x7D, 0x28: 0x29 } + +function peek () { + return str.charCodeAt(index + 1) +} + +function next () { + return str.charCodeAt(++index) +} + +function eof () { + return index >= len +} + +function eatSpace () { + while (peek() === spaceChr) { + next() + } +} + +function isStringStart (chr) { + return chr === doubleChr || chr === singleChr +} + +function isExpStart (chr) { + return expStartChr[chr] +} + +function isExpEnd (start, chr) { + return expChrPair[start] === chr +} + +function parseString () { + var stringQuote = next() + var chr + while (!eof()) { + chr = next() + // escape char + if (chr === escapeChr) { + next() + } else if (chr === stringQuote) { + break + } + } +} + +function parseSpecialExp (chr) { + var inExp = 0 + var startChr = chr + + while (!eof()) { + chr = peek() + if (isStringStart(chr)) { + parseString() + continue + } + + if (startChr === chr) { + inExp++ + } + if (isExpEnd(startChr, chr)) { + inExp-- + } + + next() + + if (inExp === 0) { + break + } + } +} /** - * Push a filter to the current directive object + * syntax: + * expression | filterName [arg arg [| filterName arg arg]] */ -function pushFilter () { - var exp = str.slice(lastFilterIndex, i).trim() - var filter - if (exp) { - filter = {} - var tokens = exp.match(filterTokenRE) - filter.name = tokens[0] - if (tokens.length > 1) { - filter.args = tokens.slice(1).map(processFilterArg) +function parseExpression () { + var start = index + while (!eof()) { + chr = peek() + if (isStringStart(chr)) { + parseString() + } else if (isExpStart(chr)) { + parseSpecialExp(chr) + } else if (chr === pipeChr) { + next() + chr = peek() + if (chr === pipeChr) { + next() + } else { + if (state === startState || state === filterArgState) { + state = filterState + } + break + } + } else if (chr === spaceChr && (state === filterNameState || state === filterArgState)) { + eatSpace() + break + } else { + if (state === filterState) { + state = filterNameState + } + next() } } - if (filter) { - (dir.filters = dir.filters || []).push(filter) + + return str.slice(start + 1, index) || null +} + +function parseFilterList () { + var filters = [] + while (!eof()) { + filters.push(parseFilter()) } - lastFilterIndex = i + 1 + return filters +} + +function parseFilter () { + var filter = {} + var args + + state = filterState + filter.name = parseExpression().trim() + + state = filterArgState + args = parseFilterArguments() + + if (args.length) { + filter.args = args + } + return filter +} + +function parseFilterArguments () { + var args = [] + while (!eof() && state !== filterState) { + var arg = parseExpression() + if (!arg) { + break + } + args.push(processFilterArg(arg)) + } + + return args } /** @@ -83,51 +220,22 @@ export function parseDirective (s) { // reset parser state str = s - inSingle = inDouble = false - curly = square = paren = 0 - lastFilterIndex = 0 dir = {} + len = str.length + index = -1 + chr = '' + state = startState - for (i = 0, l = str.length; i < l; i++) { - prev = c - c = str.charCodeAt(i) - if (inSingle) { - // check single quote - if (c === 0x27 && prev !== 0x5C) inSingle = !inSingle - } else if (inDouble) { - // check double quote - if (c === 0x22 && prev !== 0x5C) inDouble = !inDouble - } else if ( - c === 0x7C && // pipe - str.charCodeAt(i + 1) !== 0x7C && - str.charCodeAt(i - 1) !== 0x7C - ) { - if (dir.expression == null) { - // first filter, end of expression - lastFilterIndex = i + 1 - dir.expression = str.slice(0, i).trim() - } else { - // already has filter - pushFilter() - } - } else { - switch (c) { - case 0x22: inDouble = true; break // " - case 0x27: inSingle = true; break // ' - case 0x28: paren++; break // ( - case 0x29: paren--; break // ) - case 0x5B: square++; break // [ - case 0x5D: square--; break // ] - case 0x7B: curly++; break // { - case 0x7D: curly--; break // } - } - } - } + var filters - if (dir.expression == null) { - dir.expression = str.slice(0, i).trim() - } else if (lastFilterIndex !== 0) { - pushFilter() + if (str.indexOf('|') < 0) { + dir.expression = str.trim() + } else { + dir.expression = parseExpression().trim() + filters = parseFilterList() + if (filters.length) { + dir.filters = filters + } } cache.put(s, dir) diff --git a/test/unit/specs/parsers/directive_spec.js b/test/unit/specs/parsers/directive_spec.js index 982207c1482..1abcc356c1c 100644 --- a/test/unit/specs/parsers/directive_spec.js +++ b/test/unit/specs/parsers/directive_spec.js @@ -85,6 +85,32 @@ describe('Directive Parser', function () { expect(res.filters[0].args).toBeUndefined() }) + it('white spaces inside object literal', function () { + var res = parse('abc | filter {a:1} {b: 2}') + expect(res.expression).toBe('abc') + expect(res.filters.length).toBe(1) + expect(res.filters[0].name).toBe('filter') + expect(res.filters[0].args.length).toBe(2) + expect(res.filters[0].args[0].value).toBe('{a:1}') + expect(res.filters[0].args[0].dynamic).toBe(true) + expect(res.filters[0].args[1].value).toBe('{b: 2}') + expect(res.filters[0].args[1].dynamic).toBe(true) + }) + + it('white spaces inside array literal', function () { + var res = parse('abc | filter0 abc||def | filter1 [ 1, { a: 2 }]') + expect(res.expression).toBe('abc') + expect(res.filters.length).toBe(2) + expect(res.filters[0].name).toBe('filter0') + expect(res.filters[0].args.length).toBe(1) + expect(res.filters[0].args[0].value).toBe('abc||def') + expect(res.filters[0].args[0].dynamic).toBe(true) + expect(res.filters[1].name).toBe('filter1') + expect(res.filters[1].args.length).toBe(1) + expect(res.filters[1].args[0].value).toBe('[ 1, { a: 2 }]') + expect(res.filters[1].args[0].dynamic).toBe(true) + }) + it('cache', function () { var res1 = parse('a || b | c') var res2 = parse('a || b | c')