sprintf を Javascript で実装する

Category:
Last Updated: 2022/01/06 08:01:16

TIP

最新版は下記のページで公開しています。


こんな感じ。

/** 
 * `sprintf` with Javascript
 * 
 * Return a formatted string
 * @see https://www.php.net/manual/en/function.sprintf.php
 * 
 * @param string format
 * @param mixed ...args
 * @return string
 */
const sprintf = (format, ...args)=>{
	const r = (()=>{
		const uint_ = (n)=>{
			try {
				return BigInt.asUintN(64, BigInt(n))
			} catch(e_) {
				return n >= 0 ? n : ~n // not precise
			}
		}
		const 
			b = (a)=>parseInt(a,10).toString(2),
			c = (a)=>String.fromCharCode(parseInt(a, 10)), 
			d = (a)=>parseInt(a,10),
			e = (a,p)=>parseFloat(a).toExponential(p > 0 ? p : 6),
			E = (a,p)=>e.call(null,a,p).replace("e","E"),
			f = (a,p)=>parseFloat(a).toFixed(p >= 0 ? p: 6), 
			F = (a,p)=>f.call(null,a,p),
			g = (a,p,fn)=>{
				p = p >= 0 ? Math.max(1, ~~p) : 6;

				if (a < 1e-4 || Math.pow(10, p) <= a + 0.5) {
					return String(e(a, p-1))
				} else {
					return String(parseFloat(a).toPrecision( p ))
				}
			}, 
			G = (a,p)=>g(a,p).toUpperCase(), 
			h = (a,p)=>g(a,p),
			H = (a,p)=>g(a,p).toUpperCase(), 
			o = (a,p)=>uint_(a).toString(8),
			s = (a,p)=>p > 0 && a.length > p ? a.substr(0,p) : a,
			u = (a)=>uint_(a),
			x = (a)=>uint_(a).toString(16),
			X = (a)=>x(a).toUpperCase();
		return {b,c,d,e,E,f,F,g,G,h,H,o,s,u,x,X}
	})();

	//%[argnum$][flags][width][.precision]specifier.
	let s = ""+format;
	let index = 0;
	s = s.replace(/\%((\d+)\$)?([\+\-]{0,2})?(0+|\x20|\'.)?(\d+)?(\.(\d+))?([l]?[^\s])/g, 
		function(match, argnum_,argnum, signs, char, width, precision_,precision,specifier) {
			//console.log(arguments)
			if (specifier == "%") return '%';

			// specifier
			if (specifier.length > 1 && specifier[0] == "l") specifier = specifier[1]; // l format is actually ignored in php sprintf.
			if (! (specifier in r)) throw `undefined specifier: "${specifier}".`;


			// argments
			const idx = argnum ? argnum - 1 : index++;
			if (args.length <= idx) throw `Too few arguments`

			let a = args[idx]

			// specifier
			precision = typeof precision == "undefined" ? -1 : ~~precision
			let ret = ""+r[specifier](a, precision);
			
			// flags
			// https://www.php.net/manual/en/function.sprintf.php
			let minus_flg = false;
			let plus_flg = false;
			if (signs) {
				if (signs.indexOf('+') >= 0) {
					if ('deEfFgGhH'.indexOf(specifier) >= 0) {
						if (a >= 0) {
							plus_flg = true;
							if (width) width--;
						}
					}
				}
				if (signs.indexOf('-') >= 0) {
					minus_flg = true;
				}
			}
			if (width && width > ret.length) {
				if (! char && char !== "0") char = " ";
				char = char[0] === "'" ? char[1] : char[0];
				
				if (minus_flg == true && char == "0") {
					if (specifier != "s" && ret.indexOf(".") < 0) {
						char = " ";
					} 
				}

				if (minus_flg) {
					ret += char.repeat(width - ret.length);
				} else {
					ret = char.repeat(width - ret.length) + ret;
				}
			}
			if (plus_flg) {
				ret = "+" + ret;
			}

			return ret
		})
	return s
}

module.exports = sprintf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

# jestによるテストコード

const sprintf = require("/path/to/sprintf.js");

test("test sprintf()", ()=>{
	// Testcases for some common usages
	expect(sprintf("%d monkeys in the %s",5,"tree")).toBe("5 monkeys in the tree") 
	expect(sprintf("%2$d monkeys in the %1$s","tree",4)).toBe("4 monkeys in the tree") 

	// Testcases for each formats
	expect(sprintf("%b",43951789)).toBe("10100111101010011010101101") 
	expect(sprintf("%c",65)).toBe("A") 
	expect(sprintf("%d",43951789)).toBe("43951789") 
	expect(sprintf("%e",43951789)).toBe("4.395179e+7") 
	expect(sprintf("%u",43951789)).toBe("43951789") 
	expect(sprintf("%u",-43951789)).toBe("18446744073665599827") 
	expect(sprintf("%u",-1)).toBe("18446744073709551615") 
	expect(sprintf("%f",43951789)).toBe("43951789.000000") 
	expect(sprintf("%o",43951789)).toBe("247523255") 
	expect(sprintf("%s",43951789)).toBe("43951789") 
	expect(sprintf("%x",43951789)).toBe("29ea6ad") 
	expect(sprintf("%X",43951789)).toBe("29EA6AD") 

	// "l" format is always ignored in php sprintf
	expect(sprintf("%lb",43951789)).toBe("10100111101010011010101101") 
	expect(sprintf("%ld",43951789)).toBe("43951789") 
	expect(sprintf("%le",43951789)).toBe("4.395179e+7") 
	expect(sprintf("%lu",43951789)).toBe("43951789") 
	expect(sprintf("%lu",-43951789)).toBe("18446744073665599827") 
	expect(sprintf("%lu",-1)).toBe("18446744073709551615") 
	expect(sprintf("%lf",43951789)).toBe("43951789.000000") 
	expect(sprintf("%lo",43951789)).toBe("247523255") 
	expect(sprintf("%ls",43951789)).toBe("43951789") // l is ignored even with s
	expect(sprintf("%lx",43951789)).toBe("29ea6ad") 
	expect(sprintf("%lX",43951789)).toBe("29EA6AD") 

	// Testcases for "+"
	expect(sprintf("%+b",43951789)).toBe("10100111101010011010101101") 
	expect(sprintf("%+c",65)).toBe("A") // no "+" with chars or strings
	expect(sprintf("%+d",43951789)).toBe("+43951789") 
	expect(sprintf("%+e",43951789)).toBe("+4.395179e+7") 
	expect(sprintf("%+u",43951789)).toBe("43951789") 
	expect(sprintf("%+u",-43951789)).toBe("18446744073665599827") 
	expect(sprintf("%+u",-1)).toBe("18446744073709551615") 
	expect(sprintf("%+f",43951789)).toBe("+43951789.000000") 
	expect(sprintf("%+o",43951789)).toBe("247523255") 
	expect(sprintf("%+s",43951789)).toBe("43951789") 
	expect(sprintf("%+x",43951789)).toBe("29ea6ad") 
	expect(sprintf("%+X",43951789)).toBe("29EA6AD") 

	// Testcases for numbers
	expect(sprintf("%02d",1)).toBe("01") 
	expect(sprintf("%02.2f",1)).toBe("1.00") 
	expect(sprintf("%02.2f",1.1234)).toBe("1.12") 
	expect(sprintf("%06.2f",1.1234)).toBe("001.12") 

	// Testcases for numbers with general formats
	expect(sprintf("%g",123.4567)).toBe("123.457") 
	expect(sprintf("%.5g",123.456)).toBe("123.46") 
	expect(sprintf("%.2g",1234500000000)).toBe("1.2e+12") 
	expect(sprintf("%.2g","123.45e+10")).toBe("1.2e+12") 
	expect(sprintf("%.2g",1.2345e-8)).toBe("1.2e-8") 
	expect(sprintf("%.2g","123.45e-10")).toBe("1.2e-8") 
	expect(sprintf("%.2G",1234500000000)).toBe("1.2E+12") 
	expect(sprintf("%.2G","123.45e+10")).toBe("1.2E+12") 
	expect(sprintf("%.2G",1.2345e-8)).toBe("1.2E-8") 
	expect(sprintf("%.2G","123.45e-10")).toBe("1.2E-8") 

	// Testcases for numbers with "+"
	expect(sprintf("%+02d",1)).toBe("+1") 
	expect(sprintf("%+02.2f",1)).toBe("+1.00") 
	expect(sprintf("%+02.2f",1.1234)).toBe("+1.12") 
	expect(sprintf("%+06.2f",1.1234)).toBe("+01.12") 

	// Testcases for numbers with "+" and "-"
	expect(sprintf("%-+02d",1)).toBe("+1") 
	expect(sprintf("%-+03d",1)).toBe("+1 ") 
	expect(sprintf("%-+02.2f",1)).toBe("+1.00") 
	expect(sprintf("%-+02.2f",1.1234)).toBe("+1.12") 
	expect(sprintf("%-+06.2f",1.1234)).toBe("+1.120")

	// Testcases for string positions
	expect(sprintf("[%s]","monkey")).toBe("[monkey]") 
	expect(sprintf("[%10s]","monkey")).toBe("[    monkey]") 
	expect(sprintf("[%-10s]","monkey")).toBe("[monkey    ]") 
	expect(sprintf("[%010s]","monkey")).toBe("[0000monkey]") 
	expect(sprintf("[%'#10s]","monkey")).toBe("[####monkey]") 
	expect(sprintf("[%-010s]","monkey")).toBe("[monkey0000]") 
	expect(sprintf("[%-'#10s]","monkey")).toBe("[monkey####]") 	
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# PHPUnit によるphp版のsprintfのテストコード

phpsprintf が上記と同じ動作をすることを確認するために PHPUnit のテストコードも合わせて用意する。


<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class SprintfTest extends TestCase {
	public function testSprintf():void {

		// Testcases for some common usages
		$this->assertSame(sprintf('%d monkeys in the %s',5,'tree'), '5 monkeys in the tree'); 
		$this->assertSame(sprintf('%2$d monkeys in the %1$s','tree',4), '4 monkeys in the tree'); 

		// Testcases for each formats
		$this->assertSame(sprintf('%b',43951789), '10100111101010011010101101'); 
		$this->assertSame(sprintf('%c',65), 'A'); 
		$this->assertSame(sprintf('%d',43951789), '43951789'); 
		$this->assertSame(sprintf('%e',43951789), '4.395179e+7'); 
		$this->assertSame(sprintf('%u',43951789), '43951789'); 
		$this->assertSame(sprintf('%u',-43951789), '18446744073665599827'); 
		$this->assertSame(sprintf('%u',-1), '18446744073709551615'); 
		$this->assertSame(sprintf('%f',43951789), '43951789.000000'); 
		$this->assertSame(sprintf('%o',43951789), '247523255'); 
		$this->assertSame(sprintf('%s',43951789), '43951789'); 
		$this->assertSame(sprintf('%x',43951789), '29ea6ad'); 
		$this->assertSame(sprintf('%X',43951789), '29EA6AD'); 

		// "l" format is always ignored in php sprintf
		$this->assertSame(sprintf('%lb',43951789), '10100111101010011010101101'); 
		$this->assertSame(sprintf('%ld',43951789), '43951789'); 
		$this->assertSame(sprintf('%le',43951789), '4.395179e+7'); 
		$this->assertSame(sprintf('%lu',43951789), '43951789'); 
		$this->assertSame(sprintf('%lu',-43951789), '18446744073665599827'); 
		$this->assertSame(sprintf('%lu',-1), '18446744073709551615'); 
		$this->assertSame(sprintf('%lf',43951789), '43951789.000000'); 
		$this->assertSame(sprintf('%lo',43951789), '247523255'); 
		$this->assertSame(sprintf('%ls',43951789), '43951789'); // l is ignored even with s
		$this->assertSame(sprintf('%lx',43951789), '29ea6ad'); 
		$this->assertSame(sprintf('%lX',43951789), '29EA6AD'); 

		// Testcases for "+"
		$this->assertSame(sprintf('%+b',43951789), '10100111101010011010101101'); 
		$this->assertSame(sprintf('%+c',65), 'A'); // no "+" with chars or strings
		$this->assertSame(sprintf('%+d',43951789), '+43951789'); 
		$this->assertSame(sprintf('%+e',43951789), '+4.395179e+7'); 
		$this->assertSame(sprintf('%+u',43951789), '43951789'); 
		$this->assertSame(sprintf('%+u',-43951789), '18446744073665599827'); 
		$this->assertSame(sprintf('%+u',-1), '18446744073709551615'); 
		$this->assertSame(sprintf('%+f',43951789), '+43951789.000000'); 
		$this->assertSame(sprintf('%+o',43951789), '247523255'); 
		$this->assertSame(sprintf('%+s',43951789), '43951789'); 
		$this->assertSame(sprintf('%+x',43951789), '29ea6ad'); 
		$this->assertSame(sprintf('%+X',43951789), '29EA6AD'); 

		// Testcases for numbers
		$this->assertSame(sprintf('%02d',1), '01'); 
		$this->assertSame(sprintf('%02.2f',1), '1.00'); 
		$this->assertSame(sprintf('%02.2f',1.1234), '1.12'); 
		$this->assertSame(sprintf('%06.2f',1.1234), '001.12'); 

		// Testcases for numbers with general formats
		$this->assertSame(sprintf('%g',123.4567), '123.457'); 
		$this->assertSame(sprintf('%.5g',123.456), '123.46'); 
		$this->assertSame(sprintf('%.2g',1234500000000), '1.2e+12'); 
		$this->assertSame(sprintf('%.2g','123.45e+10'), '1.2e+12'); 
		$this->assertSame(sprintf('%.2g',1.2345e-8), '1.2e-8'); 
		$this->assertSame(sprintf('%.2g','123.45e-10'), '1.2e-8'); 
		$this->assertSame(sprintf('%.2G',1234500000000), '1.2E+12'); 
		$this->assertSame(sprintf('%.2G','123.45e+10'), '1.2E+12'); 
		$this->assertSame(sprintf('%.2G',1.2345e-8), '1.2E-8'); 
		$this->assertSame(sprintf('%.2G','123.45e-10'), '1.2E-8'); 

		// Testcases for numbers with "+"
		$this->assertSame(sprintf('%+02d',1), '+1'); 
		$this->assertSame(sprintf('%+02.2f',1), '+1.00'); 
		$this->assertSame(sprintf('%+02.2f',1.1234), '+1.12'); 
		$this->assertSame(sprintf('%+06.2f',1.1234), '+01.12'); 

		// Testcases for numbers with "+" and "-"
		$this->assertSame(sprintf('%-+02d',1), '+1'); 
		$this->assertSame(sprintf('%-+03d',1), '+1 '); 
		$this->assertSame(sprintf('%-+02.2f',1), '+1.00'); 
		$this->assertSame(sprintf('%-+02.2f',1.1234), '+1.12'); 
		$this->assertSame(sprintf('%-+06.2f',1.1234), '+1.120'); 

		// Testcases for string positions
		$this->assertSame(sprintf('[%s]','monkey'), '[monkey]'); 
		$this->assertSame(sprintf('[%10s]','monkey'), '[    monkey]'); 
		$this->assertSame(sprintf('[%-10s]','monkey'), '[monkey    ]'); 
		$this->assertSame(sprintf('[%010s]','monkey'), '[0000monkey]'); 
		$this->assertSame(sprintf('[%\'#10s]','monkey'), '[####monkey]'); 
		$this->assertSame(sprintf('[%-010s]','monkey'), '[monkey0000]'); 
		$this->assertSame(sprintf('[%-\'#10s]','monkey'), '[monkey####]'); 
	}
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

Category:
Last Updated: 2022/01/06 08:01:16
Copyright © Web Ninja All Rights Reserved.