iter.ha (4765B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use ascii; 5 use strings; 6 use strconv; 7 use types; 8 9 // Tagged union of the [[formattable]] types and [[mods]]. Used for 10 // functions which accept format strings. 11 export type field = (...formattable | *mods); 12 13 // Tagged union of all types which are formattable. 14 export type formattable = (...types::numeric | uintptr | str | rune | bool | 15 nullable *opaque | void); 16 17 // Negative modifier. Specifies for numerical arguments when to prepend a plus 18 // or minus sign or a blank space. 19 export type neg = enum { 20 NONE, 21 SPACE, 22 PLUS, 23 }; 24 25 // Alignment modifier. Specifies how to align an argument within a given width. 26 export type alignment = enum { 27 RIGHT, 28 CENTER, 29 LEFT, 30 }; 31 32 // Specifies how to format an argument. 33 export type mods = struct { 34 alignment: alignment, 35 pad: rune, 36 neg: neg, 37 width: size, 38 prec: size, 39 base: strconv::base, 40 ffmt: strconv::ffmt, 41 fflags: strconv::fflags, 42 }; 43 44 type iterator = struct { 45 iter: strings::iterator, 46 args: []field, 47 idx: size, 48 checkunused: bool, 49 }; 50 51 fn iter(fmt: str, args: []field) iterator = iterator { 52 iter = strings::iter(fmt), 53 args = args, 54 idx = 0, 55 checkunused = true, 56 }; 57 58 fn next(it: *iterator) (str | (formattable, mods) | done) = { 59 let r = match (strings::next(&it.iter)) { 60 case done => 61 return done; 62 case let r: rune => 63 yield r; 64 }; 65 switch (r) { 66 case '{' => void; // handled below 67 case '}' => 68 match (strings::next(&it.iter)) { 69 case done => 70 abort("Invalid format string (hanging '}')"); 71 case let r: rune => 72 assert(r == '}', "Invalid format string (hanging '}')"); 73 }; 74 return "}"; 75 case => 76 strings::prev(&it.iter); 77 let start = it.iter; 78 for (let r => strings::next(&it.iter)) { 79 if (r == '{' || r == '}') { 80 strings::prev(&it.iter); 81 break; 82 }; 83 }; 84 return strings::slice(&start, &it.iter); 85 }; 86 87 r = getrune(it); 88 if (r == '{') { 89 return "{"; 90 }; 91 92 let idx = if (ascii::isdigit(r)) { 93 strings::prev(&it.iter); 94 it.checkunused = false; 95 defer r = getrune(it); 96 yield scan_sz(it); 97 } else { 98 defer it.idx += 1; 99 yield it.idx; 100 }; 101 assert(idx < len(it.args), "Not enough parameters given"); 102 let arg = it.args[idx] as formattable; 103 let mod = mods { ... }; 104 105 switch (r) { 106 case ':' => 107 scan_modifiers(it, &mod); 108 case '%' => 109 r = getrune(it); 110 let idx = if (ascii::isdigit(r)) { 111 strings::prev(&it.iter); 112 it.checkunused = false; 113 defer r = getrune(it); 114 yield scan_sz(it); 115 } else { 116 defer it.idx += 1; 117 yield it.idx; 118 }; 119 assert(idx < len(it.args), "Not enough parameters given"); 120 mod = *(it.args[idx] as *mods); 121 assert(r == '}', "Invalid format string (didn't find '}' after modifier index)"); 122 case '}' => void; 123 case => abort("Invalid format string"); 124 }; 125 126 return (arg, mod); 127 }; 128 129 fn scan_modifiers(it: *iterator, mod: *mods) void = { 130 mod.pad = ' '; 131 for (true) switch (getrune(it)) { 132 // alignment 133 case '-' => mod.alignment = alignment::LEFT; 134 case '=' => mod.alignment = alignment::CENTER; 135 // padding 136 case '_' => mod.pad = getrune(it); 137 // negation 138 case ' ' => mod.neg = neg::SPACE; 139 case '+' => mod.neg = neg::PLUS; 140 // base 141 case 'x' => mod.base = strconv::base::HEX_LOWER; 142 case 'X' => mod.base = strconv::base::HEX_UPPER; 143 case 'o' => mod.base = strconv::base::OCT; 144 case 'b' => mod.base = strconv::base::BIN; 145 // ffmt 146 case 'e' => mod.ffmt = strconv::ffmt::E; 147 case 'f' => mod.ffmt = strconv::ffmt::F; 148 case 'g' => mod.ffmt = strconv::ffmt::G; 149 // fflags 150 case 'F' => 151 switch (getrune(it)) { 152 case 's' => mod.fflags |= strconv::fflags::SHOW_POS; 153 case '.' => mod.fflags |= strconv::fflags::SHOW_POINT; 154 case 'U' => mod.fflags |= strconv::fflags::UPPERCASE; 155 case 'E' => mod.fflags |= strconv::fflags::UPPER_EXP; 156 case 'S' => mod.fflags |= strconv::fflags::SHOW_POS_EXP; 157 case '2' => mod.fflags |= strconv::fflags::SHOW_TWO_EXP_DIGITS; 158 case => abort("Invalid float flag"); 159 }; 160 // precision 161 case '.' => mod.prec = scan_sz(it); 162 // width 163 case '1', '2', '3', '4', '5', '6', '7', '8', '9' => 164 strings::prev(it); 165 mod.width = scan_sz(it); 166 case => 167 strings::prev(it); 168 break; 169 }; 170 assert(getrune(it) == '}', "Invalid format string (unterminated '{')"); 171 }; 172 173 fn scan_sz(it: *iterator) size = { 174 let start = it.iter; 175 assert(ascii::isdigit(getrune(it))); 176 for (ascii::isdigit(getrune(it))) void; 177 strings::prev(&it.iter); 178 179 match (strconv::stoz(strings::slice(&start, &it.iter))) { 180 case strconv::invalid => 181 abort("Invalid format string (invalid integer)"); 182 case strconv::overflow => 183 abort("Invalid format string (integer overflow)"); 184 case let z: size => 185 return z; 186 }; 187 }; 188 189 fn getrune(it: *iterator) rune = match (strings::next(&it.iter)) { 190 case done => 191 abort("Invalid format string (unterminated '{')"); 192 case let r: rune => 193 return r; 194 };