hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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