Browse Source

Allow flushing with `strings.Builder`; Make `fmt.fprint*` procedures use a custom flush procedure

gingerBill 4 years ago
parent
commit
e83af93394
2 changed files with 168 additions and 88 deletions
  1. 41 17
      core/fmt/fmt.odin
  2. 127 71
      core/strings/builder.odin

+ 41 - 17
core/fmt/fmt.odin

@@ -11,7 +11,7 @@ import "core:time"
 import "core:unicode/utf8"
 import "intrinsics"
 
-DEFAULT_BUFFER_SIZE :: #config(FMT_DEFAULT_BUFFER_SIZE, 1<<12);
+DEFAULT_BUFFER_SIZE :: #config(FMT_DEFAULT_BUFFER_SIZE, 1<<10);
 
 Info :: struct {
 	minus:     bool,
@@ -62,29 +62,58 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist
 }
 
 
+Flush_Data :: struct {
+	handle: os.Handle,
+	bytes_written: int,
+}
+
+fmt_file_builder :: proc(data: []byte, fd: ^Flush_Data) -> strings.Builder {
+	b := strings.builder_from_slice(data);
+	b.flush_data = rawptr(fd);
+	b.flush_proc = proc(b: ^strings.Builder) -> (do_reset: bool) {
+		fd := (^Flush_Data)(b.flush_data);
+		res := strings.to_string(b^);
+		n, _ := os.write_string(fd.handle, res);
+		fd.bytes_written += max(n, 0);
+		return true;
+	};
+
+	return b;
+}
+
 fprint :: proc(fd: os.Handle, args: ..any, sep := " ") -> int {
 	data: [DEFAULT_BUFFER_SIZE]byte;
-	buf := strings.builder_from_slice(data[:]);
+	flush_data := Flush_Data{handle=fd};
+	buf := fmt_file_builder(data[:], &flush_data);
 	res := sbprint(buf=&buf, args=args, sep=sep);
-	os.write_string(fd, res);
-	return len(res);
+	strings.flush_builder(&buf);
+	return flush_data.bytes_written;
 }
 
 fprintln :: proc(fd: os.Handle, args: ..any, sep := " ") -> int {
 	data: [DEFAULT_BUFFER_SIZE]byte;
-	buf := strings.builder_from_slice(data[:]);
+	flush_data := Flush_Data{handle=fd};
+	buf := fmt_file_builder(data[:], &flush_data);
 	res := sbprintln(buf=&buf, args=args, sep=sep);
-	os.write_string(fd, res);
-	return len(res);
+	strings.flush_builder(&buf);
+	return flush_data.bytes_written;
 }
 fprintf :: proc(fd: os.Handle, fmt: string, args: ..any) -> int {
 	data: [DEFAULT_BUFFER_SIZE]byte;
-	buf := strings.builder_from_slice(data[:]);
+	flush_data := Flush_Data{handle=fd};
+	buf := fmt_file_builder(data[:], &flush_data);
 	res := sbprintf(&buf, fmt, ..args);
-	os.write_string(fd, res);
-	return len(res);
+	strings.flush_builder(&buf);
+	return flush_data.bytes_written;
+}
+fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info) -> int {
+	data: [DEFAULT_BUFFER_SIZE]byte;
+	flush_data := Flush_Data{handle=fd};
+	buf := fmt_file_builder(data[:], &flush_data);
+	reflect.write_type(&buf, info);
+	strings.flush_builder(&buf);
+	return flush_data.bytes_written;
 }
-
 
 // print* procedures return the number of bytes written
 print   :: proc(args: ..any, sep := " ") -> int { return fprint(fd=os.stdout, args=args, sep=sep); }
@@ -174,12 +203,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! {
 	p("Panic", message, loc);
 }
 
-fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info) {
-	data: [DEFAULT_BUFFER_SIZE]byte;
-	buf := strings.builder_from_slice(data[:]);
-	reflect.write_type(&buf, info);
-	os.write_string(fd, strings.to_string(buf));
-}
+
 
 
 

