String.prototype.format

String.prototype.format = (function(regx, zero){
  return function(){
    var args = arguments, x = 0;
    return this.replace(regx, function(_, pos_s, flag, width, pos_w, prec, pos_p, type){
      if(type == '%') return '%';
      if(~width.indexOf('*')) width = pos_w > 0 ? args[pos_w - 1] : args[x++];
      prec = prec === undefined ? -1
        : ~prec.indexOf('*') ? (pos_p > 0 ? args[pos_p - 1] : args[x++]) : +prec;
      var r = '', a = pos_s > 0 ? args[pos_s - 1] : args[x++], sharp = ~flag.indexOf('#');
      var sign = a < 0 && (a *= -1) ? '-'
        : ~flag.indexOf('+') ? '+' : ~flag.indexOf(' ') ? ' ' : '';
      switch(type){
       case 'd': case 'i': r = !r && prec == 0 ? '' : sign + zero(''+ (a | 0), prec); break;
       case 'u':
        a = sign == '-' ? (sign = '', 0x100000000 - (a | 0)) : a | 0;
        r = sign + zero(''+ a, prec);
        break;
       case 'b': case 'B': r = sign + (sharp ? '0b' : '') + zero((+a).toString(2),  prec); break;
       case 'o': case 'O': r = sign + (sharp ? '0'  : '') + zero((+a).toString(8),  prec); break;
       case 'x': case 'X': r = sign + (sharp ? '0x' : '') + zero((+a).toString(16), prec); break;
       case 'f': case 'F':
        r = sign + (+a).toFixed(~prec ? prec : 6);
        if(!prec && sharp) r += '.';
        break;
       case 'e': case 'E':
        r = sign + (+a).toExponential(~prec ? prec : 6);
        if(!prec && sharp) r = r.replace(/e/, '.e');
        break;
       case 'g': case 'G':
        r = sign + (+a).toPrecision(~prec ? prec : 6);
        if(!sharp) r = r.replace(/\.?0+(?=e|$)/, '');
        break;
       case 'a': case 'A':
        r = sign + (+a).toString(16);
        if(~prec) r = r.replace(/([^.]+)\.?([^\(]*)(.*)/, function(_, i,d,e){
          return i + (prec ? '.'+ (d +'0000000000000').substr(0, prec) + e : sharp ? '.' : '') });
        break;
       case 'c': r = String.fromCharCode(a | 0); break;
       case 'p': if(typeof uneval == 'function') a = uneval(a); // fallthrough
       case 's': r = ~prec ? a.substr(0, prec) : a; break;
      }
      if(/[BXEGA]/.test(type)) r = r.toUpperCase();
      if((width -= r.length) > 0){
        var t = Array(width + 1).join(~flag.indexOf(0) && /[^cs]/.test(type) ? 0 : ' ');
        r = ~flag.indexOf('-') ? r + t : t + r;
      }
      return r;
    });
  };
})(
  /%(?:(\d+)\$)?([-+ #0]*)(\d*|\*(?:(\d+)\$)?)(?:\.(\d+|\*(?:(\d+)\$)?))?([diubBoOxXeEfFgGaAcsp%])/g,
  function(s, p){ return (p -= s.length) > 0 ? Array(p + 1).join(0) + s : s }
);

どう書くに投稿した sprintf がベース。Life is beautiful: Javascript、クロージャを使ったプライベート関数の隠蔽についてを参考にオブジェクトをキャッシュしてみたが,効果の程は不明。

  • 既知の問題:
    • "%#010b".format(10) が "0b00001010" でなく "00000b1010" になる。"%#10.8b".format(10) とする必要あり。