diff --git a/lib/std/collections/maybe.c3 b/lib/std/collections/maybe.c3 new file mode 100644 index 000000000..9e51c3e70 --- /dev/null +++ b/lib/std/collections/maybe.c3 @@ -0,0 +1,19 @@ +module std::collections::maybe(); + +struct Maybe +{ + Type value; + bool has_value; +} + +fn Maybe value(Type val) +{ + return { .value = val, .has_value = true }; +} + +const Maybe EMPTY = { }; + +macro Type! Maybe.get(self) +{ + return self.has_value ? self.value : SearchResult.MISSING?; +} diff --git a/lib/std/collections/tuple.c3 b/lib/std/collections/tuple.c3 new file mode 100644 index 000000000..d8869e844 --- /dev/null +++ b/lib/std/collections/tuple.c3 @@ -0,0 +1,16 @@ +module std::collections::tuple(); + +struct Tuple +{ + Type1 first; + Type2 second; +} + +module std::collections::triple(); + +struct Triple +{ + Type1 first; + Type2 second; + Type3 third; +} \ No newline at end of file diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index 06ef130b6..4c1c58c44 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -368,14 +368,14 @@ macro bool equals(a, b, isz len = -1, usz $align = 0) return true; } -macro @clone(&value, Allocator *using = mem::heap()) @builtin +macro @clone(value, Allocator *using = mem::heap()) @builtin { $typeof(value)* x = malloc($typeof(value), .using = using); *x = value; return x; } -macro @tclone(&value) @builtin => @clone(value, mem::temp()); +macro @tclone(value) @builtin => @clone(value, mem::temp()); macro type_alloc_must_be_aligned($Type) { @@ -601,16 +601,17 @@ macro void @pool(TempAllocator* #other_temp = null; @body) @builtin tlocal Allocator* thread_allocator @private = &allocator::LIBC_ALLOCATOR; tlocal TempAllocator* thread_temp_allocator @private = null; tlocal TempAllocator*[2] temp_allocator_pair @private; +Allocator* temp_base_allocator @private = &allocator::LIBC_ALLOCATOR; -macro TempAllocator* create_default_sized_temp_allocator() @local +macro TempAllocator* create_default_sized_temp_allocator(Allocator* allocator) @local { $switch (env::MEMORY_ENV) $case NORMAL: - return allocator::new_temp(1024 * 256, thread_allocator)!!; + return allocator::new_temp(1024 * 256, allocator)!!; $case SMALL: - return allocator::new_temp(1024 * 16, thread_allocator)!!; + return allocator::new_temp(1024 * 16, allocator)!!; $case TINY: - return allocator::new_temp(1024 * 2, thread_allocator)!!; + return allocator::new_temp(1024 * 2, allocator)!!; $case NONE: unreachable("Temp allocator must explicitly created when memory-env is set to 'none'."); $endswitch @@ -631,8 +632,8 @@ import libc; fn void init_default_temp_allocators() @private { - temp_allocator_pair[0] = create_default_sized_temp_allocator(); - temp_allocator_pair[1] = create_default_sized_temp_allocator(); + temp_allocator_pair[0] = create_default_sized_temp_allocator(temp_base_allocator); + temp_allocator_pair[1] = create_default_sized_temp_allocator(temp_base_allocator); thread_temp_allocator = temp_allocator_pair[0]; } @@ -661,5 +662,6 @@ fn void initialize_wasm_mem() @init(1) @private uptr start = (uptr)&__heap_base; if (start > mem::DEFAULT_MEM_ALIGNMENT) allocator::wasm_memory.use = start; wasm_allocator.init(fn (x) => allocator::wasm_memory.allocate_block(x)); + temp_base_allocator = &wasm_allocator; thread_allocator = &wasm_allocator; } \ No newline at end of file diff --git a/lib/std/math/math.random.c3 b/lib/std/math/math.random.c3 index d95c8097d..a2aa6bd28 100644 --- a/lib/std/math/math.random.c3 +++ b/lib/std/math/math.random.c3 @@ -11,30 +11,47 @@ interface Random fn void next_bytes(char[] buffer); } -macro Random.seed(&self, seed) +macro bool is_random(random) => $assignable(random, Random*); + +/** + * @require is_random(random) + **/ +macro void seed(random, seed) { - self.set_seed(@as_char_view(seed)); + random.set_seed(@as_char_view(seed)); } -fn int Random.next(&random, int max) +/** + * @require is_random(random) + **/ +macro int next(random, int max) { - return (int)(random.next_double() * max); + return (int)(next_double(random) * max); } -fn bool Random.next_bool(&self) +/** + * @require is_random(random) + **/ +macro void next_bool(random) { - return self.next_byte() & 1 == 0; + return random.next_byte() & 1; } -fn float Random.next_float(&self) +/** + * @require is_random(random) + **/ +macro float next_float(random) { - uint val = self.next_int() & (1 << 24 - 1); + uint val = random.next_int() & (1 << 24 - 1); return val / (float)(1 << 24); } -fn double Random.next_double(&self) +/** + * @require is_random(random) + **/ +macro double next_double(random) { - ulong val = self.next_long() & (1UL << 53 - 1); + ulong val = random.next_long() & (1UL << 53 - 1); return val * 0x1.0p-53; } diff --git a/releasenotes.md b/releasenotes.md index ff956e6de..6c5ff14a9 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -3,6 +3,7 @@ ## 0.5.0 Change List ### Changes / improvements +- `$defined` can take a list of expressions. - `$and` compile time "and" which does not check expressions after the first is an error. - `$is_const` returns true if an expression is compile time const. - `$assignable` returns true is an expression may be implicitly cast to a type. @@ -129,6 +130,7 @@ - `assert` may now take varargs for formatting. ### Stdlib changes +- Tuple and Maybe types. - `.as_str()` replaced by `.str_view()` - Added `math::log(x , base)` and `math::ln(x)`. - Hashmap keys implicitly copied if copy/free are defined. @@ -179,6 +181,7 @@ - Added posix socket functions. ### Fixes +- Structs returned from macros and then indexed into directly could previously be miscompiled. - Naked functions now correctly handles `asm`. - Indexing into arrays would not always widen the index safely. - Macros with implicit return didn't correctly deduct the return type. diff --git a/resources/examples/benchmarks.c3 b/resources/examples/benchmarks.c3 index 6b55caef1..7ed2102a1 100644 --- a/resources/examples/benchmarks.c3 +++ b/resources/examples/benchmarks.c3 @@ -20,7 +20,7 @@ fn void! very_long_name_bench() @benchmark return std::thread::sleep_ms(10); } -static initialize +fn void initialize() @init { set_benchmark_warmup_iterations(5); set_benchmark_max_iterations(1000); diff --git a/resources/examples/contextfree/boolerr.c3 b/resources/examples/contextfree/boolerr.c3 index 119be3fae..a857e330b 100644 --- a/resources/examples/contextfree/boolerr.c3 +++ b/resources/examples/contextfree/boolerr.c3 @@ -1,52 +1,16 @@ module test; import libc; import std::io; +import std::collections::maybe; -struct Doc { Head *head; } -struct Head { DString* title; } +def MaybeString = Maybe(); +def MaybeHead = Maybe(); +def new_head_val = maybe::value(); +def new_string_val = maybe::value(); -struct Summary +fault TitleResult { - DString* title; - bool ok; -} - -struct StringData @private -{ - Allocator* allocator; - usz len; - usz capacity; - char[*] chars; -} - -fn void Summary.print(Summary *s, File* out) -{ - String title = s.title ? s.title.str_view() : "missing"; - (void)io::fprintf(out, "Summary({ .title = %s, .ok = %s})", title, s.ok); -} - -fn bool contains(String haystack, String needle) -{ - usz len = haystack.len; - usz needle_len = needle.len; - if (len < needle_len) return false; - if (!needle_len) return true; - len -= needle_len - 1; - for (usz i = 0; i < len; i++) - { - if (mem::equals(haystack[i..], needle)) - { - return true; - } - } - return false; -} - -macro @dupe(value) -{ - $typeof(&value) temp = malloc_checked($typeof(value))!; - *temp = value; - return temp; + TITLE_MISSING } fault ReadError @@ -54,58 +18,52 @@ fault ReadError BAD_READ, } -fn Doc! readDoc(String url) +struct Doc { MaybeHead head; } +struct Head { MaybeString title; } + +struct Summary { - if (contains(url, "fail")) return ReadError.BAD_READ?; - if (contains(url, "head-missing")) return { .head = null }; - if (contains(url, "title-missing")) return { @dupe(Head { .title = null }) }; - if (contains(url, "title-empty")) return { @dupe(Head { .title = @dupe((DString)null) }) }; - DString str; - str.printf("Title of %s", url); - return { @dupe(Head { .title = @dupe(str) }) }; + MaybeString title; + bool ok; } -fn Summary buildSummary(Doc doc) +fn void! Summary.print(Summary *s, OutStream* out) +{ + io::fprintf(out, "Summary({ .title = %s, .ok = %s})", s.title.get() ?? "missing", s.ok)!; +} + +fn Doc! read_doc(String url) +{ + if (url.contains("fail")) return ReadError.BAD_READ?; + if (url.contains("head-missing")) return { }; + if (url.contains("title-missing")) return { .head = new_head_val({}) }; + if (url.contains("title-empty")) return { .head = new_head_val({ .title = new_string_val("")}) }; + return { .head = new_head_val({ .title = new_string_val(string::printf("Title of %s", url)) }) }; +} + +fn Summary build_summary(Doc doc) { return Summary { - .title = doc.head ? doc.head.title : null, + .title = new_string_val(doc.head.get().title.get()) ?? MaybeString {}, .ok = true, }; } -fn Summary readAndBuildSummary(String url) +fn Summary read_and_build_summary(String url) { - return buildSummary(readDoc(url)) ?? Summary { .title = null, .ok = false }; - /* - // or - Summary summary = buildSummary(readDoc(url)); - if (catch summary) return Summary { .title = null, .ok = false }; - return summary; - // or - Summary summary = buildSummary(readDoc(url)); - if (try summary) return summary; - return Summary { .title = null, .ok = false }; - */ + return build_summary(read_doc(url)) ?? Summary {}; } - -fault TitleResult +fn bool! is_title_non_empty(Doc doc) { - TITLE_MISSING + String! title = doc.head.get().title.get(); + if (catch title) return TitleResult.TITLE_MISSING?; + return title.len > 0; } -fn bool! isTitleNonEmpty(Doc doc) +fn bool! read_whether_title_non_empty(String url) { - if (!doc.head) return TitleResult.TITLE_MISSING?; - DString* head = doc.head.title; - if (!head) return TitleResult.TITLE_MISSING?; - return head.len() > 0; -} - - -fn bool! readWhetherTitleNonEmpty(String url) -{ - return isTitleNonEmpty(readDoc(url)); + return is_title_non_empty(read_doc(url)); } fn String bool_to_string(bool b) @@ -113,27 +71,26 @@ fn String bool_to_string(bool b) return b ? "true" : "false"; } - fn void main() { const String[] URLS = { "good", "title-empty", "title-missing", "head-missing", "fail" }; DynamicArenaAllocator dynamic_arena; dynamic_arena.init(1024); + OutStream* out = io::stdout(); foreach (String url : URLS) { mem::@scoped(&dynamic_arena) { io::printf(`Checking "https://%s/":` "\n", url); - Summary summary = readAndBuildSummary(url); - io::printf(" Summary: "); - summary.print(io::stdout()); - io::printn(""); - String title_sure = summary.title ? summary.title.str_view() : ""; - io::printf(" Title: %s\n", title_sure); - bool! has_title = readWhetherTitleNonEmpty(url); + Summary summary = read_and_build_summary(url); + io::fprintf(out, " Summary: ")!!; + summary.print(out)!!; + io::fprintn(out, "")!!; + io::fprintf(out, " Title: %s\n", summary.title.get() ?? "")!!; + bool! has_title = read_whether_title_non_empty(url); // This looks a bit less than elegant, but as you see it's mostly due to having to // use printf here. - io::printf(" Has title: %s vs %s\n", bool_to_string(has_title) ?? (@catch(has_title)).nameof, has_title ?? false); + io::fprintf(out, " Has title: %s vs %s\n", bool_to_string(has_title) ?? (@catch(has_title)).nameof, has_title ?? false)!!; }; dynamic_arena.reset(); } diff --git a/resources/examples/contextfree/guess_number.c3 b/resources/examples/contextfree/guess_number.c3 index 6676bf6a7..770e622be 100644 --- a/resources/examples/contextfree/guess_number.c3 +++ b/resources/examples/contextfree/guess_number.c3 @@ -1,8 +1,7 @@ module guess_number; import std::io; -import libc; - -extern fn isz getline(char** linep, usz* linecapp, CFile stream); +import std::math; +import std::time; struct Game { @@ -20,33 +19,21 @@ fault InputResult int err_count = 0; -fn int! askGuess(int high) +fn int! ask_guess(int high) { - libc::printf("Guess a number between 1 and %d: ", high); - String text = readLine()!; - char* end = null; - int value = (int)libc::strtol(text.ptr, &end, 10); - if (end && end[0] >= ' ') return InputResult.NOT_AN_INT?; - return value; + io::printf("Guess a number between 1 and %d: ", high); + String text = io::readline() ?? InputResult.FAILED_TO_READ?!; + return text.to_int() ?? InputResult.NOT_AN_INT?; } -fn String! readLine() -{ - char* chars = tmalloc(1024); - isz loaded = getline(&chars, &&(usz)1023, libc::stdin()); - if (loaded < 0) return InputResult.FAILED_TO_READ?; - chars[loaded] = 0; - return (String)chars[0..(loaded - 1)]; -} - -fn int! askGuessMulti(int high) +fn int! ask_guess_multi(int high) { while (true) { - int! result = askGuess(high); + int! result = ask_guess(high); if (@catch(result) == InputResult.NOT_AN_INT) { - libc::printf("I didn't understand that.\n"); + io::printn("I didn't understand that."); err_count++; continue; } @@ -58,20 +45,20 @@ fn void! Game.play(Game *game) { while (!game.done) { - int guess = askGuessMulti(game.high)!; + int guess = ask_guess_multi(game.high)!; game.report(guess); game.update(guess); } } -fn void Game.report(Game *game, int guess) +fn void Game.report(Game* game, int guess) { String desc = {| if (guess < game.answer) return "too low"; if (guess > game.answer) return "too high"; return "the answer"; |}; - libc::printf("%d is %.*s.\n", guess, (int)desc.len, desc.ptr); + io::printfn("%d is %s.\n", guess, desc); } fn void Game.update(Game *game, int guess) @@ -82,11 +69,12 @@ fn void Game.update(Game *game, int guess) fn void! main() { - libc::srand((int)libc::clock()); + Lcg128Random rand; + random::seed(&rand, clock::now()); int high = 100; - int answer = libc::rand() % high + 1; + int answer = random::next(&rand, high) + 1; Game game = { .answer = answer, .high = high }; (void)game.play(); - libc::printf("Finished in %d guesses.\n", game.guesses); - libc::printf("Total input errors: %d.\n", err_count); + io::printfn("Finished in %d guesses.", game.guesses); + io::printfn("Total input errors: %d.", err_count); } \ No newline at end of file diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 86f237bff..49c57bf2d 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -768,6 +768,7 @@ static void llvm_emit_member_addr(GenContext *c, BEValue *value, Decl *parent, D llvm_value_bitcast(c, value, found->type); break; case TYPE_STRUCT: + llvm_value_addr(c, value); llvm_value_struct_gep(c, value, value, (unsigned)index); break; default: