// Copyright 2020 The Libc Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package libc // import "modernc.org/libc" import ( "bytes" "fmt" "strconv" "strings" "unsafe" ) const ( modNone = iota modHH modH modL modLL modQ modCapitalL modJ modZ modCapitalZ modT mod32 mod64 ) // Format of the format string // // The format string is a character string, beginning and ending in its initial // shift state, if any. The format string is composed of zero or more // directives: ordinary characters (not %), which are copied unchanged to // the output stream; and conversion specifications, each of which results in // fetching zero or more subsequent arguments. func printf(format, args uintptr) []byte { format0 := format args0 := args buf := bytes.NewBuffer(nil) for { switch c := *(*byte)(unsafe.Pointer(format)); c { case '%': format = printfConversion(buf, format, &args) case 0: if dmesgs { dmesg("%v: %q, %#x -> %q", origin(1), GoString(format0), args0, buf.Bytes()) } return buf.Bytes() default: format++ buf.WriteByte(c) } } } // Each conversion specification is introduced by the character %, and ends // with a conversion specifier. In between there may be (in this order) zero // or more flags, an optional minimum field width, an optional precision and // an optional length modifier. func printfConversion(buf *bytes.Buffer, format uintptr, args *uintptr) uintptr { format++ // '%' spec := "%" // Flags characters // // The character % is followed by zero or more of the following flags: flags: for { switch c := *(*byte)(unsafe.Pointer(format)); c { case '#': // The value should be converted to an "alternate form". For o conversions, // the first character of the output string is made zero (by prefixing a 0 if // it was not zero already). For x and X conversions, a nonzero result has // the string "0x" (or "0X" for X conversions) prepended to it. For a, A, e, // E, f, F, g, and G conversions, the result will always contain a decimal // point, even if no digits follow it (normally, a decimal point appears in the // results of those conversions only if a digit follows). For g and G // conversions, trailing zeros are not removed from the result as they would // otherwise be. For other conversions, the result is undefined. format++ spec += "#" case '0': // The value should be zero padded. For d, i, o, u, x, X, a, A, e, E, f, F, // g, and G conversions, the converted value is padded on the left with zeros // rather than blanks. If the 0 and - flags both appear, the 0 flag is // ignored. If a precision is given with a numeric conversion (d, i, o, u, x, // and X), the 0 flag is ignored. For other conversions, the behav‐ ior is // undefined. format++ spec += "0" case '-': // The converted value is to be left adjusted on the field boundary. (The // default is right justification.) The converted value is padded on the right // with blanks, rather than on the left with blanks or zeros. A - overrides a // 0 if both are given. format++ spec += "-" case ' ': // A blank should be left before a positive number (or empty string) produced // by a signed conversion. format++ spec += " " case '+': // A sign (+ or -) should always be placed before a number produced by a signed // conversion. By default, a sign is used only for negative numbers. A + // overrides a space if both are used. format++ spec += "+" default: break flags } } format, width, hasWidth := parseFieldWidth(format) if hasWidth { spec += strconv.Itoa(width) } format, prec, hasPrecision := parsePrecision(format, args) format, mod := parseLengthModifier(format) var str string more: // Conversion specifiers // // A character that specifies the type of conversion to be applied. The // conversion specifiers and their meanings are: switch c := *(*byte)(unsafe.Pointer(format)); c { case 'd', 'i': // The int argument is converted to signed decimal notation. The precision, // if any, gives the minimum number of digits that must appear; if the // converted value requires fewer digits, it is padded on the left with zeros. // The default precision is 1. When 0 is printed with an explicit precision 0, // the output is empty. format++ var arg int64 switch mod { case modNone, modL, modLL, mod64: arg = VaInt64(args) case modH: arg = int64(int16(VaInt32(args))) case modHH: arg = int64(int8(VaInt32(args))) case mod32: arg = int64(VaInt32(args)) default: panic(todo("", mod)) } if arg == 0 && hasPrecision && prec == 0 { break } if hasPrecision { panic(todo("", prec)) } f := spec + "d" str = fmt.Sprintf(f, arg) case 'u': // The unsigned int argument is converted to unsigned decimal notation. The // precision, if any, gives the minimum number of digits that must appear; if // the converted value requires fewer digits, it is padded on the left with // zeros. The default precision is 1. When 0 is printed with an explicit // precision 0, the output is empty. format++ var arg uint64 switch mod { case modNone: arg = uint64(VaUint32(args)) case modL, modLL, mod64: arg = VaUint64(args) case modH: arg = uint64(uint16(VaInt32(args))) case modHH: arg = uint64(uint8(VaInt32(args))) case mod32: arg = uint64(VaInt32(args)) default: panic(todo("", mod)) } if arg == 0 && hasPrecision && prec == 0 { break } if hasPrecision { panic(todo("", prec)) } f := spec + "d" str = fmt.Sprintf(f, arg) case 'o': // The unsigned int argument is converted to unsigned octal notation. The // precision, if any, gives the minimum number of digits that must appear; if // the converted value requires fewer digits, it is padded on the left with // zeros. The default precision is 1. When 0 is printed with an explicit // precision 0, the output is empty. format++ var arg uint64 switch mod { case modNone: arg = uint64(VaUint32(args)) case modL, modLL, mod64: arg = VaUint64(args) case modH: arg = uint64(uint16(VaInt32(args))) case modHH: arg = uint64(uint8(VaInt32(args))) case mod32: arg = uint64(VaInt32(args)) default: panic(todo("", mod)) } if arg == 0 && hasPrecision && prec == 0 { break } if hasPrecision { panic(todo("", prec)) } f := spec + "o" str = fmt.Sprintf(f, arg) case 'I': if !isWindows { panic(todo("%#U", c)) } format++ switch c = *(*byte)(unsafe.Pointer(format)); c { case 'x', 'X': // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-wsprintfa // // Ix, IX // // 64-bit unsigned hexadecimal integer in lowercase or uppercase on 64-bit // platforms, 32-bit unsigned hexadecimal integer in lowercase or uppercase on // 32-bit platforms. if unsafe.Sizeof(int(0)) == 4 { mod = mod32 } case '3': // https://en.wikipedia.org/wiki/Printf_format_string#Length_field // // I32 For integer types, causes printf to expect a 32-bit (double word) integer argument. format++ switch c = *(*byte)(unsafe.Pointer(format)); c { case '2': format++ mod = mod32 goto more default: panic(todo("%#U", c)) } case '6': // https://en.wikipedia.org/wiki/Printf_format_string#Length_field // // I64 For integer types, causes printf to expect a 64-bit (quad word) integer argument. format++ switch c = *(*byte)(unsafe.Pointer(format)); c { case '4': format++ mod = mod64 goto more default: panic(todo("%#U", c)) } default: panic(todo("%#U", c)) } fallthrough case 'X': fallthrough case 'x': // The unsigned int argument is converted to unsigned hexadecimal notation. // The letters abcdef are used for x conversions; the letters ABCDEF are used // for X conversions. The precision, if any, gives the minimum number of // digits that must appear; if the converted value requires fewer digits, it is // padded on the left with zeros. The default precision is 1. When 0 is // printed with an explicit precision 0, the output is empty. format++ var arg uint64 switch mod { case modNone: arg = uint64(VaUint32(args)) case modL, modLL, mod64: arg = VaUint64(args) case modH: arg = uint64(uint16(VaInt32(args))) case modHH: arg = uint64(uint8(VaInt32(args))) case mod32: arg = uint64(VaInt32(args)) default: panic(todo("", mod)) } if arg == 0 && hasPrecision && prec == 0 { break } if strings.Contains(spec, "#") && arg == 0 { spec = strings.ReplaceAll(spec, "#", "") } var f string switch { case hasPrecision: f = fmt.Sprintf("%s.%d%c", spec, prec, c) default: f = spec + string(c) } str = fmt.Sprintf(f, arg) case 'e', 'E': // The double argument is rounded and converted in the style [-]d.ddde±dd where // there is one digit before the decimal-point character and the number of // digits after it is equal to the precision; if the precision is missing, it // is taken as 6; if the precision is zero, no decimal-point character appears. // An E conversion uses the letter E (rather than e) to intro‐ duce the // exponent. The exponent always contains at least two digits; if the value is // zero, the exponent is 00. format++ arg := VaFloat64(args) if !hasPrecision { prec = 6 } f := fmt.Sprintf("%s.%d%c", spec, prec, c) str = fmt.Sprintf(f, arg) case 'f', 'F': // The double argument is rounded and converted to decimal notation in the // style [-]ddd.ddd, where the number of digits after the decimal-point // character is equal to the precision specification. If the precision // is missing, it is taken as 6; if the precision is explicitly zero, no // decimal-point character appears. If a decimal point appears, at least one // digit appears before it. format++ arg := VaFloat64(args) if !hasPrecision { prec = 6 } f := fmt.Sprintf("%s.%d%c", spec, prec, c) str = fmt.Sprintf(f, arg) case 'G': fallthrough case 'g': // The double argument is converted in style f or e (or F or E for G // conversions). The precision specifies the number of significant digits. If // the precision is missing, 6 digits are given; if the precision is zero, it // is treated as 1. Style e is used if the exponent from its conversion is // less than -4 or greater than or equal to the precision. Trailing zeros are // removed from the fractional part of the result; a decimal point appears only // if it is followed by at least one digit. format++ arg := VaFloat64(args) if !hasPrecision { prec = 6 } if prec == 0 { prec = 1 } f := fmt.Sprintf("%s.%d%c", spec, prec, c) str = fmt.Sprintf(f, arg) case 's': // If no l modifier is present: the const char * argument is expected to be a // pointer to an array of character type (pointer to a string). Characters // from the array are written up to (but not including) a terminating null byte // ('\0'); if a precision is specified, no more than the number specified are // written. If a precision is given, no null byte need be present; if // the precision is not specified, or is greater than the size of the array, // the array must contain a terminating null byte. // // If an l modifier is present: the const wchar_t * argument is expected // to be a pointer to an array of wide characters. Wide characters from the // array are converted to multibyte characters (each by a call to the // wcrtomb(3) function, with a conversion state starting in the initial state // before the first wide character), up to and including a terminating null // wide character. The resulting multibyte characters are written up to // (but not including) the terminating null byte. If a precision is specified, // no more bytes than the number specified are written, but no partial // multibyte characters are written. Note that the precision determines the // number of bytes written, not the number of wide characters or screen // positions. The array must contain a terminating null wide character, // unless a precision is given and it is so small that the number of bytes // written exceeds it before the end of the array is reached. format++ arg := VaUintptr(args) switch mod { case modNone: var f string switch { case hasPrecision: f = fmt.Sprintf("%s.%ds", spec, prec) str = fmt.Sprintf(f, GoString(arg)) default: f = spec + "s" str = fmt.Sprintf(f, GoString(arg)) } default: panic(todo("")) } case 'p': // The void * pointer argument is printed in hexadecimal (as if by %#x or // %#lx). format++ arg := VaUintptr(args) buf.WriteString("0x") buf.WriteString(strconv.FormatInt(int64(arg), 16)) case 'c': // If no l modifier is present, the int argument is converted to an unsigned // char, and the resulting character is written. If an l modifier is present, // the wint_t (wide character) ar‐ gument is converted to a multibyte sequence // by a call to the wcrtomb(3) function, with a conversion state starting in // the initial state, and the resulting multibyte string is writ‐ ten. format++ switch mod { case modNone: arg := VaInt32(args) buf.WriteByte(byte(arg)) default: panic(todo("")) } case '%': // A '%' is written. No argument is converted. The complete conversion // specification is '%%'. format++ buf.WriteByte('%') default: panic(todo("%#U", c)) } buf.WriteString(str) return format } // Field width // // An optional decimal digit string (with nonzero first digit) specifying a // minimum field width. If the converted value has fewer characters than the // field width, it will be padded with spa‐ ces on the left (or right, if the // left-adjustment flag has been given). Instead of a decimal digit string one // may write "*" or "*m$" (for some decimal integer m) to specify that the // field width is given in the next argument, or in the m-th argument, // respectively, which must be of type int. A negative field width is taken as // a '-' flag followed by a positive field width. In no case does a // nonexistent or small field width cause truncation of a field; if the result // of a conversion is wider than the field width, the field is expanded to // contain the conversion result. func parseFieldWidth(format uintptr) (_ uintptr, n int, ok bool) { first := true for { var digit int switch c := *(*byte)(unsafe.Pointer(format)); { case first && c == '0': return format, n, ok case first && c == '*': panic(todo("")) case c >= '0' && c <= '9': format++ ok = true first = false digit = int(c) - '0' default: return format, n, ok } n0 := n n = 10*n + digit if n < n0 { panic(todo("")) } } } // Precision // // An optional precision, in the form of a period ('.') followed by an // optional decimal digit string. Instead of a decimal digit string one may // write "*" or "*m$" (for some decimal integer m) to specify that the // precision is given in the next argument, or in the m-th argument, // respectively, which must be of type int. If the precision is given as just // '.', the precision is taken to be zero. A negative precision is taken // as if the precision were omitted. This gives the minimum number of digits // to appear for d, i, o, u, x, and X conversions, the number of digits to // appear after the radix character for a, A, e, E, f, and F conversions, the // maximum number of significant digits for g and G conversions, or the maximum // number of characters to be printed from a string for s and S conversions. func parsePrecision(format uintptr, args *uintptr) (_ uintptr, n int, ok bool) { for { switch c := *(*byte)(unsafe.Pointer(format)); c { case '.': format++ first := true for { switch c := *(*byte)(unsafe.Pointer(format)); { case first && c == '*': format++ n = int(VaInt32(args)) return format, n, true case c >= '0' && c <= '9': format++ first = false n0 := n n = 10*n + (int(c) - '0') if n < n0 { panic(todo("")) } default: return format, n, true } } default: return format, 0, false } } } // Length modifier // // Here, "integer conversion" stands for d, i, o, u, x, or X conversion. // // hh A following integer conversion corresponds to a signed char or // unsigned char argument, or a following n conversion corresponds to a pointer // to a signed char argument. // // h A following integer conversion corresponds to a short int or unsigned // short int argument, or a following n conversion corresponds to a pointer to // a short int argument. // // l (ell) A following integer conversion corresponds to a long int or // unsigned long int argument, or a following n conversion corresponds to a // pointer to a long int argument, or a fol‐ lowing c conversion corresponds to // a wint_t argument, or a following s conversion corresponds to a pointer to // wchar_t argument. // // ll (ell-ell). A following integer conversion corresponds to a long long // int or unsigned long long int argument, or a following n conversion // corresponds to a pointer to a long long int argument. // // q A synonym for ll. This is a nonstandard extension, derived from BSD; // avoid its use in new code. // // L A following a, A, e, E, f, F, g, or G conversion corresponds to a // long double argument. (C99 allows %LF, but SUSv2 does not.) // // j A following integer conversion corresponds to an intmax_t or // uintmax_t argument, or a following n conversion corresponds to a pointer to // an intmax_t argument. // // z A following integer conversion corresponds to a size_t or ssize_t // argument, or a following n conversion corresponds to a pointer to a size_t // argument. // // Z A nonstandard synonym for z that predates the appearance of z. Do // not use in new code. // // t A following integer conversion corresponds to a ptrdiff_t argument, // or a following n conversion corresponds to a pointer to a ptrdiff_t // argument. func parseLengthModifier(format uintptr) (_ uintptr, n int) { switch c := *(*byte)(unsafe.Pointer(format)); c { case 'h': format++ n = modH switch c := *(*byte)(unsafe.Pointer(format)); c { case 'h': format++ n = modHH } return format, n case 'l': format++ n = modL switch c := *(*byte)(unsafe.Pointer(format)); c { case 'l': format++ n = modLL } return format, n case 'q': panic(todo("")) case 'L': panic(todo("")) case 'j': panic(todo("")) case 'z': panic(todo("")) case 'Z': panic(todo("")) case 't': panic(todo("")) default: return format, 0 } }