+ 127 - 71
core/strings/builder.odin

@@ -4,20 +4,26 @@ import "core:mem"
 import "core:unicode/utf8"
 import "core:strconv"
 
+Builder_Flush_Proc :: #type proc(b: ^Builder) -> (do_reset: bool);
+
 Builder :: struct {
 	buf: [dynamic]byte,
+
+	// The custom flush procedure allows for the ability to flush the buffer, i.e. write to file
+	flush_proc: Builder_Flush_Proc,
+	flush_data: rawptr,
 }
 
 make_builder_none :: proc(allocator := context.allocator) -> Builder {
-	return Builder{make([dynamic]byte, allocator)};
+	return Builder{buf=make([dynamic]byte, allocator)};
 }
 
 make_builder_len :: proc(len: int, allocator := context.allocator) -> Builder {
-	return Builder{make([dynamic]byte, len, allocator)};
+	return Builder{buf=make([dynamic]byte, len, allocator)};
 }
 
 make_builder_len_cap :: proc(len, cap: int, allocator := context.allocator) -> Builder {
-	return Builder{make([dynamic]byte, len, cap, allocator)};
+	return Builder{buf=make([dynamic]byte, len, cap, allocator)};
 }
 
 make_builder :: proc{
@@ -42,6 +48,25 @@ reset_builder :: proc(b: ^Builder) {
 	clear(&b.buf);
 }
 
+flush_builder :: proc(b: ^Builder) -> (was_reset: bool) {
+	if b.flush_proc != nil {
+		was_reset = b.flush_proc(b);
+		if was_reset {
+			reset_builder(b);
+
+		}
+	}
+	return;
+}
+
+flush_builder_check_space :: proc(b: ^Builder, required: int) -> (was_reset: bool) {
+	if n := max(cap(b.buf) - len(b.buf), 0); n < required {
+		was_reset = flush_builder(b);
+	}
+	return;
+}
+
+
 builder_from_slice :: proc(backing: []byte) -> Builder {
 	s := transmute(mem.Raw_Slice)backing;
 	d := mem.Raw_Dynamic_Array{
@@ -50,7 +75,9 @@ builder_from_slice :: proc(backing: []byte) -> Builder {
 		cap  = s.len,
 		allocator = mem.nil_allocator(),
 	};
-	return transmute(Builder)d;
+	return Builder{
+		buf = transmute([dynamic]byte)d,
+	};
 }
 to_string :: proc(b: Builder) -> string {
 	return string(b.buf[:]);
@@ -62,15 +89,41 @@ builder_len :: proc(b: Builder) -> int {
 builder_cap :: proc(b: Builder) -> int {
 	return cap(b.buf);
 }
+builder_space :: proc(b: Builder) -> int {
+	return max(cap(b.buf), len(b.buf), 0);
+}
 
-write_byte :: proc(b: ^Builder, x: byte) {
-	append(&b.buf, x);
+write_byte :: proc(b: ^Builder, x: byte) -> (n: int) {
+	flush_builder_check_space(b, 1);
+	if builder_space(b^) > 0 {
+		append(&b.buf, x);
+		n += 1;
+	}
+	return;
+}
+
+write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) {
+	x := x;
+	for len(x) != 0 {
+		flush_builder_check_space(b, len(x));
+		space := builder_space(b^);
+		if space == 0 {
+			break; // No need to append
+		}
+		i := min(space, len(x));
+		n += i;
+		append(&b.buf, ..x[:i]);
+		if len(x) <= i {
+			break; // No more data to append
+		}
+		x = x[i:];
+	}
+	return;
 }
 
 write_rune :: proc(b: ^Builder, r: rune) -> int {
 	if r < utf8.RUNE_SELF {
-		write_byte(b, byte(r));
-		return 1;
+		return write_byte(b, byte(r));
 	}
 
 	s, n := utf8.encode_rune(r);
@@ -78,12 +131,8 @@ write_rune :: proc(b: ^Builder, r: rune) -> int {
 	return n;
 }
 
-write_string :: proc(b: ^Builder, s: string) {
-	write_bytes(b, transmute([]byte)s);
-}
-
-write_bytes :: proc(b: ^Builder, x: []byte) {
-	append(&b.buf, ..x);
+write_string :: proc(b: ^Builder, s: string) -> (n: int) {
+	return write_bytes(b, transmute([]byte)s);
 }
 
 pop_byte :: proc(b: ^Builder) -> (r: byte) {
@@ -107,8 +156,8 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) {
 @(private, static)
 DIGITS_LOWER := "0123456789abcdefx";
 
-write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') {
-	write_byte(b, quote);
+write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') -> (n: int) {
+	n += write_byte(b, quote);
 	for width, s := 0, str; len(s) > 0; s = s[width:] {
 		r := rune(s[0]);
 		width = 1;
@@ -116,51 +165,57 @@ write_quoted_string :: proc(b: ^Builder, str: string, quote: byte = '"') {
 			r, width = utf8.decode_rune_in_string(s);
 		}
 		if width == 1 && r == utf8.RUNE_ERROR {
-			write_byte(b, '\\');
-			write_byte(b, 'x');
-			write_byte(b, DIGITS_LOWER[s[0]>>4]);
-			write_byte(b, DIGITS_LOWER[s[0]&0xf]);
+			n += write_byte(b, '\\');
+			n += write_byte(b, 'x');
+			n += write_byte(b, DIGITS_LOWER[s[0]>>4]);
+			n += write_byte(b, DIGITS_LOWER[s[0]&0xf]);
 			continue;
 		}
 
-		write_escaped_rune(b, r, quote);
+		n += write_escaped_rune(b, r, quote);
 
 	}
-	write_byte(b, quote);
+	n += write_byte(b, quote);
+	return;
 }
 
 
-write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) {
-	if write_quote { write_byte(b, '\''); }
+write_encoded_rune :: proc(b: ^Builder, r: rune, write_quote := true) -> (n: int) {
+	if write_quote {
+		n += write_byte(b, '\'');
+	}
 	switch r {
-	case '\a': write_string(b, `\a"`);
-	case '\b': write_string(b, `\b"`);
-	case '\e': write_string(b, `\e"`);
-	case '\f': write_string(b, `\f"`);
-	case '\n': write_string(b, `\n"`);
-	case '\r': write_string(b, `\r"`);
-	case '\t': write_string(b, `\t"`);
-	case '\v': write_string(b, `\v"`);
+	case '\a': n += write_string(b, `\a"`);
+	case '\b': n += write_string(b, `\b"`);
+	case '\e': n += write_string(b, `\e"`);
+	case '\f': n += write_string(b, `\f"`);
+	case '\n': n += write_string(b, `\n"`);
+	case '\r': n += write_string(b, `\r"`);
+	case '\t': n += write_string(b, `\t"`);
+	case '\v': n += write_string(b, `\v"`);
 	case:
 		if r < 32 {
-			write_string(b, `\x`);
+			n += write_string(b, `\x`);
 			buf: [2]byte;
 			s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil);
 			switch len(s) {
-			case 0: write_string(b, "00");
-			case 1: write_byte(b, '0');
-			case 2: write_string(b, s);
+			case 0: n += write_string(b, "00");
+			case 1: n += write_byte(b, '0');
+			case 2: n += write_string(b, s);
 			}
 		} else {
-			write_rune(b, r);
+			n += write_rune(b, r);
 		}
 
 	}
-	if write_quote { write_byte(b, '\''); }
+	if write_quote {
+		n += write_byte(b, '\'');
+	}
+	return;
 }
 
 
-write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) {
+write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false) -> (n: int) {
 	is_printable :: proc(r: rune) -> bool {
 		if r <= 0xff {
 			switch r {
@@ -178,75 +233,76 @@ write_escaped_rune :: proc(b: ^Builder, r: rune, quote: byte, html_safe := false
 	if html_safe {
 		switch r {
 		case '<', '>', '&':
-			write_byte(b, '\\');
-			write_byte(b, 'u');
+			n += write_byte(b, '\\');
+			n += write_byte(b, 'u');
 			for s := 12; s >= 0; s -= 4 {
-				write_byte(b, DIGITS_LOWER[r>>uint(s) & 0xf]);
+				n += write_byte(b, DIGITS_LOWER[r>>uint(s) & 0xf]);
 			}
 			return;
 		}
 	}
 
 	if r == rune(quote) || r == '\\' {
-		write_byte(b, '\\');
-		write_byte(b, byte(r));
+		n += write_byte(b, '\\');
+		n += write_byte(b, byte(r));
 		return;
 	} else if is_printable(r) {
-		write_encoded_rune(b, r, false);
+		n += write_encoded_rune(b, r, false);
 		return;
 	}
 	switch r {
-	case '\a': write_string(b, `\a`);
-	case '\b': write_string(b, `\b`);
-	case '\e': write_string(b, `\e`);
-	case '\f': write_string(b, `\f`);
-	case '\n': write_string(b, `\n`);
-	case '\r': write_string(b, `\r`);
-	case '\t': write_string(b, `\t`);
-	case '\v': write_string(b, `\v`);
+	case '\a': n += write_string(b, `\a`);
+	case '\b': n += write_string(b, `\b`);
+	case '\e': n += write_string(b, `\e`);
+	case '\f': n += write_string(b, `\f`);
+	case '\n': n += write_string(b, `\n`);
+	case '\r': n += write_string(b, `\r`);
+	case '\t': n += write_string(b, `\t`);
+	case '\v': n += write_string(b, `\v`);
 	case:
 		switch c := r; {
 		case c < ' ':
-			write_byte(b, '\\');
-			write_byte(b, 'x');
-			write_byte(b, DIGITS_LOWER[byte(c)>>4]);
-			write_byte(b, DIGITS_LOWER[byte(c)&0xf]);
+			n += write_byte(b, '\\');
+			n += write_byte(b, 'x');
+			n += write_byte(b, DIGITS_LOWER[byte(c)>>4]);
+			n += write_byte(b, DIGITS_LOWER[byte(c)&0xf]);
 
 		case c > utf8.MAX_RUNE:
 			c = 0xfffd;
 			fallthrough;
 		case c < 0x10000:
-			write_byte(b, '\\');
-			write_byte(b, 'u');
+			n += write_byte(b, '\\');
+			n += write_byte(b, 'u');
 			for s := 12; s >= 0; s -= 4 {
-				write_byte(b, DIGITS_LOWER[c>>uint(s) & 0xf]);
+				n += write_byte(b, DIGITS_LOWER[c>>uint(s) & 0xf]);
 			}
 		case:
-			write_byte(b, '\\');
-			write_byte(b, 'U');
+			n += write_byte(b, '\\');
+			n += write_byte(b, 'U');
 			for s := 28; s >= 0; s -= 4 {
-				write_byte(b, DIGITS_LOWER[c>>uint(s) & 0xf]);
+				n += write_byte(b, DIGITS_LOWER[c>>uint(s) & 0xf]);
 			}
 		}
 	}
+	return;
 }
 
 
-write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) {
+write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) {
 	buf: [32]byte;
 	s := strconv.append_bits(buf[:], u64(i), base, false, 64, strconv.digits, nil);
-	write_string(b, s);
+	return write_string(b, s);
 }
-write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) {
+write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) {
 	buf: [32]byte;
 	s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil);
-	write_string(b, s);
+	return write_string(b, s);
 }
 
-write_uint :: proc(b: ^Builder, i: uint, base: int = 10) {
-	write_u64(b, u64(i), base);
+write_uint :: proc(b: ^Builder, i: uint, base: int = 10) -> (n: int) {
+	return write_u64(b, u64(i), base);
 }
-write_int :: proc(b: ^Builder, i: int, base: int = 10) {
-	write_i64(b, i64(i), base);
+write_int :: proc(b: ^Builder, i: int, base: int = 10) -> (n: int) {
+	return write_i64(b, i64(i), base);
 }