Просмотр исходного кода

expression parser generate setter too

Evan You 12 лет назад
Родитель
Сommit
8a868b8b17
2 измененных файлов с 51 добавлено и 17 удалено
  1. 39 15
      src/parse/expression.js
  2. 12 2
      test/unit/expression_parser_spec.js

+ 39 - 15
src/parse/expression.js

@@ -2,8 +2,6 @@ var _ = require('../util')
 var Cache = require('../cache')
 var Cache = require('../cache')
 var expressionCache = new Cache(1000)
 var expressionCache = new Cache(1000)
 
 
-function noop () {}
-
 /**
 /**
  * Extract all accessor paths from an expression.
  * Extract all accessor paths from an expression.
  *
  *
@@ -82,7 +80,7 @@ function save (str) {
  */
  */
 
 
 function rewrite (path) {
 function rewrite (path) {
-  return path.charAt(0) + 'scope.' + path.slice(1)
+  return 'scope.' + path
 }
 }
 
 
 /**
 /**
@@ -99,6 +97,7 @@ function restore (str, i) {
 
 
 /**
 /**
  * Build a getter function. Requires eval.
  * Build a getter function. Requires eval.
+ *
  * We isolate the try/catch so it doesn't affect the optimization
  * We isolate the try/catch so it doesn't affect the optimization
  * of the parse function when it is not called.
  * of the parse function when it is not called.
  *
  *
@@ -106,20 +105,41 @@ function restore (str, i) {
  * @return {Function|undefined}
  * @return {Function|undefined}
  */
  */
 
 
-function build (body) {
+function makeGetter (body) {
+  try {
+    return new Function('scope', 'return ' + body + ';')
+  } catch (e) {}
+}
+
+/**
+ * Build a setter function.
+ *
+ * This is only needed in rare situations like "a[b]" where
+ * a settable path requires dynamic evaluation.
+ *
+ * This setter function may throw error when called if the
+ * expression body is not a valid left-hand expression in
+ * assignment.
+ *
+ * @param {String} body
+ * @return {Function|undefined}
+ */
+
+function makeSetter (body) {
   try {
   try {
-    return new Function('scope', body)
+    return new Function('scope', 'value', body + ' = value;')
   } catch (e) {}
   } catch (e) {}
 }
 }
 
 
 /**
 /**
- * Parse an expression and rewrite into a getter function
+ * Parse an expression and rewrite into a getter/setter functions
  *
  *
  * @param {String} code
  * @param {String} code
+ * @param {Boolean} needSet
  * @return {Function}
  * @return {Function}
  */
  */
 
 
-exports.parse = function (code) {
+exports.parse = function (code, needSet) {
   // try cache
   // try cache
   var hit = expressionCache.get(code)
   var hit = expressionCache.get(code)
   if (hit) {
   if (hit) {
@@ -127,27 +147,31 @@ exports.parse = function (code) {
   }
   }
   // extract paths
   // extract paths
   var paths = extractPaths(code)
   var paths = extractPaths(code)
-  var body = 'return ' + code + ';'
+  var body = code
   // rewrite paths
   // rewrite paths
   if (paths.length) {
   if (paths.length) {
     var pathRE = new RegExp(
     var pathRE = new RegExp(
-      '[^$\\w\\.](' +
+      '(\\b|\\$)' +
       paths.map(escapeDollar).join('|') +
       paths.map(escapeDollar).join('|') +
-      ')[^$\\w\\.]',
+      '(\\b|\\$)',
       'g'
       'g'
     )
     )
     saved.length = 0
     saved.length = 0
-    body = body
+    body = body // pad for regex
       .replace(PREPARE_RE, save)
       .replace(PREPARE_RE, save)
       .replace(pathRE, rewrite)
       .replace(pathRE, rewrite)
       .replace(RESTORE_RE, restore)
       .replace(RESTORE_RE, restore)
+      .trim()
   }
   }
   // generate function
   // generate function
-  var fn = build(body)
-  if (fn) {
-    expressionCache.put(code, fn)
+  var getter = makeGetter(body)
+  if (getter) {
+    if (needSet) {
+      getter.setter = makeSetter(body)
+    }
+    expressionCache.put(code, getter)
   } else {
   } else {
     _.warn('Invalid expression: "' + code + '"\nGenerated function body: ' + body)
     _.warn('Invalid expression: "' + code + '"\nGenerated function body: ' + body)
   }
   }
-  return fn || noop
+  return getter
 }
 }

+ 12 - 2
test/unit/expression_parser_spec.js

@@ -8,7 +8,7 @@ function assertExp (testCase) {
 var testCases = [
 var testCases = [
   {
   {
     // string concat
     // string concat
-    exp: 'a + b',
+    exp: 'a+b',
     scope: {
     scope: {
       a: 'hello',
       a: 'hello',
       b: 'world'
       b: 'world'
@@ -94,10 +94,20 @@ var testCases = [
 
 
 describe('Expression Parser', function () {
 describe('Expression Parser', function () {
   
   
-  it('parse', function () {
+  it('parse getter', function () {
     testCases.forEach(assertExp)
     testCases.forEach(assertExp)
   })
   })
 
 
+  it('parse setter', function () {
+    var setter = expParser.parse('a[b]', true).setter
+    var scope = {
+      a: { c: 1 },
+      b: 'c'
+    }
+    setter(scope, 2)
+    expect(scope.a.c).toBe(2)
+  })
+
   it('cache', function () {
   it('cache', function () {
     var fn1 = expParser.parse('a + b')
     var fn1 = expParser.parse('a + b')
     var fn2 = expParser.parse('a + b')
     var fn2 = expParser.parse('a + b')