code: function (attrs) {
    var result = null;
  
    if (HTML.isArray(attrs)) {
      // first element is nondynamic attrs, rest are template tags
      var nondynamicAttrs = parseAttrs(attrs[0]);
      if (nondynamicAttrs) {
        result = (result || []);
        result.push(nondynamicAttrs);
      }
      for (var i = 1; i < attrs.length; i++) {
        var token = attrs[i];
        if (token.t !== 'TemplateTag')
          throw new Error("Expected TemplateTag token");
        result = (result || []);
        result.push(token.v);
      }
      return result;
    }
  
    for (var k in attrs) {
      if (! result)
        result = {};
  
      var inValue = attrs[k];
      var outParts = [];
      for (var i = 0; i < inValue.length; i++) {
        var token = inValue[i];
        if (token.t === 'CharRef') {
          outParts.push(convertCharRef(token));
        } else if (token.t === 'TemplateTag') {
          outParts.push(token.v);
        } else if (token.t === 'Chars') {
          pushOrAppendString(outParts, token.v);
        }
      }
  
      var outValue = (inValue.length === 0 ? '' :
                      (outParts.length === 1 ? outParts[0] : outParts));
      var properKey = HTMLTools.properCaseAttributeName(k);
      result[properKey] = outValue;
    }
  
    return result;
  }
docs: Input is always a dictionary (even if zero attributes) and each value in the dictionary is an array of `Chars`, `CharRef`, and maybe `TemplateTag` tokens.  Output is null if there are zero attributes, and otherwise a dictionary, or an array of dictionaries and template tags. Each value in the dictionary is HTMLjs (e.g. a string or an array of `Chars`, `CharRef`, and `TemplateTag` nodes).  An attribute value with no input tokens is represented as "", not an empty array, in order to prop open empty attributes with no template tags.