From b175b9318a8169c3255dd7a3e3222b2fbc89eb48 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 22 Feb 2023 17:06:06 +0100 Subject: [PATCH] Fix conversion `if (int x = foo())`. Initial stream api. Extended enumset. --- lib/std/collections/enumset.c3 | 97 +++++++++++--- lib/std/io/io.c3 | 9 +- lib/std/io/io_file.c3 | 23 ++-- lib/std/io/io_stream.c3 | 205 ++++++++++++++++++++++++++++++ lib/std/io/stream/bytereader.c3 | 80 ++++++++++++ lib/std/io/stream/bytewriter.c3 | 114 +++++++++++++++++ lib/std/io/stream/filestream.c3 | 18 +++ src/compiler/llvm_codegen_stmt.c | 2 +- src/compiler/semantic_analyser.c | 1 + src/version.h | 2 +- test/unit/stdlib/io/bytestream.c3 | 25 ++++ 11 files changed, 541 insertions(+), 35 deletions(-) create mode 100644 lib/std/io/io_stream.c3 create mode 100644 lib/std/io/stream/bytereader.c3 create mode 100644 lib/std/io/stream/bytewriter.c3 create mode 100644 lib/std/io/stream/filestream.c3 create mode 100644 test/unit/stdlib/io/bytestream.c3 diff --git a/lib/std/collections/enumset.c3 b/lib/std/collections/enumset.c3 index f042db38e..6cc36c126 100644 --- a/lib/std/collections/enumset.c3 +++ b/lib/std/collections/enumset.c3 @@ -1,83 +1,144 @@ -// TODO: ensure the type is an enum first. +// Copyright (c) 2021 Christoffer Lerno. All rights reserved. +// Use of this source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. + +/** + * @require $Type.kindof == TypeKind.ENUM "Only enums maybe be used with an enumset" + **/ module std::collections::enumset; -$assert(Enum.elements < 64, "Maximum number of elements for an enum used as enum set is 63"); -$switch ($$C_INT_SIZE): -$case 64: - typedef EnumSetType @private = ulong; -$case 32: - $if (Enum.elements < 32): -typedef EnumSetType @private = uint; - $else: +$if (Enum.elements > 128): +typedef EnumSetType @private = char[(Enum.elements + 7) / 8]; +const IS_CHAR_ARRAY = true; +$elif (Enum.elements > 64): +typedef EnumSetType @private = uint128; +const IS_CHAR_ARRAY = false; +$elif (Enum.elements > 32 || $$C_INT_SIZE > 32): typedef EnumSetType @private = ulong; - $endif; -$default: - $if (Enum.elements < 16): +const IS_CHAR_ARRAY = false; +$elif (Enum.elements > 16 || $$C_INT_SIZE > 16): +typedef EnumSetType @private = uint; +const IS_CHAR_ARRAY = false; +$elif (Enum.elements > 8 || $$C_INT_SIZE > 8): typedef EnumSetType @private = ushort; - $elif (Enum.elements < 31): -typedef EnumSetType @private = uint; - $else: -typedef EnumSetType @private = ulong; - $endif; -$endswitch; +const IS_CHAR_ARRAY = false; +$else: +typedef EnumSetType @private = char; +const IS_CHAR_ARRAY = false; +$endif; typedef EnumSet = distinct EnumSetType; fn void EnumSet.add(EnumSet* this, Enum v) { +$if (IS_CHAR_ARRAY): + (*this)[v / 8] |= (char)(1u << (v % 8)); +$else: *this = (EnumSet)((EnumSetType)*this | 1u << (EnumSetType)v); +$endif; } fn void EnumSet.clear(EnumSet* this) { +$if (IS_CHAR_ARRAY): + *this = {}; +$else: *this = 0; +$endif; } fn bool EnumSet.remove(EnumSet* this, Enum v) { +$if (IS_CHAR_ARRAY): + if (!this.has(v) @inline) return false; + (*this)[v / 8] &= (char)~(1 << (v % 8)); + return true; +$else: EnumSetType old = (EnumSetType)*this; EnumSetType new = old & ~(1u << (EnumSetType)v); *this = (EnumSet)new; return old != new; +$endif; } fn bool EnumSet.has(EnumSet* this, Enum v) { +$if (IS_CHAR_ARRAY): + return (bool)(((*this)[v / 8] << (v % 8)) & 0x01); +$else: return ((EnumSetType)*this & (1u << (EnumSetType)v)) != 0; +$endif; } fn void EnumSet.add_all(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + foreach (i, c : s) (*this)[i] |= c; +$else: *this = (EnumSet)((EnumSetType)*this | (EnumSetType)s); +$endif; } fn void EnumSet.retain_all(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + foreach (i, c : s) (*this)[i] &= c; +$else: *this = (EnumSet)((EnumSetType)*this & (EnumSetType)s); +$endif; } fn void EnumSet.remove_all(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + foreach (i, c : s) (*this)[i] &= ~c; +$else: *this = (EnumSet)((EnumSetType)*this & ~(EnumSetType)s); +$endif; } fn EnumSet EnumSet.and_of(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + EnumSet copy = *this; + copy.retain_all(s); + return copy; +$else: return (EnumSet)((EnumSetType)*this & (EnumSetType)s); +$endif; } fn EnumSet EnumSet.or_of(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + EnumSet copy = *this; + copy.add_all(s); + return copy; +$else: return (EnumSet)((EnumSetType)*this | (EnumSetType)s); +$endif; } + fn EnumSet EnumSet.diff_of(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + EnumSet copy = *this; + copy.remove_all(s); + return copy; +$else: return (EnumSet)((EnumSetType)*this & ~(EnumSetType)s); +$endif; } fn EnumSet EnumSet.xor_of(EnumSet* this, EnumSet s) { +$if (IS_CHAR_ARRAY): + EnumSet copy = *this; + foreach (i, c : s) copy[i] ^= c; + return copy; +$else: return (EnumSet)((EnumSetType)*this ^ (EnumSetType)s); +$endif; } \ No newline at end of file diff --git a/lib/std/io/io.c3 b/lib/std/io/io.c3 index 47978e9e9..952b55574 100644 --- a/lib/std/io/io.c3 +++ b/lib/std/io/io.c3 @@ -19,19 +19,22 @@ enum Seek fault IoError { FILE_NOT_FOUND, - FILE_NOT_SEEKABLE, FILE_NOT_VALID, - FILE_INVALID_POSITION, + INVALID_POSITION, FILE_OVERFLOW, FILE_IS_PIPE, FILE_EOF, - FILE_INCOMPLETE_WRITE, + INCOMPLETE_WRITE, FILE_NOT_DIR, NO_PERMISSION, + INVALID_PUSHBACK, + EOF, + NOT_SEEKABLE, NAME_TOO_LONG, INTERRUPTED, GENERAL_ERROR, UNKNOWN_ERROR, + UNSUPPORTED_OPERATION, } fn void putchar(char c) @inline diff --git a/lib/std/io/io_file.c3 b/lib/std/io/io_file.c3 index 1192ed1f2..4fcafb4b4 100644 --- a/lib/std/io/io_file.c3 +++ b/lib/std/io/io_file.c3 @@ -51,19 +51,21 @@ fn void! File.memopen(File* file, char[] data, String mode) /** * @require file.file != null **/ -fn void! File.seek(File *file, long offset, Seek seekMode = Seek.SET) +fn usz! File.seek(File *file, isz offset, Seek seekMode = Seek.SET) { if (libc::fseek(file.file, (SeekIndex)(offset), (int)(seekMode))) { switch (libc::errno()) { - case errno::EBADF: return IoError.FILE_NOT_SEEKABLE!; - case errno::EINVAL: return IoError.FILE_INVALID_POSITION!; + case errno::EBADF: return IoError.NOT_SEEKABLE!; + case errno::EINVAL: return IoError.INVALID_POSITION!; case errno::EOVERFLOW: return IoError.FILE_OVERFLOW!; case errno::ESPIPE: return IoError.FILE_IS_PIPE!; default: return IoError.UNKNOWN_ERROR!; } } + if (seekMode == Seek.SET) return offset; + return libc::ftell(file.file); } /** @@ -93,7 +95,7 @@ fn void! File.close(File *file) @inline case errno::ENETDOWN: case errno::ENETUNREACH: case errno::ENOSPC: - case errno::EIO: return IoError.FILE_INCOMPLETE_WRITE!; + case errno::EIO: return IoError.INCOMPLETE_WRITE!; default: return IoError.UNKNOWN_ERROR!; } } @@ -109,24 +111,21 @@ fn bool File.eof(File* file) @inline } /** - * @require file && file.file + * @param [in] buffer */ -fn usz File.read(File* file, void* buffer, usz items, usz element_size = 1) +fn usz File.read(File* file, char[] buffer) { - return libc::fread(buffer, element_size, items, file.file); + return libc::fread(buffer.ptr, 1, buffer.len, file.file); } /** * @param [&in] file * @param [&out] buffer - * @param items - * @param element_size * @require file.file `File must be initialized` - * @require element_size > 1 */ -fn usz File.write(File* file, void* buffer, usz items, usz element_size = 1) +fn usz File.write(File* file, char[] buffer) { - return libc::fwrite(buffer, element_size, items, file.file); + return libc::fwrite(buffer.ptr, 1, buffer.len, file.file); } /** diff --git a/lib/std/io/io_stream.c3 b/lib/std/io/io_stream.c3 new file mode 100644 index 000000000..3fe383b26 --- /dev/null +++ b/lib/std/io/io_stream.c3 @@ -0,0 +1,205 @@ +module std::io; + +typedef CloseStreamFn = fn void!(Stream*); +typedef FlushStreamFn = fn void!(Stream*); +typedef SeekStreamFn = fn usz!(Stream*, isz offset, Seek seek); +typedef LenStreamFn = fn usz(Stream*); +typedef AvailableStreamFn = fn usz(Stream*); +typedef ReadStreamFn = fn usz!(Stream*, char[] bytes); +typedef ReadFromStreamFn = fn usz!(Stream*, Stream*); +typedef ReadByteStreamFn = fn char!(Stream*); +typedef PushbackByteStreamFn = fn void!(Stream*); +typedef WriteStreamFn = fn usz!(Stream*, char[] bytes); +typedef WriteToStreamFn = fn usz!(Stream*, Stream* out); +typedef WriteByteStreamFn = fn void!(Stream*, char c); +typedef DestroyStreamFn = fn void!(Stream*); + +struct StreamInterface +{ + CloseStreamFn close_fn; + FlushStreamFn flush_fn; + SeekStreamFn seek_fn; + LenStreamFn len_fn; + AvailableStreamFn available_fn; + ReadStreamFn read_fn; + ReadFromStreamFn read_stream_fn; + ReadByteStreamFn read_byte_fn; + PushbackByteStreamFn pushback_byte_fn; + WriteStreamFn write_fn; + WriteToStreamFn write_stream_fn; + WriteByteStreamFn write_byte_fn; + DestroyStreamFn destroy_fn; +} + +struct Stream +{ + StreamInterface *fns; + void* data; +} + +fn bool Stream.supports_seek(Stream* s) @inline => (bool)s.fns.seek_fn; +fn bool Stream.supports_available(Stream* s) @inline => s.fns.available_fn || s.fns.seek_fn; +fn bool Stream.supports_len(Stream* s) @inline => s.fns.len_fn || s.fns.seek_fn; +fn bool Stream.supports_read(Stream* s) @inline => s.fns.read_fn || s.fns.read_byte_fn; +fn bool Stream.supports_read_from(Stream* s) @inline => (bool)s.fns.read_stream_fn; +fn bool Stream.supports_write_to(Stream* s) @inline => (bool)s.fns.write_stream_fn; +fn bool Stream.supports_pushback_byte(Stream* s) @inline => s.fns.pushback_byte_fn || s.fns.seek_fn; +fn bool Stream.supports_write(Stream* s) @inline => s.fns.write_fn || s.fns.write_byte_fn; + +fn void! Stream.destroy(Stream* s) @inline @maydiscard +{ + if (s.fns.destroy_fn) return s.fns.destroy_fn(s); + return s.close(); +} + +fn void! Stream.close(Stream* s) @inline @maydiscard +{ + if (CloseStreamFn func = s.fns.close_fn) return func(s); +} + +fn usz! Stream.seek(Stream* s, isz offset, Seek seek) @inline +{ + if (SeekStreamFn func = s.fns.seek_fn) return func(s, offset, seek); + return IoError.NOT_SEEKABLE!; +} + +fn usz! Stream.available(Stream* s) @inline +{ + if (AvailableStreamFn func = s.fns.available_fn) return func(s); + if (SeekStreamFn func = s.fns.seek_fn) + { + usz curr = func(s, 0, Seek.CURSOR)?; + usz len = func(s, 0, Seek.END)?; + func(s, curr, Seek.SET)?; + return len - curr; + } + return IoError.NOT_SEEKABLE!; +} + +fn usz! Stream.read(Stream* s, char[] buffer) +{ + if (ReadStreamFn func = s.fns.read_fn) return func(s, buffer); + if (ReadByteStreamFn func = s.fns.read_byte_fn) + { + usz len = 0; + foreach (&cptr : buffer) + { + char! c = func(s); + if (catch err = c) + { + case IoError.EOF: return len; + default: return err!; + } + *cptr = c; + len++; + } + } + return IoError.UNSUPPORTED_OPERATION!; +} + +fn char! Stream.read_byte(Stream* s) @inline +{ + if (ReadByteStreamFn func = s.fns.read_byte_fn) return func(s); + return IoError.UNSUPPORTED_OPERATION!; +} + +fn usz! Stream.write(Stream* s, char[] bytes) @inline +{ + if (WriteStreamFn func = s.fns.write_fn) return func(s, bytes); + if (WriteByteStreamFn func = s.fns.write_byte_fn) + { + foreach (c : bytes) func(s, c)?; + return bytes.len; + } + return IoError.UNSUPPORTED_OPERATION!; +} + +fn void! Stream.write_byte(Stream* s, char b) @inline +{ + if (WriteByteStreamFn func = s.fns.write_byte_fn) return func(s, b); + return IoError.UNSUPPORTED_OPERATION!; +} + +fn usz! Stream.write_to(Stream* s, Stream* to) @inline +{ + if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, to); + return IoError.UNSUPPORTED_OPERATION!; +} + +fn usz! Stream.read_from(Stream* s, Stream* from) @inline +{ + if (ReadFromStreamFn func = s.fns.read_stream_fn) return func(s, from); + return IoError.UNSUPPORTED_OPERATION!; +} + +fn void! Stream.flush(Stream* s) @inline @maydiscard +{ + if (FlushStreamFn func = s.fns.flush_fn) return func(s); + return IoError.UNSUPPORTED_OPERATION!; +} + +fn usz! Stream.len(Stream* s) @inline +{ + if (LenStreamFn func = s.fns.len_fn) return func(s); + if (SeekStreamFn func = s.fns.seek_fn) + { + usz curr = func(s, 0, Seek.CURSOR)?; + usz len = func(s, 0, Seek.END)?; + func(s, curr, Seek.SET)?; + return len; + } + return IoError.NOT_SEEKABLE!; +} + +fn void! Stream.pushback_byte(Stream* s) @inline +{ + if (PushbackByteStreamFn func = s.fns.pushback_byte_fn) return func(s); + if (SeekStreamFn func = s.fns.seek_fn) + { + func(s, -1, CURSOR)?; + return; + } + return IoError.UNSUPPORTED_OPERATION!; +} + +fn void! Stream.write_string(Stream* s, String str) @inline => (void)(s.write((char[])str)?); + +fn usz! Stream.copy_to(Stream* s, Stream* dst, char[] buffer = {}) +{ + if (buffer.len) return copy_through_buffer(s, dst, buffer); + if (WriteToStreamFn func = s.fns.write_stream_fn) return func(s, dst); + if (ReadFromStreamFn func = dst.fns.read_stream_fn) return func(dst, s); +$switch (env::MEMORY_ENV): +$case NORMAL: + @pool() + { + return copy_through_buffer(s, dst, array::talloc(char, 4096)); + }; +$case SMALL: + @pool() + { + return copy_through_buffer(s, dst, array::talloc(char, 1024)); + }; +$case TINY: +$case NONE: + return copy_through_buffer(s, dst, &&(char[256]{})); +$endswitch; +} + +macro usz! copy_through_buffer(Stream* s, Stream* dst, char[] buffer) @local +{ + usz total_copied; + while (true) + { + usz! len = s.read(buffer); + if (catch err = len) + { + case IoError.EOF: return total_copied; + default: return err!; + } + if (!len) return total_copied; + usz written = dst.write(buffer[:len])?; + total_copied += len; + if (written != len) return IoError.INCOMPLETE_WRITE!; + } +} diff --git a/lib/std/io/stream/bytereader.c3 b/lib/std/io/stream/bytereader.c3 new file mode 100644 index 000000000..a486644ee --- /dev/null +++ b/lib/std/io/stream/bytereader.c3 @@ -0,0 +1,80 @@ +module std::io; +import std::math; + +struct ByteReader +{ + char[] bytes; + usz index; +} + +fn void ByteReader.init(ByteReader* reader, char[] bytes) +{ + *reader = { .bytes = bytes }; +} + +fn Stream ByteReader.as_stream(ByteReader* reader) +{ + return { .fns = &bytereader_interface, .data = reader }; +} + +fn usz! ByteReader.read(ByteReader* reader, char[] bytes) +{ + if (reader.index >= reader.bytes.len) return IoError.EOF!; + usz len = math::min(reader.bytes.len - reader.index, bytes.len); + if (len == 0) return 0; + mem::copy(bytes.ptr, &reader.bytes[reader.index], len); + reader.index += len; + return len; +} + +fn char! ByteReader.read_byte(ByteReader* reader) +{ + if (reader.index >= reader.bytes.len) return IoError.EOF!; + return reader.bytes[reader.index++]; +} + +fn void! ByteReader.pushback_byte(ByteReader* reader) +{ + if (!reader.index) return IoError.INVALID_PUSHBACK!; + reader.index--; +} + +fn usz! ByteReader.seek(ByteReader* reader, isz offset, Seek seek) +{ + isz new_index; + switch (seek) + { + case SET: new_index = offset; + case CURSOR: new_index = reader.index + offset; + case END: new_index = reader.bytes.len + offset; + } + if (new_index < 0) return IoError.INVALID_POSITION!; + reader.index = new_index; + return new_index; +} + +fn usz! ByteReader.write_stream(ByteReader* reader, Stream* writer) +{ + if (reader.index >= reader.bytes.len) return 0; + usz written = writer.write(reader.bytes[reader.index..])?; + reader.index += written; + assert(reader.index <= reader.bytes.len); + return written; +} + +fn usz ByteReader.available(ByteReader* reader) +{ + return math::max((isz)0, (isz)reader.bytes.len - reader.index); +} + +StreamInterface bytereader_interface = { + .len_fn = fn (s) => ((ByteReader*)s.data).bytes.len, + .read_fn = fn (s, char[] bytes) => ((ByteReader*)s.data).read(bytes) @inline, + .read_byte_fn = fn (s) => ((ByteReader*)s.data).read_byte() @inline, + .pushback_byte_fn = fn (s) => ((ByteReader*)s.data).pushback_byte() @inline, + .seek_fn = fn (s, offset, seek) => ((ByteReader*)s.data).seek(offset, seek) @inline, + .write_stream_fn = fn (s, writer) => ((ByteReader*)s.data).write_stream(writer) @inline, + .available_fn = fn (s) => ((ByteReader*)s.data).available() @inline, +}; + + diff --git a/lib/std/io/stream/bytewriter.c3 b/lib/std/io/stream/bytewriter.c3 new file mode 100644 index 000000000..ecb2ee59c --- /dev/null +++ b/lib/std/io/stream/bytewriter.c3 @@ -0,0 +1,114 @@ +module std::io; + +struct ByteWriter +{ + char[] bytes; + usz index; + Allocator* allocator; +} + +/** + * @param [&inout] writer + * @param [&in] allocator + * @require writer.bytes.len == 0 "Init may not run on on already initialized data" + * @ensure allocator != null, index == 0 + **/ +fn void ByteWriter.init(ByteWriter* writer, Allocator* allocator = mem::current_allocator()) +{ + *writer = { .bytes = {}, .allocator = allocator }; +} + +/** + * @param [&inout] writer + * @require writer.bytes.len == 0 "Init may not run on on already initialized data" + * @ensure allocator != null, index == 0 + **/ +fn void ByteWriter.tinit(ByteWriter* writer) +{ + *writer = { .bytes = {}, .allocator = mem::temp_allocator() }; +} + +fn Stream ByteWriter.as_stream(ByteWriter* writer) +{ + return { .fns = &bytewriter_interface, .data = writer }; +} + +fn void ByteWriter.destroy(ByteWriter* writer) +{ + if (!writer.allocator) return; + if (void* ptr = writer.bytes.ptr) writer.allocator.free(ptr)!!; + *writer = { }; +} + +fn String ByteWriter.as_str(ByteWriter* writer) +{ + return writer.bytes[:writer.index]; +} + +fn void! ByteWriter.ensure_capacity(ByteWriter* writer, usz len) @inline +{ + if (writer.bytes.len > len) return; + if (len < 16) len = 16; + usz new_capacity = math::next_power_of_2(len); + char* new_ptr = writer.allocator.realloc(writer.bytes.ptr, new_capacity)?; + writer.bytes = new_ptr[:new_capacity]; +} + +fn usz! ByteWriter.write(ByteWriter* writer, char[] bytes) +{ + writer.ensure_capacity(writer.index + bytes.len)?; + mem::copy(&writer.bytes[writer.index], bytes.ptr, bytes.len); + writer.index += bytes.len; + return bytes.len; +} + +fn void! ByteWriter.write_byte(ByteWriter* writer, char c) +{ + writer.ensure_capacity(writer.index + 1)?; + writer.bytes[writer.index++] = c; +} + +/** + * @param [&inout] writer + * @param [&inout] reader + **/ +fn usz! ByteWriter.read_from(ByteWriter* writer, Stream* reader) +{ + if (reader.supports_available()) + { + usz total_read = 0; + while (usz available = reader.available()?) + { + writer.ensure_capacity(writer.index + available)?; + usz len = reader.read(writer.bytes[writer.index..])?; + total_read += len; + writer.index += len; + } + return total_read; + } + usz total_read = 0; + while (true) + { + // See how much we can read. + usz len_to_read = writer.bytes.len - writer.index; + // Less than 16 bytes? Double the capacity + if (len_to_read < 16) + { + writer.ensure_capacity(writer.bytes.len * 2)?; + } + // Read into the rest of the buffer + usz read = reader.read(writer.bytes[writer.index..])?; + writer.index += read; + // Ok, we reached the end. + if (read < len_to_read) return total_read; + // Otherwise go another round + } +} + +StreamInterface bytewriter_interface = { + .destroy_fn = fn (s) => ((ByteWriter*)s.data).destroy(), + .len_fn = fn (s) => ((ByteWriter*)s.data).bytes.len, + .write_fn = fn (s, char[] bytes) => ((ByteWriter*)s.data).write(bytes), + .write_byte_fn = fn (s, char c) => ((ByteWriter*)s.data).write_byte(c), + .read_stream_fn = fn (s, reader) => ((ByteWriter*)s.data).read_from(reader), +}; \ No newline at end of file diff --git a/lib/std/io/stream/filestream.c3 b/lib/std/io/stream/filestream.c3 new file mode 100644 index 000000000..d9348eb5a --- /dev/null +++ b/lib/std/io/stream/filestream.c3 @@ -0,0 +1,18 @@ +module std::io; + +fn Stream File.as_stream(File* file) +{ + return { .fns = &filestream_interface, .data = file }; +} + +StreamInterface filestream_interface = { + .close_fn = fn (s) => ((File*)s.data).close(), + .seek_fn = fn (s, offset, seek) => ((File*)s.data).seek(offset, seek) @inline, + .read_fn = fn (s, char[] bytes) => ((File*)s.data).read(bytes) @inline, + .write_fn = fn (s, char[] bytes) => ((File*)s.data).write(bytes) @inline, + .write_byte_fn = fn (s, char c) => ((File*)s.data).putc(c) @inline, + .read_byte_fn = fn (s) => ((File*)s.data).getc() @inline, + .flush_fn = fn (s) => ((File*)s.data).flush() @inline, +}; + + diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index caf3f4fe8..adbf471b3 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -152,7 +152,7 @@ static void llvm_emit_decl_expr_list(GenContext *context, BEValue *be_value, Exp if (type->type_kind != TYPE_BOOL) { CastKind cast = cast_to_bool_kind(type); - llvm_emit_cast(context, cast, last, be_value, type, type_bool); + llvm_emit_cast(context, cast, last, be_value, type_bool, type); } } } diff --git a/src/compiler/semantic_analyser.c b/src/compiler/semantic_analyser.c index 55266d7e5..3e044b738 100644 --- a/src/compiler/semantic_analyser.c +++ b/src/compiler/semantic_analyser.c @@ -324,6 +324,7 @@ RESOLVE_LAMBDA:; } FOREACH_END(); if (found_lambda) goto RESOLVE_LAMBDA; + halt_on_error(); if (active_target.strip_unused && !active_target.testing) { diff --git a/src/version.h b/src/version.h index 1975de5c7..88abf286c 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.80" \ No newline at end of file +#define COMPILER_VERSION "0.4.81" \ No newline at end of file diff --git a/test/unit/stdlib/io/bytestream.c3 b/test/unit/stdlib/io/bytestream.c3 new file mode 100644 index 000000000..a1c70e198 --- /dev/null +++ b/test/unit/stdlib/io/bytestream.c3 @@ -0,0 +1,25 @@ +module std::io @test; + +fn void! bytestream() +{ + ByteReader r; + r.init("abc"); + Stream s = r.as_stream(); + assert(s.len()? == 3); + char[5] buffer; + assert('a' == s.read_byte()?); + s.pushback_byte()?; + usz len = s.read(&buffer)?; + assert(buffer[:len] == "abc"); + ByteWriter w; + w.init(); + Stream ws = w.as_stream(); + ws.write("helloworld")?; + assert(w.as_str() == "helloworld"); + s.seek(0, SET)?; + ws.read_from(&s)?; + s.seek(1, SET)?; + s.write_to(&ws)?; + assert(w.as_str() == "helloworldabcbc"); +} +