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)
        }
    }
}