var _ = require('../util') var Cache = require('../cache') var cache = new Cache(1000) var argRE = /^[^\{\?]+$|^'[^']*'$|^"[^"]*"$/ var filterTokenRE = /[^\s'"]+|'[^']*'|"[^"]*"/g var reservedArgRE = /^in$|^-?\d+/ /** * Parser state */ var str var c, i, l var inSingle var inDouble var curly var square var paren var begin var argIndex var dirs var dir var lastFilterIndex var arg /** * Push a directive object into the result Array */ function pushDir () { dir.raw = str.slice(begin, i).trim() if (dir.expression === undefined) { dir.expression = str.slice(argIndex, i).trim() } else if (lastFilterIndex !== begin) { pushFilter() } if (i === 0 || dir.expression) { dirs.push(dir) } } /** * Push a filter to the current directive object */ 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) } } if (filter) { (dir.filters = dir.filters || []).push(filter) } lastFilterIndex = i + 1 } /** * Check if an argument is dynamic and strip quotes. * * @param {String} arg * @return {Object} */ function processFilterArg (arg) { var stripped = reservedArgRE.test(arg) ? arg : _.stripQuotes(arg) var dynamic = stripped === false return { value: dynamic ? arg : stripped, dynamic: dynamic } } /** * Parse a directive string into an Array of AST-like * objects representing directives. * * Example: * * "click: a = a + 1 | uppercase" will yield: * { * arg: 'click', * expression: 'a = a + 1', * filters: [ * { name: 'uppercase', args: null } * ] * } * * @param {String} str * @return {Array} */ exports.parse = function (s) { var hit = cache.get(s) if (hit) { return hit } // reset parser state str = s inSingle = inDouble = false curly = square = paren = begin = argIndex = 0 lastFilterIndex = 0 dirs = [] dir = {} arg = null for (i = 0, l = str.length; i < l; i++) { c = str.charCodeAt(i) if (inSingle) { // check single quote if (c === 0x27) inSingle = !inSingle } else if (inDouble) { // check double quote if (c === 0x22) inDouble = !inDouble } else if ( c === 0x2C && // comma !paren && !curly && !square ) { // reached the end of a directive pushDir() // reset & skip the comma dir = {} begin = argIndex = lastFilterIndex = i + 1 } else if ( c === 0x3A && // colon !dir.expression && !dir.arg ) { // argument arg = str.slice(begin, i).trim() // test for valid argument here // since we may have caught stuff like first half of // an object literal or a ternary expression. if (argRE.test(arg)) { argIndex = i + 1 dir.arg = _.stripQuotes(arg) || arg } } else if ( c === 0x7C && // pipe str.charCodeAt(i + 1) !== 0x7C && str.charCodeAt(i - 1) !== 0x7C ) { if (dir.expression === undefined) { // first filter, end of expression lastFilterIndex = i + 1 dir.expression = str.slice(argIndex, 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 // } } } } if (i === 0 || begin !== i) { pushDir() } cache.put(s, dirs) return dirs }