diff --git a/lib/std/core/builtin.c3 b/lib/std/core/builtin.c3 index 9da8aea7c..13c9fbee1 100644 --- a/lib/std/core/builtin.c3 +++ b/lib/std/core/builtin.c3 @@ -410,6 +410,7 @@ macro swizzle2(v, v2, ...) @builtin { return $$swizzle2(v, v2, $vasplat); } + <* Return the excuse in the Optional if it is Empty, otherwise return a null fault. @@ -434,6 +435,67 @@ macro bool @ok(#expr) @builtin return true; } +<* + Check if an Optional expression evaluates to a fault. If so, return it; + else, assign the result to an expression. + + @require $defined(#v = #v) : "#v must be a variable" + @require $defined(#expr!) : "Expected an optional expression" + @require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type` +*> +macro void? @try(#v, #expr) @builtin +{ + var res = #expr; + if (catch err = res) return err?; + #v = res; +} + +<* + Check if an Optional expression evaluates to a fault. If so, return true if it is the + expected fault, the optional if it is unexpected, or false if there was no fault and + the assign happened. + + This can be used in like this: + + while (true) + { + char[] data; + // Read until end of file + if (@try_catch(data, load_line(), io::EOF)) break; + .. use data .. + } + + In this example we read until we reach an EOF, which is expected. However, if we encounter some other + fault, we rethrow is. Without this macro, the code is instead written like: + + while (true) + { + char[]? data; + data = load_line(); + if (catch err = data) + { + if (err = io::EOF) break; + return err? + } + .. use data .. + } + + @require $defined(#v = #v) : "#v must be a variable" + @require $defined(#expr!) : "Expected an optional expression" + @require @assignable_to(#expr!!, $typeof(#v)) : `Type of #expr must be an optional of #v's type` + @return "True if it was the expected fault, false if the variable was assigned, otherwise returns an optional." +*> +macro bool? @try_catch(#v, #expr, fault expected_fault) @builtin +{ + var res = #expr; + if (catch err = res) + { + return err == expected_fault ? true : err?; + } + #v = res; + return false; +} + <* @require $defined(&#value, (char*)&#value) : "This must be a value that can be viewed as a char array" *> diff --git a/releasenotes.md b/releasenotes.md index 656737818..c1aebbf28 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -32,6 +32,7 @@ - Add `thread::fence` providing a thread fence. - Place output in `out` by default for projects. Use temp folder for building at the command line. - Allow absolute paths for `$embed`. +- Add `@try` and `@try_catch`. ### Fixes - mkdir/rmdir would not work properly with substring paths on non-windows platforms. diff --git a/test/unit/stdlib/core/builtintests.c3 b/test/unit/stdlib/core/builtintests.c3 index 7bb39ba12..281be076c 100644 --- a/test/unit/stdlib/core/builtintests.c3 +++ b/test/unit/stdlib/core/builtintests.c3 @@ -28,6 +28,41 @@ fn void test_enum_by_name() assert(@catch(enum_by_name(Tester, "GHI")) == NOT_FOUND); } + +faultdef SOME_FAULT, ABC_FAULT; + +fn void test_try_catch() +{ + int val; + int? x = ABC_FAULT?; + assert(@try_catch(val, x, ABC_FAULT)!!); + assert(val == 0); + assert(!@catch(@try_catch(val, x, ABC_FAULT))); + x = SOME_FAULT?; + assert(@catch(@try_catch(val, x, ABC_FAULT)) == SOME_FAULT); + x = 3; + assert(!@try_catch(val, x, ABC_FAULT)!!); + assert(val == 3); +} + +fn void test_try_set() +{ + assert(enum_by_name(Tester, "ABC")!! == Tester.ABC); + + Tester val; + if (catch @try(val, enum_by_name(Tester, "ABC"))) abort("Test failure"); + assert(val == Tester.ABC); + + Tester another; + if (catch err = @try(another, enum_by_name(Tester, "GHI"))) + { + assert(err == NOT_FOUND); + return; + } + + abort("Test failure"); +} + fn void test_likely() { int a = 2;