c :: import "@ffi/c" meta :: import "@std/meta" ReadError :: enum { read_failed } WriteError :: enum { write_failed no_progress } Io :: struct { context ?*mut anyopaque vtable @IoVTable } IoVTable :: struct { read @func(context ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError write @func(context ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError } ReadStream :: enum(c_int) { stdin = 0 } WriteStream :: enum(c_int) { stdout = 1 stderr = 2 } Reader :: struct { impl Io stream ReadStream } Writer :: struct { impl Io stream WriteStream } read func(reader Reader, buffer []mut u8) usize ! ReadError { if buffer.len == 0 { return 0 } count usize :: try reader.impl.vtable.read(reader.impl.context, reader.stream, buffer) if count > buffer.len { return .read_failed } return count } write func(writer Writer, bytes []u8) usize ! WriteError { if bytes.len == 0 { return 0 } count usize :: try writer.impl.vtable.write(writer.impl.context, writer.stream, bytes) if count > bytes.len { return .write_failed } return count } write_all func(writer Writer, bytes []u8) void ! WriteError { offset usize = 0 while offset < bytes.len { count usize :: write(writer, bytes[offset..]) catch |err| { return err } if count == 0 { return .no_progress } offset += count } return } hide write_decimal_signed func(writer Writer, value i64) void ! WriteError { buffer [21]mut u8 = undefined end usize = buffer.len current i64 = value while true { digit_value i64 :: rem!(current, 10) digit u8 = 0 if digit_value < 0 { digit = u8(-digit_value) } else { digit = u8(digit_value) } end -= 1 buffer[end] = '0' + digit current = divtrunc!(current, 10) if current == 0 { break } } if value < 0 { end -= 1 buffer[end] = '-' } try write_all(writer, buffer[end..]) return } hide write_decimal_unsigned func(writer Writer, value u64) void ! WriteError { buffer [21]mut u8 = undefined end usize = buffer.len current u64 = value while true { digit u8 :: u8(rem!(current, 10)) end -= 1 buffer[end] = '0' + digit current = divtrunc!(current, 10) if current == 0 { break } } try write_all(writer, buffer[end..]) return } hide FormatTokenKind :: enum { unused literal string decimal } hide FormatToken :: struct { kind FormatTokenKind start usize end usize field []u8 } hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken { tokens [N]mut FormatToken = undefined for (usize(0))..format.len |index| { tokens[index] = FormatToken {kind = .unused, start = 0, end = 0, field = ""} } field_count usize = 0 match typeinfo!(Args) { .record |record|: { if !record.is_tuple { compile_error!("io.print arguments must be a tuple") } field_count = record.fields.len } else: compile_error!("io.print arguments must be a tuple") } token_count usize = 0 argument_count usize = 0 literal_start usize = 0 cursor usize = 0 while cursor < format.len { byte :: format[cursor] if byte == '{' { if cursor + 1 >= format.len { compile_error!("io.print format has an unmatched '{'") } if cursor > literal_start { tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""} token_count += 1 } next :: format[cursor + 1] if next == '{' { tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, field = ""} token_count += 1 cursor += 2 literal_start = cursor continue } if cursor + 2 >= format.len or format[cursor + 2] != '}' { compile_error!("io.print format expects '{s}' or '{d}'") } kind FormatTokenKind = .unused if next == 's' { kind = .string } else if next == 'd' { kind = .decimal } else { compile_error!("io.print format has an unknown specifier") } if argument_count >= field_count { compile_error!("io.print format argument count does not match the tuple") } tokens[token_count] = FormatToken { kind = kind, start = 0, end = 0, field = format_field_name(Args, argument_count), } token_count += 1 argument_count += 1 cursor += 3 literal_start = cursor continue } if byte == '}' { if cursor + 1 >= format.len or format[cursor + 1] != '}' { compile_error!("io.print format has an unmatched '}'") } if cursor > literal_start { tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""} token_count += 1 } tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, field = ""} token_count += 1 cursor += 2 literal_start = cursor continue } cursor += 1 } if literal_start < format.len { tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = format.len, field = ""} } if argument_count != field_count { compile_error!("io.print format argument count does not match the tuple") } return tokens } hide format_field_name func($T type, index usize) []u8 { match typeinfo!(T) { .record |record|: return record.fields[index].name else: compile_error!("io.print arguments must be a tuple") } } hide write_decimal func(writer Writer, $T type, value T) void ! WriteError { match typeinfo!(T) { .integer: if minval!(T) < 0 { try write_decimal_signed(writer, i64(value)) } else { try write_decimal_unsigned(writer, u64(value)) } else: compile_error!("io.print '{d}' requires an integer argument") } return } print func( writer Writer, $format []u8, $Args type, args Args, ) void ! WriteError { inline for parse_format(format.len, format, Args) |token| { if (token.kind == .unused) { break } if (token.kind == .literal) { try write_all(writer, format[token.start..token.end]) continue } if (token.kind == .string) { try write_all(writer, field!(args, token.field)) continue } try write_decimal(writer, field!(args, token.field)) } return } hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError { request usize = buffer.len maximum usize :: usize(maxval!(c_long)) if request > maximum { request = maximum } while true { count c_long :: c.read(c_int(stream), buffer.ptr, c_ulong(request)) if count >= 0 { return usize(count) } if c.__error()^ != 4 { return .read_failed } } } hide system_write func(_ ?*mut anyopaque, stream WriteStream, bytes []u8) usize ! WriteError { fd c_int :: c_int(stream) request usize = bytes.len maximum usize :: usize(maxval!(c_long)) if request > maximum { request = maximum } while true { count c_long :: c.write(fd, bytes.ptr, c_ulong(request)) if count >= 0 { return usize(count) } if c.__error()^ != 4 { return .write_failed } } } hide system_vtable IoVTable :: IoVTable { read = system_read, write = system_write, } hide system func() Io { return Io { context = none, vtable = &system_vtable, } }