18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// Copyright 2006 Google Inc.
28ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen//
38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// Licensed under the Apache License, Version 2.0 (the "License");
48ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// you may not use this file except in compliance with the License.
58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// You may obtain a copy of the License at
68ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen//
78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// http://www.apache.org/licenses/LICENSE-2.0
88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen//
98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// Unless required by applicable law or agreed to in writing, software
108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// distributed under the License is distributed on an "AS IS" BASIS,
118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// implied. See the License for the specific language governing
138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen// permissions and limitations under the License.
148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Author: Steffen Meschkat <mesch@google.com>
168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @fileoverview This class is used to evaluate expressions in a local
188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context. Used by JstProcessor.
198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Names of special variables defined by the jstemplate evaluation
248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context. These can be used in js expression in jstemplate
258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * attributes.
268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar VAR_index = '$index';
288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar VAR_count = '$count';
298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar VAR_this = '$this';
308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar VAR_context = '$context';
318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar VAR_top = '$top';
328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * The name of the global variable which holds the value to be returned if
368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context evaluation results in an error.
378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.
388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar GLOB_default = '$default';
408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):
448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * So far, these are only used here, but we could use them thoughout
458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * the code and thus move them to constants.js.
468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar CHAR_colon = ':';
488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar REGEXP_semicolon = /\s*;\s*/;
498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * See constructor_()
538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object|null} opt_data
548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_parent
558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @constructor
568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction JsEvalContext(opt_data, opt_parent) {
588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.constructor_.apply(this, arguments);
598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Context for processing a jstemplate. The context contains a context
638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * object, whose properties can be referred to in jstemplate
648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * expressions, and it holds the locally defined variables.
658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object|null} opt_data The context object. Null if no context.
678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_parent The parent context, from which local
698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * variables are inherited. Normally the context object of the parent
708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context is the object whose property the parent object is. Null for the
718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context of the root object.
728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
738ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.prototype.constructor_ = function(opt_data, opt_parent) {
748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var me = this;
758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * The context for variable definitions in which the jstemplate
788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * expressions are evaluated. Other than for the local context,
798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * which replaces the parent context, variable definitions of the
808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * parent are inherited. The special variable $this points to data_.
818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   *
828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * If this instance is recycled from the cache, then the property is
838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * already initialized.
848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   *
858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * @type {Object}
868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (!me.vars_) {
888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    me.vars_ = {};
898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (opt_parent) {
918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // If there is a parent node, inherit local variables from the
928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // parent.
938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    copyProperties(me.vars_, opt_parent.vars_);
948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  } else {
958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // If a root node, inherit global symbols. Since every parent
968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // chain has a root with no parent, global variables will be
978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // present in the case above too. This means that globals can be
988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // overridden by locals, as it should be.
998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    copyProperties(me.vars_, JsEvalContext.globals_);
1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * The current context object is assigned to the special variable
1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * $this so it is possible to use it in expressions.
1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * @type Object
1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  me.vars_[VAR_this] = opt_data;
1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * The entire context structure is exposed as a variable so it can be
1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * passed to javascript invocations through jseval.
1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
1138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  me.vars_[VAR_context] = me;
1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * The local context of the input data in which the jstemplate
1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * expressions are evaluated. Notice that this is usually an Object,
1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * but it can also be a scalar value (and then still the expression
1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * $this can be used to refer to it). Notice this can even be value,
1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * undefined or null. Hence, we have to protect jsexec() from using
1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * undefined or null, yet we want $this to reflect the true value of
1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * the current context. Thus we assign the original value to $this,
1238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * above, but for the expression context we replace null and
1248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * undefined by the empty string.
1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   *
1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * @type {Object|null}
1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  me.data_ = getDefaultObject(opt_data, STRING_empty);
1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (!opt_parent) {
1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // If this is a top-level context, create a variable reference to the data
1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // to allow for  accessing top-level properties of the original context
1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // data from child contexts.
1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    me.vars_[VAR_top] = me.data_;
1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * A map of globally defined symbols. Every instance of JsExprContext
1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * inherits them in its vars_.
1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @type Object
1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1448ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.globals_ = {}
1458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Sets a global symbol. It will be available like a variable in every
1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * JsEvalContext instance. This is intended mainly to register
1508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * immutable global objects, such as functions, at load time, and not
1518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * to add global data at runtime. I.e. the same objections as to
1528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * global variables in general apply also here. (Hence the name
1538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * "global", and not "global var".)
1548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} name
1558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object|null} value
1568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1578ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.setGlobal = function(name, value) {
1588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  JsEvalContext.globals_[name] = value;
1598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Set the default value to be returned if context evaluation results in an
1648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * error. (This can occur if a non-existent value was requested).
1658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1668ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.setGlobal(GLOB_default, null);
1678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * A cache to reuse JsEvalContext instances. (IE6 perf)
1718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
1728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @type Array.<JsEvalContext>
1738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1748ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.recycledInstances_ = [];
1758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * A factory to create a JsEvalContext instance, possibly reusing
1798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * one from recycledInstances_. (IE6 perf)
1808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
1818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} opt_data
1828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {JsEvalContext} opt_parent
1838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {JsEvalContext}
1848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
1858ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.create = function(opt_data, opt_parent) {
1868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (jsLength(JsEvalContext.recycledInstances_) > 0) {
1878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var instance = JsEvalContext.recycledInstances_.pop();
1888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    JsEvalContext.call(instance, opt_data, opt_parent);
1898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return instance;
1908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  } else {
1918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return new JsEvalContext(opt_data, opt_parent);
1928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
1948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
1978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Recycle a used JsEvalContext instance, so we can avoid creating one
1988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * the next time we need one. (IE6 perf)
1998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {JsEvalContext} instance
2018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2028ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.recycle = function(instance) {
2038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  for (var i in instance.vars_) {
2048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // NOTE(mesch): We avoid object creation here. (IE6 perf)
2058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    delete instance.vars_[i];
2068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
2078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  instance.data_ = null;
2088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  JsEvalContext.recycledInstances_.push(instance);
2098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Executes a function created using jsEvalToFunction() in the context
2148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * of vars, data, and template.
2158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Function} exprFunction A javascript function created from
2178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * a jstemplate attribute value.
2188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Element} template DOM node of the template.
2208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Object|null} The value of the expression from which
2228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * exprFunction was created in the current js expression context and
2238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * the context of template.
2248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2258ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.prototype.jsexec = function(exprFunction, template) {
2268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  try {
2278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return exprFunction.call(template, this.vars_, this.data_);
2288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  } catch (e) {
2298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    log('jsexec EXCEPTION: ' + e + ' at ' + template +
2308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        ' with ' + exprFunction);
2318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return JsEvalContext.globals_[GLOB_default];
2328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
2338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Clones the current context for a new context object. The cloned
2388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context has the data object as its context object and the current
2398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context as its parent context. It also sets the $index variable to
2408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * the given value. This value usually is the position of the data
2418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * object in a list for which a template is instantiated multiply.
2428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object} data The new context object.
2448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {number} index Position of the new context when multiply
2468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * instantiated. (See implementation of jstSelect().)
2478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {number} count The total number of contexts that were multiply
2498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * instantiated. (See implementation of jstSelect().)
2508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {JsEvalContext}
2528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2538ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.prototype.clone = function(data, index, count) {
2548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var ret = JsEvalContext.create(data, this);
2558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  ret.setVariable(VAR_index, index);
2568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  ret.setVariable(VAR_count, count);
2578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return ret;
2588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Binds a local variable to the given value. If set from jstemplate
2638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * jsvalue expressions, variable names must start with $, but in the
2648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * API they only have to be valid javascript identifier.
2658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} name
2678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Object?} value
2698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2708ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.prototype.setVariable = function(name, value) {
2718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.vars_[name] = value;
2728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Returns the value bound to the local variable of the given name, or
2778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * undefined if it wasn't set. There is no way to distinguish a
2788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * variable that wasn't set from a variable that was set to
2798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * undefined. Used mostly for testing.
2808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} name
2828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Object?} value
2848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2858ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.prototype.getVariable = function(name) {
2868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return this.vars_[name];
2878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
2888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
2908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
2918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Evaluates a string expression within the scope of this context
2928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * and returns the result.
2938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} expr A javascript expression
2958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {Element} opt_template An optional node to serve as "this"
2968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
2978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Object?} value
2988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
2998ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.prototype.evalExpression = function(expr, opt_template) {
3008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var exprFunction = jsEvalToFunction(expr);
3018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return this.jsexec(exprFunction, opt_template);
3028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
3038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Uninlined string literals for jsEvalToFunction() (IE6 perf).
3078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar STRING_a = 'a_';
3098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar STRING_b = 'b_';
3108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenvar STRING_with = 'with (a_) with (b_) return ';
3118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Cache for jsEvalToFunction results.
3158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @type Object
3168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3178ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenJsEvalContext.evalToFunctionCache_ = {};
3188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Evaluates the given expression as the body of a function that takes
3228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * vars and data as arguments. Since the resulting function depends
3238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * only on expr, we cache the result so we save some Function
3248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * invocations, and some object creations in IE6.
3258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} expr A javascript expression.
3278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Function} A function that returns the value of expr in the
3298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * context of vars and data.
3308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction jsEvalToFunction(expr) {
3328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  if (!JsEvalContext.evalToFunctionCache_[expr]) {
3338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    try {
3348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      // NOTE(mesch): The Function constructor is faster than eval().
3358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      JsEvalContext.evalToFunctionCache_[expr] =
3368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        new Function(STRING_a, STRING_b, STRING_with + expr);
3378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    } catch (e) {
3388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      log('jsEvalToFunction (' + expr + ') EXCEPTION ' + e);
3398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
3408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
3418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return JsEvalContext.evalToFunctionCache_[expr];
3428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
3438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Evaluates the given expression to itself. This is meant to pass
3478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * through string attribute values.
3488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} expr
3508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {string}
3528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction jsEvalToSelf(expr) {
3548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return expr;
3558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
3568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Parses the value of the jsvalues attribute in jstemplates: splits
3608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * it up into a map of labels and expressions, and creates functions
3618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * from the expressions that are suitable for execution by
3628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * JsEvalContext.jsexec(). All that is returned as a flattened array
3638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * of pairs of a String and a Function.
3648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} expr
3668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Array}
3688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction jsEvalToValues(expr) {
3708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // TODO(mesch): It is insufficient to split the values by simply
3718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // finding semi-colons, as the semi-colon may be part of a string
3728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  // constant or escaped.
3738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var ret = [];
3748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var values = expr.split(REGEXP_semicolon);
3758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  for (var i = 0, I = jsLength(values); i < I; ++i) {
3768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var colon = values[i].indexOf(CHAR_colon);
3778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (colon < 0) {
3788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      continue;
3798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
3808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var label = stringTrim(values[i].substr(0, colon));
3818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var value = jsEvalToFunction(values[i].substr(colon + 1));
3828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    ret.push(label, value);
3838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
3848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return ret;
3858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
3868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
3888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/**
3898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Parses the value of the jseval attribute of jstemplates: splits it
3908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * up into a list of expressions, and creates functions from the
3918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * expressions that are suitable for execution by
3928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * JsEvalContext.jsexec(). All that is returned as an Array of
3938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * Function.
3948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @param {string} expr
3968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *
3978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * @return {Array.<Function>}
3988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
3998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction jsEvalToExpressions(expr) {
4008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var ret = [];
4018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var values = expr.split(REGEXP_semicolon);
4028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  for (var i = 0, I = jsLength(values); i < I; ++i) {
4038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (values[i]) {
4048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      var value = jsEvalToFunction(values[i]);
4058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      ret.push(value);
4068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
4078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
4088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  return ret;
4098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen}
410