docs: function render(stack, template, context, partials, parentView) {
    var i, j, value, valueLength;
    if (typeof template === 'undefined') {
      return;
    } else if (typeof template === 'string') {
      stack.createObject(template, {escapeHTML: true});
    } else if (Context.isArray(template)) {
      for (i = 0; i < template.length; i++) {
        render(stack, template[i], context, partials, parentView);
      }
    } else if (template.type === 'Template') {
      render(stack, template.templateObj, context, partials, parentView);
    } else if (template.type === 'WidgetConstructor') {
      // Widgets are how we attach Views to subtrees
      // If we have a parentView, we're rendering Vdom, if not this is rendering to Dom or string, so ignore
      if (parentView) {
        // If we have a parent view, attach this as a widget so the child view will render and attach
        stack.createObject(new template.constructor(template.template, template.childView, context, parentView));
      } else {
        // If we aren't in the context of a view, just render out the widget without attaching the child view
        template.template._iterate(null, context, null, null, stack);
      }
      return;
    }
  
    switch (template.t) {
      case types.TEXT:
        if (template.r) {
          stack.createObject(template.r, {escapeHTML: true});
        }
        break;
      // <!-- comment -->
      case types.COMMENT:
        var toString = new ToString();
        render(toString, template.c, context, partials, parentView);
        stack.createComment(toString.getOutput());
        return;
      // {{value}} or {{{value}}} or {{& value}}
      case types.INTERPOLATOR:
      case types.TRIPLE:
        value = context.lookup(template.r);
        if (value != null) {
          // If value is already a widget or vnode, add it wholesale
          if (template.t === types.TRIPLE && (isWidget(value) || isVNode(value))) {
            stack.createObject(value);
          } else if (template.t === types.TRIPLE && value.type === 'Template') {
            var ctx = value.context || context;
            render(stack, value.templateObj, ctx, partials, parentView);
          } else if (Context.isArray(value) && template.t === types.TRIPLE && value.vdomArray === true) {
            for (i = 0; i < value.length; i++) {
              stack.createObject(value[i]);
            }
          } else {
            stack.createObject(stack.stringify(value), {
              // TRIPLE is unescaped content, so it may need parsing
              parse: template.t === types.TRIPLE,
              // INTERPOLATOR is escaped content, so it may need escaping
              escape: template.t === types.INTERPOLATOR
            });
          }
        }
        break;
  
      // {{> partial}}
      case types.PARTIAL:
        var partialName = template.r;
        if (partials[partialName]) {
          var partialTemplate = partials[partialName];
          if (partialTemplate._iterate) {
            // @Todo determine if using partial context is necessary
            partialTemplate._iterate(null, context, parentView, partials, stack);
          } else {
            render(stack, partialTemplate, context, partials[partialName].partials || partials, parentView);
          }
        } else {
          // @TODO, perhaps return this string as the partial result so it renders to the page?
          errors.warningNoPartialRegisteredWithTheName(partialName);
        }
        break;
  
      // {{# section}} or {{^ unless}}
      case types.SECTION:
        var handleLambda = null;
        var lambdaHandled = false;
        if (template.n !== types.SECTION_UNLESS) {
          // Section tags can be lambdas with contents
          handleLambda = function(fn, ctx) {
            // Create a template from the section's children
            lambdaHandled = true;
            if (fn.type === 'Widget' && typeof fn.updateContent === 'function') {
              var contentTemplate = new (require('./template'))(template.f, partials, parentView, context);
              fn.updateContent(contentTemplate);
              stack.createObject(fn);
            } else {
              var templateString = toSource(template.f);
              var lambdaValue = fn.call(ctx, templateString, function(template) {
                var templateData = compiler(template);
                return templateData.template.toString(context);
              });
              if (lambdaValue) {
                if (typeof lambdaValue === 'string') {
                  var resultHasClass = parentView && parentView.childViews && lambdaValue.indexOf(' class=') > -1;
                  var resultHasMustache = lambdaValue.indexOf('{{') > -1;
                  if (resultHasMustache || resultHasClass) {
                    var lambdaTemplateData = compiler(lambdaValue);
                    if (resultHasClass) {
                      lambdaTemplateData.template = lambdaTemplateData.template.attachView({
                        el: false,
                        childViews: parentView.childViews
                      });
                    }
                    lambdaValue = lambdaTemplateData.template.templateObj;
                  } else {
                    stack.createObject(lambdaValue, {
                      parse: true,
                      escape: false
                    });
                    return;
                  }
                }
                render(stack, lambdaValue, context, partials, parentView);
              }
            }
          };
        }
        value = context.lookup(template.r, handleLambda);
        // if the render function passed into the lambda handler was invoked, don't process further
        if (lambdaHandled) {
          break;
        }
        value = Context.parseValue(value);
        if (template.n === types.SECTION_UNLESS) {
          if (!value.isTruthy) {
            render(stack, template.f, context, partials, parentView);
          }
        } else if (value.isTruthy) {
          // Sections become loops if an array or collection is passed
          if (value.isArray) {
            // Iterate over the collection
            for (j = 0, valueLength = value.value.length; j < valueLength; ++j) {
              render(stack, template.f, context.push(value.value[j]), partials, parentView);
            }
          } else if (typeof value.value === 'object' || typeof value.value === 'string') {
            // If the found value is an object or string, push it onto the context
            render(stack, template.f, context.push(value.value), partials, parentView);
          } else {
            // Not an object or string usually means boolean, so don't bother pushing that
            render(stack, template.f, context, partials, parentView);
          }
        }
        break;
  
      // DOM node
      case types.ELEMENT:
        var properties = {};
        var attributeHandler = function(values, attr) {
          properties[attr] = renderAttributeString(values, context);
        };
  
        // Parse static attribute values
        _.each(template.a, attributeHandler);
        // Handle dynamic values if need be
        if (template.m) {
          var attrs = parseStringAttrs(template.m, context);
          if (attrs != null) {
            _.each(attrs, attributeHandler);
          }
        }
  
        // openElement gives back a unique ID so it can validate pairs when closing
        var elem = stack.openElement(template.e.toLowerCase(), properties);
  
        // Recuse into the elements' children
        if (template.f) {
          render(stack, template.f, context, partials, parentView);
        }
  
        stack.closeElement(elem);
    }
  }