directive.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. var _ = require('../util')
  2. var Cache = require('../cache')
  3. var cache = new Cache(1000)
  4. var argRE = /^[^\{\?]+$|^'[^']*'$|^"[^"]*"$/
  5. var filterTokenRE = /[^\s'"]+|'[^']+'|"[^"]+"/g
  6. var reservedArgRE = /^in$|^-?\d+/
  7. /**
  8. * Parser state
  9. */
  10. var str
  11. var c, i, l
  12. var inSingle
  13. var inDouble
  14. var curly
  15. var square
  16. var paren
  17. var begin
  18. var argIndex
  19. var dirs
  20. var dir
  21. var lastFilterIndex
  22. var arg
  23. /**
  24. * Push a directive object into the result Array
  25. */
  26. function pushDir () {
  27. dir.raw = str.slice(begin, i).trim()
  28. if (dir.expression === undefined) {
  29. dir.expression = str.slice(argIndex, i).trim()
  30. } else if (lastFilterIndex !== begin) {
  31. pushFilter()
  32. }
  33. if (i === 0 || dir.expression) {
  34. dirs.push(dir)
  35. }
  36. }
  37. /**
  38. * Push a filter to the current directive object
  39. */
  40. function pushFilter () {
  41. var exp = str.slice(lastFilterIndex, i).trim()
  42. var filter
  43. if (exp) {
  44. filter = {}
  45. var tokens = exp.match(filterTokenRE)
  46. filter.name = tokens[0]
  47. if (tokens.length > 1) {
  48. filter.args = tokens.slice(1).map(function (arg) {
  49. var stripped = reservedArgRE.test(arg)
  50. ? arg
  51. : _.stripQuotes(arg)
  52. return {
  53. value: stripped || arg,
  54. dynamic: !stripped
  55. }
  56. })
  57. }
  58. }
  59. if (filter) {
  60. (dir.filters = dir.filters || []).push(filter)
  61. }
  62. lastFilterIndex = i + 1
  63. }
  64. /**
  65. * Parse a directive string into an Array of AST-like
  66. * objects representing directives.
  67. *
  68. * Example:
  69. *
  70. * "click: a = a + 1 | uppercase" will yield:
  71. * {
  72. * arg: 'click',
  73. * expression: 'a = a + 1',
  74. * filters: [
  75. * { name: 'uppercase', args: null }
  76. * ]
  77. * }
  78. *
  79. * @param {String} str
  80. * @return {Array<Object>}
  81. */
  82. exports.parse = function (s) {
  83. var hit = cache.get(s)
  84. if (hit) {
  85. return hit
  86. }
  87. // reset parser state
  88. str = s
  89. inSingle = inDouble = false
  90. curly = square = paren = begin = argIndex = 0
  91. lastFilterIndex = 0
  92. dirs = []
  93. dir = {}
  94. arg = null
  95. for (i = 0, l = str.length; i < l; i++) {
  96. c = str.charCodeAt(i)
  97. if (inSingle) {
  98. // check single quote
  99. if (c === 0x27) inSingle = !inSingle
  100. } else if (inDouble) {
  101. // check double quote
  102. if (c === 0x22) inDouble = !inDouble
  103. } else if (
  104. c === 0x2C && // comma
  105. !paren && !curly && !square
  106. ) {
  107. // reached the end of a directive
  108. pushDir()
  109. // reset & skip the comma
  110. dir = {}
  111. begin = argIndex = lastFilterIndex = i + 1
  112. } else if (
  113. c === 0x3A && // colon
  114. !dir.expression &&
  115. !dir.arg
  116. ) {
  117. // argument
  118. arg = str.slice(begin, i).trim()
  119. // test for valid argument here
  120. // since we may have caught stuff like first half of
  121. // an object literal or a ternary expression.
  122. if (argRE.test(arg)) {
  123. argIndex = i + 1
  124. dir.arg = _.stripQuotes(arg) || arg
  125. }
  126. } else if (
  127. c === 0x7C && // pipe
  128. str.charCodeAt(i + 1) !== 0x7C &&
  129. str.charCodeAt(i - 1) !== 0x7C
  130. ) {
  131. if (dir.expression === undefined) {
  132. // first filter, end of expression
  133. lastFilterIndex = i + 1
  134. dir.expression = str.slice(argIndex, i).trim()
  135. } else {
  136. // already has filter
  137. pushFilter()
  138. }
  139. } else {
  140. switch (c) {
  141. case 0x22: inDouble = true; break // "
  142. case 0x27: inSingle = true; break // '
  143. case 0x28: paren++; break // (
  144. case 0x29: paren--; break // )
  145. case 0x5B: square++; break // [
  146. case 0x5D: square--; break // ]
  147. case 0x7B: curly++; break // {
  148. case 0x7D: curly--; break // }
  149. }
  150. }
  151. }
  152. if (i === 0 || begin !== i) {
  153. pushDir()
  154. }
  155. cache.put(s, dirs)
  156. return dirs
  157. }