go-printf - 解答
実装コード
package printf
import "os"
// Printf はフォーマットに従って標準出力に出力する
func Printf(format string, args ...interface{}) int {
str := Sprintf(format, args...)
os.Stdout.WriteString(str)
return len(str)
}
// Sprintf はフォーマットに従って文字列を生成する
func Sprintf(format string, args ...interface{}) string {
var result []byte
argIndex := 0
runes := []rune(format)
for i := 0; i < len(runes); i++ {
if runes[i] == '%' {
if i+1 >= len(runes) {
result = append(result, '%')
continue
}
i++
spec := parseSpec(runes, &i)
if spec.specifier == '%' {
result = append(result, '%')
continue
}
if argIndex >= len(args) {
result = append(result, []byte("%!(MISSING)")...)
continue
}
formatted := formatArg(args[argIndex], spec)
result = append(result, []byte(formatted)...)
argIndex++
} else {
result = append(result, []byte(string(runes[i]))...)
}
}
return string(result)
}
type formatSpec struct {
flags string
width int
precision int
hasPrecision bool
specifier rune
}
func parseSpec(runes []rune, pos *int) formatSpec {
spec := formatSpec{width: -1, precision: -1}
// フラグの解析
for *pos < len(runes) && isFlag(runes[*pos]) {
spec.flags += string(runes[*pos])
(*pos)++
}
// 幅の解析
if *pos < len(runes) && isDigit(runes[*pos]) {
spec.width = parseNumber(runes, pos)
}
// 精度の解析
if *pos < len(runes) && runes[*pos] == '.' {
(*pos)++
spec.hasPrecision = true
if *pos < len(runes) && isDigit(runes[*pos]) {
spec.precision = parseNumber(runes, pos)
} else {
spec.precision = 0
}
}
// 指定子の解析
if *pos < len(runes) {
spec.specifier = runes[*pos]
}
return spec
}
func isFlag(r rune) bool {
return r == '-' || r == '+' || r == ' ' || r == '0' || r == '#'
}
func isDigit(r rune) bool {
return r >= '0' && r <= '9'
}
func parseNumber(runes []rune, pos *int) int {
num := 0
for *pos < len(runes) && isDigit(runes[*pos]) {
num = num*10 + int(runes[*pos]-'0')
(*pos)++
}
(*pos)--
return num
}
func formatArg(arg interface{}, spec formatSpec) string {
switch spec.specifier {
case 'c':
return formatChar(arg)
case 's':
return formatString(arg, spec)
case 'd', 'i':
return formatInt(arg, 10, spec, false)
case 'u':
return formatUint(arg, 10, spec)
case 'x':
return formatInt(arg, 16, spec, false)
case 'X':
return formatInt(arg, 16, spec, true)
case 'o':
return formatInt(arg, 8, spec, false)
case 'b':
return formatInt(arg, 2, spec, false)
case 'p':
return formatPointer(arg)
default:
return "%!" + string(spec.specifier) + "(BADVERB)"
}
}
func formatChar(arg interface{}) string {
switch v := arg.(type) {
case rune:
return string(v)
case int:
return string(rune(v))
case int32:
return string(v)
default:
return "%!c(BADTYPE)"
}
}
func formatString(arg interface{}, spec formatSpec) string {
var s string
switch v := arg.(type) {
case string:
s = v
case []byte:
s = string(v)
default:
return "%!s(BADTYPE)"
}
// 精度による切り詰め
if spec.hasPrecision && spec.precision >= 0 && spec.precision < len([]rune(s)) {
s = string([]rune(s)[:spec.precision])
}
return applyWidth(s, spec)
}
func formatInt(arg interface{}, base int, spec formatSpec, upper bool) string {
var n int64
switch v := arg.(type) {
case int:
n = int64(v)
case int8:
n = int64(v)
case int16:
n = int64(v)
case int32:
n = int64(v)
case int64:
n = v
default:
return "%!d(BADTYPE)"
}
negative := n < 0
if negative {
n = -n
}
s := intToBase(uint64(n), base, upper)
// 精度の適用(最小桁数)
if spec.hasPrecision && spec.precision > len(s) {
padding := make([]byte, spec.precision-len(s))
for i := range padding {
padding[i] = '0'
}
s = string(padding) + s
}
// 符号/接頭辞の追加
prefix := ""
if negative {
prefix = "-"
} else if hasFlag(spec.flags, '+') {
prefix = "+"
} else if hasFlag(spec.flags, ' ') {
prefix = " "
}
// # フラグ(代替形式)
if hasFlag(spec.flags, '#') && n != 0 {
switch base {
case 16:
if upper {
prefix += "0X"
} else {
prefix += "0x"
}
case 8:
prefix += "0"
case 2:
prefix += "0b"
}
}
s = prefix + s
return applyWidth(s, spec)
}
func formatUint(arg interface{}, base int, spec formatSpec) string {
var n uint64
switch v := arg.(type) {
case uint:
n = uint64(v)
case uint8:
n = uint64(v)
case uint16:
n = uint64(v)
case uint32:
n = uint64(v)
case uint64:
n = v
case int:
n = uint64(v)
default:
return "%!u(BADTYPE)"
}
s := intToBase(n, base, false)
return applyWidth(s, spec)
}
func formatPointer(arg interface{}) string {
// 簡易実装: アドレスの代わりに型情報を表示
return "0x" + intToBase(uint64(uintptr(0)), 16, false)
}
func intToBase(n uint64, base int, upper bool) string {
if n == 0 {
return "0"
}
digits := "0123456789abcdef"
if upper {
digits = "0123456789ABCDEF"
}
var result []byte
for n > 0 {
result = append([]byte{digits[n%uint64(base)]}, result...)
n /= uint64(base)
}
return string(result)
}
func applyWidth(s string, spec formatSpec) string {
if spec.width <= 0 || len(s) >= spec.width {
return s
}
padding := spec.width - len(s)
padChar := byte(' ')
if hasFlag(spec.flags, '0') && !hasFlag(spec.flags, '-') {
padChar = '0'
}
padStr := make([]byte, padding)
for i := range padStr {
padStr[i] = padChar
}
if hasFlag(spec.flags, '-') {
return s + string(padStr)
}
return string(padStr) + s
}
func hasFlag(flags string, flag rune) bool {
for _, f := range flags {
if f == flag {
return true
}
}
return false
}
テストコード
package printf
import "testing"
func TestSprintf(t *testing.T) {
tests := []struct {
format string
args []interface{}
expected string
}{
{"%d", []interface{}{42}, "42"},
{"%s", []interface{}{"hello"}, "hello"},
{"%c", []interface{}{'A'}, "A"},
{"%%", []interface{}{}, "%"},
{"%x", []interface{}{255}, "ff"},
{"%X", []interface{}{255}, "FF"},
{"%o", []interface{}{8}, "10"},
{"%b", []interface{}{5}, "101"},
{"%10d", []interface{}{42}, " 42"},
{"%-10d", []interface{}{42}, "42 "},
{"%+d", []interface{}{42}, "+42"},
{"%+d", []interface{}{-42}, "-42"},
}
for _, tt := range tests {
result := Sprintf(tt.format, tt.args...)
if result != tt.expected {
t.Errorf("Sprintf(%q, %v) = %q, want %q",
tt.format, tt.args, result, tt.expected)
}
}
}