1 //===-- Octal Converter for printf ------------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_OCT_CONVERTER_H
10 #define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_OCT_CONVERTER_H
11 
12 #include "src/stdio/printf_core/converter_utils.h"
13 #include "src/stdio/printf_core/core_structs.h"
14 #include "src/stdio/printf_core/writer.h"
15 
16 #include <inttypes.h>
17 #include <stddef.h>
18 
19 namespace __llvm_libc {
20 namespace printf_core {
21 
convert_oct(Writer * writer,const FormatSection & to_conv)22 int inline convert_oct(Writer *writer, const FormatSection &to_conv) {
23   // This is the number of digits it takes to represent a octal value of a
24   // certain number of bits. Each oct digit represents 3 bits, so the value is
25   // ceil(number of bits / 3).
26   constexpr size_t BUFF_LEN = ((sizeof(uintmax_t) * 8) + 2) / 3;
27   uintmax_t num = to_conv.conv_val_raw;
28   char buffer[BUFF_LEN];
29 
30   num = apply_length_modifier(num, to_conv.length_modifier);
31 
32   // Since the buffer is size to sized to be able fit the entire number, buf_cur
33   // can never reach 0. So, we do not need bounds checking on buf_cur.
34   size_t buff_cur = BUFF_LEN;
35   for (; num > 0 /* && buff_cur > 0 */; --buff_cur, num /= 8)
36     buffer[buff_cur - 1] = (num % 8) + '0';
37 
38   size_t num_digits = BUFF_LEN - buff_cur;
39 
40   // These are signed to prevent underflow due to negative values. Negative
41   // values are treated the same as 0.
42   int zeroes;
43   int spaces;
44 
45   // Negative precision indicates that it was not specified.
46   if (to_conv.precision < 0) {
47     if ((to_conv.flags &
48          (FormatFlags::LEADING_ZEROES | FormatFlags::LEFT_JUSTIFIED)) ==
49         FormatFlags::LEADING_ZEROES) {
50       // If this conv has flag 0 but not - and no specified precision, it's
51       // padded with 0's instead of spaces identically to if precision =
52       // min_width. For example: ("%04o", 15) -> "0017"
53       zeroes = to_conv.min_width - num_digits;
54       spaces = 0;
55     } else if (num_digits < 1) {
56       // If no precision is specified, precision defaults to 1. This means that
57       // if the integer passed to the conversion is 0, a 0 will be printed.
58       // Example: ("%3o", 0) -> "  0"
59       zeroes = 1;
60       spaces = to_conv.min_width - zeroes;
61     } else {
62       // If there are enough digits to pass over the precision, just write the
63       // number, padded by spaces.
64       zeroes = 0;
65       spaces = to_conv.min_width - num_digits;
66     }
67   } else {
68     // If precision was specified, possibly write zeroes, and possibly write
69     // spaces. Example: ("%5.4o", 010000) -> "10000"
70     // If the check for if zeroes is negative was not there, spaces would be
71     // incorrectly evaluated as 1.
72     zeroes = to_conv.precision - num_digits; // a negative value means 0
73     if (zeroes < 0)
74       zeroes = 0;
75     spaces = to_conv.min_width - zeroes - num_digits;
76   }
77 
78   // The alternate form prefix is "0", so it's handled by increasing the number
79   // of zeroes if necessary.
80   if (((to_conv.flags & FormatFlags::ALTERNATE_FORM) ==
81        FormatFlags::ALTERNATE_FORM) &&
82       zeroes < 1) {
83     zeroes = 1;
84     --spaces;
85   }
86 
87   if ((to_conv.flags & FormatFlags::LEFT_JUSTIFIED) ==
88       FormatFlags::LEFT_JUSTIFIED) {
89     // If left justified the pattern is zeroes digits spaces
90     if (zeroes > 0)
91       RET_IF_RESULT_NEGATIVE(writer->write_chars('0', zeroes));
92     if (num_digits > 0)
93       RET_IF_RESULT_NEGATIVE(writer->write(buffer + buff_cur, num_digits));
94     if (spaces > 0)
95       RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', spaces));
96   } else {
97     // Else the pattern is spaces zeroes digits
98     if (spaces > 0)
99       RET_IF_RESULT_NEGATIVE(writer->write_chars(' ', spaces));
100     if (zeroes > 0)
101       RET_IF_RESULT_NEGATIVE(writer->write_chars('0', zeroes));
102     if (num_digits > 0)
103       RET_IF_RESULT_NEGATIVE(writer->write(buffer + buff_cur, num_digits));
104   }
105   return WRITE_OK;
106 }
107 
108 } // namespace printf_core
109 } // namespace __llvm_libc
110 
111 #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_OCT_CONVERTER_H
112