Browse Source

directive parser and test

Evan You 12 years ago
parent
commit
0060fe0f0b

+ 132 - 0
src/parse/directive.js

@@ -0,0 +1,132 @@
+var Cache = require('../cache')
+var cache = new Cache(1000)
+var ARG_RE = /^[\w\$-]+$/
+var FILTER_TOKEN_RE = /[^\s'"]+|'[^']+'|"[^"]+"/g
+
+/**
+ * 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(FILTER_TOKEN_RE)
+    filter.name = tokens[0]
+    filter.args = tokens.length > 1 ? tokens.slice(1) : null
+  }
+  if (filter) {
+    (dir.filters = dir.filters || []).push(filter)
+  }
+  lastFilterIndex = i + 1
+}
+
+/**
+ * Parse a directive string into an Array of AST-like objects
+ * representing directives.
+ *
+ * @param {String} str
+ * @return {Array<Object>}
+ */
+
+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 = lastFilterIndex = 0
+  dirs = []
+  dir = {}
+  arg = null
+
+  for (i = 0, l = str.length; i < l; i++) {
+    c = str.charAt(i)
+    if (inSingle) {
+      // check single quote
+      if (c === "'") inSingle = !inSingle
+    } else if (inDouble) {
+      // check double quote
+      if (c === '"') inDouble = !inDouble
+    } else if (c === ',' && !paren && !curly && !square) {
+      // reached the end of a directive
+      pushDir()
+      // reset & skip the comma
+      dir = {}
+      begin = argIndex = lastFilterIndex = i + 1
+    } else if (c === ':' && !dir.expression && !dir.arg) {
+      // argument
+      arg = str.slice(begin, i).trim()
+      if (ARG_RE.test(arg)) {
+        argIndex = i + 1
+        dir.arg = arg
+      }
+    } else if (c === '|' && str.charAt(i + 1) !== '|' && str.charAt(i - 1) !== '|') {
+      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 '"': inDouble = true; break
+        case "'": inSingle = true; break
+        case '(': paren++; break
+        case ')': paren--; break
+        case '[': square++; break
+        case ']': square--; break
+        case '{': curly++; break
+        case '}': curly--; break
+      }
+    }
+  }
+
+  if (i === 0 || begin !== i) {
+    pushDir()
+  }
+
+  cache.put(s, dirs)
+  return dirs
+}

+ 106 - 0
test/unit/directive_parser_spec.js

@@ -0,0 +1,106 @@
+var parse = require('../../src/parse/directive').parse
+
+describe('Directive Parser', function () {
+
+  it('exp', function () {
+    var res = parse('exp')
+    expect(res.length).toBe(1)
+    expect(res[0].expression).toBe('exp')
+    expect(res[0].raw).toBe('exp')
+  })
+
+  it('arg:exp', function () {
+    var res = parse('arg:exp')
+    expect(res.length).toBe(1)
+    expect(res[0].expression).toBe('exp')
+    expect(res[0].arg).toBe('arg')
+    expect(res[0].raw).toBe('arg:exp')
+  })
+
+  it('arg : exp | abc', function () {
+    var res = parse(' arg : exp | abc de')
+    expect(res.length).toBe(1)
+    expect(res[0].expression).toBe('exp')
+    expect(res[0].arg).toBe('arg')
+    expect(res[0].raw).toBe('arg : exp | abc de')
+    expect(res[0].filters.length).toBe(1)
+    expect(res[0].filters[0].name).toBe('abc')
+    expect(res[0].filters[0].args.length).toBe(1)
+    expect(res[0].filters[0].args[0]).toBe('de')
+  })
+
+  it('a || b | c', function () {
+    var res = parse('a || b | c')
+    expect(res.length).toBe(1)
+    expect(res[0].expression).toBe('a || b')
+    expect(res[0].raw).toBe('a || b | c')
+    expect(res[0].filters.length).toBe(1)
+    expect(res[0].filters[0].name).toBe('c')
+    expect(res[0].filters[0].args).toBeNull()
+  })
+
+  it('a ? b : c', function () {
+    var res = parse('a ? b : c')
+    expect(res.length).toBe(1)
+    expect(res[0].expression).toBe('a ? b : c')
+    expect(res[0].filters).toBeUndefined()
+  })
+
+  it('"a:b:c||d|e|f" || d ? a : b', function () {
+    var res = parse('"a:b:c||d|e|f" || d ? a : b')
+    expect(res.length).toBe(1)
+    expect(res[0].expression).toBe('"a:b:c||d|e|f" || d ? a : b')
+    expect(res[0].filters).toBeUndefined()
+    expect(res[0].arg).toBeUndefined()
+  })
+
+  it('a, b, c', function () {
+    var res = parse('a, b, c')
+    expect(res.length).toBe(3)
+    expect(res[0].expression).toBe('a')
+    expect(res[1].expression).toBe('b')
+    expect(res[2].expression).toBe('c')
+  })
+
+  it('a:b | c, d:e | f, g:h | i', function () {
+    var res = parse('a:b | c, d:e | f, g:h | i')
+    expect(res.length).toBe(3)
+
+    expect(res[0].arg).toBe('a')
+    expect(res[0].expression).toBe('b')
+    expect(res[0].filters.length).toBe(1)
+    expect(res[0].filters[0].name).toBe('c')
+    expect(res[0].filters[0].args).toBeNull()
+
+    expect(res[1].arg).toBe('d')
+    expect(res[1].expression).toBe('e')
+    expect(res[1].filters.length).toBe(1)
+    expect(res[1].filters[0].name).toBe('f')
+    expect(res[1].filters[0].args).toBeNull()
+
+    expect(res[2].arg).toBe('g')
+    expect(res[2].expression).toBe('h')
+    expect(res[2].filters.length).toBe(1)
+    expect(res[2].filters[0].name).toBe('i')
+    expect(res[2].filters[0].args).toBeNull()
+  })
+
+  it('click:test(c.indexOf(d,f),"e,f"), input: d || [e,f], ok:{a:1,b:2}', function () {
+    var res = parse('click:test(c.indexOf(d,f),"e,f"), input: d || [e,f], ok:{a:1,b:2}')
+    expect(res.length).toBe(3)
+    expect(res[0].arg).toBe('click')
+    expect(res[0].expression).toBe('test(c.indexOf(d,f),"e,f")')
+    expect(res[1].arg).toBe('input')
+    expect(res[1].expression).toBe('d || [e,f]')
+    expect(res[1].filters).toBeUndefined()
+    expect(res[2].arg).toBe('ok')
+    expect(res[2].expression).toBe('{a:1,b:2}')
+  })
+
+  it('cache', function () {
+    var res1 = parse('a || b | c')
+    var res2 = parse('a || b | c')
+    expect(res1).toBe(res2)
+  })
+
+})

+ 0 - 0
test/unit/expression_spec.js → test/unit/expression_parser_spec.js


+ 0 - 0
test/unit/path_spec.js → test/unit/path_parser_spec.js