From 4ef6c4fcb128ff6d4434e83e72cee88897b80ada Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Thu, 23 Sep 2021 11:54:15 -0500 Subject: [PATCH] overloaded procedures example; added set.iterator --- core/container/set.onyx | 31 ++++++++++++ examples/06_dynamic_arrays.onyx | 2 +- examples/14_overloaded_procs.onyx | 78 ++++++++++++++++++++++++++++++- tests/bucket_array.onyx | 2 +- 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/core/container/set.onyx b/core/container/set.onyx index 1946ee60..cac405d3 100644 --- a/core/container/set.onyx +++ b/core/container/set.onyx @@ -89,6 +89,37 @@ empty :: (use set: ^Set($T)) -> bool { return entries.count == 0; } +iterator :: (set: ^Set($T)) -> Iterator(T) { + Context :: struct (T: type_expr) { + entry_array: [..] Set.Entry(T); + position: i32; + } + + context := new(#type Context(T)); + context.entry_array = set.entries; + context.position = 0; + + next :: ($T: type_expr, use context: ^Context(T)) -> (T, bool) { + if position < entry_array.count { + defer position += 1; + return entry_array[position].value, true; + + } else { + return __zero_value(T), false; + } + } + + close :: (context: rawptr) { + cfree(context); + } + + return .{ + data = context, + next = #solidify next { T = T }, + close = close, + }; +} + // // Private symbols // diff --git a/examples/06_dynamic_arrays.onyx b/examples/06_dynamic_arrays.onyx index fb4d27c8..5fae9640 100644 --- a/examples/06_dynamic_arrays.onyx +++ b/examples/06_dynamic_arrays.onyx @@ -15,7 +15,7 @@ main :: (args: [] cstr) { // Declaring dynamic arrays in Onyx looks like this. The // '..' makes it a dynamic array, as opposed to a slice // or fixed size array. - arr : [..] i32; + arr : [..] i32; // Currently, there are no implicit procedure calls anywhere // in the language because I find explicitly specifying diff --git a/examples/14_overloaded_procs.onyx b/examples/14_overloaded_procs.onyx index 91a06fb7..d7c6df1a 100644 --- a/examples/14_overloaded_procs.onyx +++ b/examples/14_overloaded_procs.onyx @@ -17,7 +17,19 @@ // (y: str) { ... }, // } // - +// +// There are several tradeoffs when comparing explicit vs implicit procedure +// overloads. The primary reason a language like C++ would have implicit overloads +// is that procedures cannot be defined inside of other procedures, and overall +// the scoping rules for procedures are a lot simpler. In Onyx however, because +// procedures can be arbitrarily nested, determining which procedure is actually +// going to be called is difficult from both a compiler design perspective, and +// from the person whose is actually writing the code. This complexity is not +// worth the engineering effort to support in the compiler, and therefore I +// settled on the explicit approach. After writing a lot of code in the Onyx, I +// believe I made the right choice. +// +// Let's look at some examples of overloaded procedures and how they are resolved. #load "core/std" @@ -25,4 +37,68 @@ use package core main :: (args: [] cstr) { + // See the overloaded procedure defined below. + + // This works as you would expect. + print_type(10); + print_type(3.14159f); + print_type("Hello, overloaded procedures!"); + + print_type(Person.{ "Joe", 38 }); + + // Some internal data structures like Map and Set use an overloaded procedure to provide a hash + // for the keys of the data. This procedure is core.hash.to_u32. If you want to have a set of + // a datatype you made, or a map with keys of a datatype you made, you need to provide an overload + // to core.hash.to_u32 for your datatype. In this program, that definition is at the end of the file. + person_set := set.make(Person); + person_set << Person.{ "Joe", 38 }; + person_set << Person.{ "Jane", 36 }; + person_set << Person.{ "Joe", 38 }; + + // "Joe" will only be printed once. + for person: set.iterator(^person_set) { + println(person); + } +} + +// Here is a basic overloaded procedure. It prints out the type of the variable you called it with. +print_type :: #match { + (x: i32) { printf("Called with an i32 ({})\n", x); }, + (x: f32) { printf("Called with an f32 ({})\n", x); }, + (x: str) { printf("Called with a str ({})\n", x); }, +} + +// print_type is a little silly to have in this language because Onyx has complete +// type knowledge at runtime. To rectify this, let's add a new case that makes +// this work for any type: + +#add_match print_type, #precedence 10 (x: $T) { + printf("Fallback case: called with a {} ({})\n", T, x); +} + +// There are a couple things going on here. The #add_match directive takes two "parameters" separated +// by a comma: the overloaded procedure to add a match option to, and the option itself. The other +// thing to notice is the "#precedence 10" specification. Onyx tries to match the arguments you provided +// with each of the options. The first one that matches perfectly, is the one that is used. The order in +// which it does this process is important, and that is why you can control it with the #precedence +// directive. The #precedence directive is allowed in two places: right before the option in the original +// #match directive, or in front of the option in an #add_match directive, as seen above. Currently, +// the options are sorted in ascending order according to the precedence. I feel like this is backwards, +// so that may change in the future. Either way, setting the precedence allows you to place the options +// in the correct order so something like (x: $T), which will match ANY single argument, is placed last +// after the specific versions above. Without the precedence, each option will match with the new option, +// as they have a default precedence of 0. It is good to note that in the original #match directive, +// options automatically have a precedence of 1 more than the option before it, so lexicographical order +// does matter. + +Person :: struct { name: str; age: i32; } + +// The overload option for hashing a person: +#add_match (package core).hash.to_u32, (use p: Person) -> u32 { + return hash.to_u32(name) * 16239563 + age; +} + +// Ignore this for now, but it is needed in order to make a set of Persons. +#operator == (p1: Person, p2: Person) -> bool { + return p1.name == p2.name && p1.age == p2.age; } diff --git a/tests/bucket_array.onyx b/tests/bucket_array.onyx index 02101106..d94ae518 100644 --- a/tests/bucket_array.onyx +++ b/tests/bucket_array.onyx @@ -3,7 +3,7 @@ use package core main :: (args: [] cstr) { - ba := bucket_array.make(i32, 4); + ba := bucket_array.make(i32, 4); for i: 24 do ba << i; sum := 0; -- 2.25.1