documented more of the standard library
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 13 Feb 2023 17:31:58 +0000 (11:31 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 13 Feb 2023 17:31:58 +0000 (11:31 -0600)
core/container/map.onyx
core/conv/format.onyx
core/conv/parse.onyx
core/hash/hash.onyx
core/io/stdio.onyx
core/math/math.onyx
core/memory/memory.onyx
core/misc/any_utils.onyx
core/sync/once.onyx
scripts/onyx-pkg.onyx

index 3d2bd202c3e9367ff3eaa882d8fdebccd56a9209..e1a20f8abea392d515b53be885b092af37c92079 100644 (file)
@@ -41,6 +41,7 @@ Map :: struct (Key_Type: type_expr, Value_Type: type_expr) where ValidKey(Key_Ty
     get     :: get
     get_ptr :: get_ptr
     get_opt :: get_opt
+    get_ptr_or_create :: get_ptr_or_create
     put     :: put
     delete  :: delete
     update  :: update
@@ -141,6 +142,20 @@ get_ptr :: (use map: ^Map, key: map.Key_Type) -> ^map.Value_Type {
     return null;
 }
 
+//
+// Returns a pointer to the value at the specified key. If the key
+// is not in the map, a new value is created and inserted, then the
+// pointer to that value is returned.
+get_ptr_or_create :: (use map: ^Map, key: map.Key_Type) -> ^map.Value_Type {
+    lr := lookup(map, key);
+    if lr.entry_index < 0 {
+        put(map, key, .{});
+        lr = lookup(map, key);
+    }
+
+    return ^entries[lr.entry_index].value;
+}
+
 //
 // Returns an Optional of the value at the specified key. The Optional
 // has a value if the key is present, otherwise the optional does not
index a9c5269c6fd3d643babb008b7f9e6744c13df705..388254e4da3c32405c461784aa1436acc9dffae7 100644 (file)
@@ -7,6 +7,10 @@ use core {map, string, array, math}
     custom_parsers   : Map(type_expr, #type (rawptr, str, Allocator) -> bool);
 }
 
+//
+// This procedure is run before main() as it is an #init procedure.
+// It looks for all custom formatting and parsing definitions and
+// registers them to be used in format_any and parse_any.
 custom_formatters_initialized :: #init () {
     map.init(^custom_formatters, default=null_proc);
     map.init(^custom_parsers,    default=null_proc);
@@ -51,39 +55,72 @@ custom_formatters_initialized :: #init () {
     }
 }
 
+//
+// Registers a formatting function for a particular type. This type is
+// inferred from the type of the third argument in the given function.
 register_custom_formatter :: (formatter: (^Format_Output, ^Format, ^$T) -> void) {
     custom_formatters[T] = formatter;
 }
 
+
+//
+// Registers a parsing function for a particular type. This type is
+// inferred from the type of the first argument in the given function.
 register_custom_parser :: (parser: (^$T, str, Allocator) -> bool) {
     custom_parsers[T] = parser;
 }
 
+
+//
+// Tag-type used to specify how to format a structure.
+//
+//     @conv.Custom_Format.{ format_structure }
+//     TheStructure :: struct { ... }
+//
 Custom_Format :: struct {
     format: (^Format_Output, ^Format, rawptr) -> void;
 }
 
+
+//
+// Tag-type used to specify that a certain procedure should be used
+// to format a type.
+//
+//     @conv.Custom_Format_Proc.{ TheStructure }
+//     format_structure :: (output: ^conv.Format_Output, format: ^conv.Format, data: ^TheStructure) { ... }
+//
 Custom_Format_Proc :: struct {
     type: type_expr;
 }
 
+
+//
+// Tag-type used to specify how to parse a structure.
+//
+//     @conv.Custom_Parse.{ parse_structure }
+//     TheStructure :: struct { ... }
+//
 Custom_Parse :: struct {
     parse: (rawptr, str, Allocator) -> bool;
 }
 
+
+//
+// Tag-type used to specify that a certain procedure should be used
+// to parse a type.
+//
+//     @conv.Custom_Parse_Proc.{ TheStructure }
+//     parse_structure :: (data: ^TheStructure, input: str, allocator: Allocator) -> bool { ... }
+//
 Custom_Parse_Proc :: struct {
     type: type_expr;
 }
 
-// @Remove // old aliases to not break old programs
-str_format :: format
-str_format_va :: format_va
-
-Format_Flush_Callback :: struct {
-    data: rawptr = null;
-    func: (rawptr, str) -> bool = null_proc;
-}
-
+//
+// Passed to any custom formatter. Wraps outputting data to any source,
+// using a `flush` callback function. Use `write` to output a string.
+// When the internal buffer is filled, `flush` is called to empty the
+// buffer to the final destination.
 Format_Output :: struct {
     data: ^u8;
     count: u32;
@@ -121,18 +158,26 @@ Format_Output :: struct {
     }
 }
 
+Format_Flush_Callback :: struct {
+    data: rawptr = null;
+    func: (rawptr, str) -> bool = null_proc;
+}
+
+
+//
+// Formatting options passed to a custom formatter.
 Format :: struct {
-    pretty_printing      := false;
-    quote_strings        := false;
-    single_quote_strings := false;
-    dereference          := false;
-    custom_format        := true;
-    interpret_numbers    := true;
-    digits_after_decimal := cast(u32) 4;
+    pretty_printing      := false;        // p
+    quote_strings        := false;        // "
+    single_quote_strings := false;        // '
+    dereference          := false;        // *
+    custom_format        := true;         // ! to disable
+    interpret_numbers    := true;         // d to disable
+    digits_after_decimal := cast(u32) 4;  // .2
 
     indentation   := cast(u32) 0;
-    base          := cast(u64) 10;
-    minimum_width := cast(u32) 0;
+    base          := cast(u64) 10;        // b16
+    minimum_width := cast(u32) 0;         // w12
 }
 
 #local
@@ -142,6 +187,16 @@ flush_to_dynstr :: (dynstr: ^[..] u8, to_write: str) => {
 }
 
 
+
+//
+// Old aliases to not break old programs. Use format and format_va instead.
+str_format :: format
+str_format_va :: format_va
+
+
+//
+// Formats a string using the provided arguments and format specified string.
+// This has many overloads to make it easy to work with.
 format :: #match {}
 
 #overload
@@ -176,6 +231,8 @@ format :: (format: str, va: ..any) -> str {
     return out;
 }
 
+//
+// Like format(), but takes the arguments as an array of `any`s, not a variadic argument array.
 format_va :: #match {}
 
 #overload
@@ -333,10 +390,17 @@ format_va :: (output: ^Format_Output, format: str, va: [] any) -> str {
 }
 
 
+//
+// This procedure converts any value into a string, using the type information system.
+// If a custom formatter is specified for the type, that is used instead.
+// This procedure is generally not used directly; instead, through format or format_va.
 format_any :: (output: ^Format_Output, formatting: ^Format, v: any) {
     use package runtime.info
     array :: package core.array;
 
+    //
+    // Dereference the any if the '*' format specifier was given.
+    // Ignored if the value given is not a pointer.
     if formatting.dereference {
         ti := get_type_info(v.type);
         if ti.kind == .Pointer {
@@ -350,6 +414,8 @@ format_any :: (output: ^Format_Output, formatting: ^Format, v: any) {
         }
     }
 
+    //
+    // Use a custom formatter, if one is registered for the type.
     if formatting.custom_format && custom_formatters->has(v.type) {
         custom_formatters[v.type](output, formatting, v.data);
         return;
index 0ecf2d41d5a1efe11c2dbe004a7314d22652a23a..bb1d0a165b9e2465b2708083f1e02e2f247ef548 100644 (file)
@@ -3,10 +3,8 @@ package core.conv
 use core {map, string, array, math}
 
 //
-// This should be called with a pointer for the first argument.
-//
-//      x: i32;
-//      parse_any(^x, "12.34");
+// Parses many different types from a string into a value.
+// Uses a custom parser if one has been specified for the type given.
 parse_any :: #match {}
 
 #overload
@@ -68,8 +66,6 @@ parse_any :: (target: rawptr, data_type: type_expr, to_parse: str, string_alloca
             line := to_parse;
             string.advance(^line);
 
-            //
-            // For now, this will return a substring in the original to_parse.
             dest := cast(^str) target;
             *dest = string.read_until(^line, #char "\"") |> string.alloc_copy(string_allocator); // @BUG // This does not handle escaped strings!
             return true;
index 2a438c41a0bb97bdd3d37b5c7628795ec10c41a6..739a79b2acc90cf829852bba1eaa4fbbada36741 100644 (file)
@@ -18,7 +18,7 @@ to_u32 :: hash
 //             return // ...
 //         }
 //     }
-
+//
 hash :: #match -> u32 {
     // Does this need to have a higher precedence value?
     // Because if I wanted to have a custom type as the key
@@ -44,6 +44,10 @@ hash :: #match -> u32 {
     macro (key: $T/HasHashMethod) => key->hash()
 }
 
+//
+// Interface that holds true when the type has a hash() overload defined.
+// Useful in datastructure when the ability to hash is dependent on whether
+// the stored type is hashable. See core/container/pair.onyx for an example.
 Hashable :: interface (t: $T) {
     { hash(t) } -> u32;
 }
index 6b7174f0dfc684d5c7a044156b6168bc0ad3fe76..42355ee3ec38f8bebcc51388cf1689fcb94384cc 100644 (file)
@@ -1,36 +1,86 @@
-package core
 // Currently, these symbols are dumped in the 'core' namespace, which means
 // most programs that just 'use package core' can access all of them, which
 // is convenient; However, it doesn't hurt to wonder if they should be the
 // 'core.io' package so these would become like 'io.printf(...)'. Of course,
 // you could always still do 'use package core.io', which would bring these
 // in anyway.
+package core
 
 
+//
+// Ensure this file did not get included in a custom runtime, as it is
+// not possible to print anything if the runtime is not known. This check
+// could be replaced with something like !#defined(runtime.__output_string),
+// but that can happen when custom runtimes are more needed.
 #if runtime.runtime == .Custom {
-    #error "'stdio' can only be included in the 'wasi' or 'js' runtime."
+    #error "'stdio' cannot be used in a custom runtime."
 }
 
-stdio_stream: io.Stream = .{ vtable = ^stdio_vtable, flags = .Block_On_Read };
+//
+// Every thread contains its own copy of standard input and output options.
+#thread_local stdio : struct {
+    // Two-way stream of standard input and output.
+    stream       : io.Stream;
+
+    // These fields are used to create a buffer of standard output.
+    print_stream : io.BufferStream;
+    print_writer : io.Writer;
+
+    // When this is true, the print_stream is flushed when the last character
+    // is a newline. When this is false, all flushing is entirely manual.
+    auto_flush : bool;
+}
+
+//
+// Internal procedure called at thread initialization to setup the
+// `stdio` object. This should not be called more than once per thread,
+// so you will likely never need to call it.
+__stdio_init :: () {
+    stdio.print_stream = io.buffer_stream_make(2048, context.allocator);
+    stdio.print_writer = io.writer_make(^stdio.print_stream, 0);
 
-auto_flush_stdio := true
+    stdio.stream = .{ vtable = ^stdio_vtable, flags = .Block_On_Read };
+
+    stdio.auto_flush = true;
+}
+
+
+//
+// Internal procedure to flush data from the print_stream to standard
+// output. Can be used directly, or through io.stream_flush(^stdio.stream);
+__flush_stdio :: () {
+    if stdio.print_stream.data.count == 0 do return;
+
+    ^stdio.print_stream
+        |> io.buffer_stream_to_str()
+        |> runtime.__output_string();
+
+    ^stdio.print_stream |> io.stream_flush();
+}
 
+//
+// Generic procedure to print something. This is normally not used
+// directly, but does support passing a different number of arguments
+// that are sent directly to io.write.
 print :: #match #locked {
     (x: str) {
         io.write(^stdio.print_writer, x);
-        if x[x.count - 1] == #char "\n" && auto_flush_stdio do __flush_stdio();
+        if x[x.count - 1] == #char "\n" && stdio.auto_flush do __flush_stdio();
     },
 
     (x)    => { io.write(^stdio.print_writer, x); },
     (x, y) => { io.write(^stdio.print_writer, x, y); },
 }
 
+//
+// Helper procedure that prints something, then prints a newline.
 println :: (x) => {
     print(x);
     print("\n");
 }
 
-// Standard formatted print.
+//
+// Standard formatted print to standard output.
 printf :: (format: str, va: ..any) {
     flush :: (_, to_output) => {
         io.write(^stdio.print_writer, to_output);
@@ -42,8 +92,9 @@ printf :: (format: str, va: ..any) {
     print(conv.format_va(buffer, format, va, .{null, flush}));
 }
 
-// Print to standard error, if available.
 #if #defined(runtime.__output_error) {
+    //
+    // Prints to standard error, if available.
     eprintf :: (format: str, va: ..any) -> str {
         flush :: (_, to_output) => {
             runtime.__output_error(to_output);
@@ -55,21 +106,31 @@ printf :: (format: str, va: ..any) {
     }
 }
 
-// Print to a dynamically allocated string.
+//
+// Prints to a dynamically allocated string, and returns the string.
+// It is the callers responsibility to free the string.
 aprintf :: (format: str, va: ..any) -> str {
     buffer: [8196] u8;
     out := conv.format_va(buffer, format, va);
     return string.alloc_copy(out);
 }
 
-// Print to a TEMPORARY dynamically allocated string.
+//
+// Prints to a dynamically allocated string in the temporary allocator,
+// and returns the string. 
 tprintf :: (format: str, va: ..any) -> str {
     buffer: [8196] u8;
     out := conv.format_va(buffer, format, va);
     return string.alloc_copy(out, allocator=context.temp_allocator);
 }
 
-byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) {
+
+//
+// Helper procedure that outputs a set of bytes at a certain location,
+// useful in debugging. The implementation does not use printf or
+// stdio.stream, because if those are corrupted due to a heap/memory
+// bug, they cannot be used.
+__byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) {
     temp: [3] u8;
 
     u8_ptr := cast(^u8) ptr;
@@ -114,27 +175,8 @@ byte_dump :: (ptr: rawptr, byte_count: u32, bytes_per_line := 8) {
 // Private and internal things
 //
 
-#thread_local stdio : struct {
-    print_stream : io.BufferStream;
-    print_writer : io.Writer;
-}
-
-__stdio_init :: () {
-    stdio.print_stream = io.buffer_stream_make(2048, context.allocator);
-    stdio.print_writer = io.writer_make(^stdio.print_stream, 0);
-}
-
-
-__flush_stdio :: () {
-    if stdio.print_stream.data.count == 0 do return;
-
-    ^stdio.print_stream
-        |> io.buffer_stream_to_str()
-        |> runtime.__output_string();
-
-    ^stdio.print_stream |> io.stream_flush();
-}
-
+//
+// The v-table for the stream in stdio.
 #local stdio_vtable := io.Stream_Vtable.{
     read = (_: ^io.Stream, buffer: [] u8) -> (io.Error, u32) {
         __flush_stdio();
index 73a829e31fc371c72abc3d68926c41cd114fdca2..74d87e0bea3a9620713278b0543e8a133986eeee 100644 (file)
@@ -131,7 +131,7 @@ atanh :: (t: $T) -> T {
 //
 // Exponentials and logarithms.
 // Exponentials with floats are implemented using a binary search using square roots, since
-// square roots are intrinsic to WASM and therefore "fast". Expoentials with integers are
+// square roots are intrinsic to WASM and therefore "fast". Exponentials with integers are
 // implemented using a fast algorithm that minimizes the number of the mulitplications that
 // are needed. Logarithms are implemented using a polynomial that is accurate in the range of
 // [1, 2], and then utilizes this identity for values outside of that range,
index a3ca6e70444f5e9c67f011b7352b73e5786f1ff0..a5537a994be3eb7231bcefe5c3bce90a7c399066 100644 (file)
@@ -1,28 +1,22 @@
 package core.memory
 
-align :: #match {
-    (size: ^u64, align: u64) {
-        if *size % align != 0 {
-            *size += align - (*size % align);
-        }
-    },
-
-    (size: u64, align: u64) -> u64 {
-        if size % align != 0 {
-            size += align - (size % align);
-        }
-        return size;
-    }        
-}
-
+//
+// Re-exports the memory_copy intrinsics. Behaves like memmove() in C.
 copy :: core.intrinsics.wasm.memory_copy
+
+//
+// Re-exports the memory_fill intrinsics. Behaves like memset() in C.
 set  :: core.intrinsics.wasm.memory_fill
 
+//
+// Initializes a slice by allocating memory for it out of the allocator.
 alloc_slice :: (sl: ^[] $T, count: i32, allocator := context.allocator) {
     sl.data  = raw_alloc(allocator, sizeof T * count);
     sl.count = count;
 }
 
+//
+// Constructs an intialized slice of `T` with `count` elements from the allocator.
 make_slice :: ($T: type_expr, count: i32, allocator := context.allocator) -> [] T {
     return .{
         data = raw_alloc(allocator, sizeof T * count),
@@ -30,6 +24,9 @@ make_slice :: ($T: type_expr, count: i32, allocator := context.allocator) -> []
     };
 }
 
+//
+// Releases the memory for the slice, as well as setting the fields of the
+// slice to be 0 so it cannot be used again.
 free_slice :: (sl: ^[] $T, allocator := context.allocator) {
     if sl.data == null do return;
 
@@ -38,6 +35,8 @@ free_slice :: (sl: ^[] $T, allocator := context.allocator) {
     sl.count = 0;
 }
 
+//
+// Copies a slice into a new slice, allocated from the allocator.
 copy_slice :: (sl: [] $T, allocator := context.allocator) -> [] T {
     data := raw_alloc(allocator, sl.count * sizeof T);
     copy(data, sl.data, sl.count * sizeof T);
@@ -45,25 +44,41 @@ copy_slice :: (sl: [] $T, allocator := context.allocator) -> [] T {
     return .{ data = data, count = sl.count };
 }
 
-resize_slice :: (sl: [] $T, new_size: i32, allocator := context.allocator) -> [] T {
-    new_slice: [] T;
-    new_slice.data = raw_alloc(allocator, sizeof T * new_size);
-    new_slice.count = new_size;
+//
+// Aligns a number to the next multiple of `align`. Can be used
+// in place when a pointer is passed, otherwise returns the new
+// aligned number.
+align :: #match #local {}
 
-    copy(new_slice.data, sl.data, sl.count * sizeof T);
+#overload
+align :: (size: ^u64, align: u64) {
+    if *size % align != 0 {
+        *size += align - (*size % align);
+    }
+}
 
-    return new_slice;
+#overload
+align :: (size: u64, align: u64) -> u64 {
+    if size % align != 0 {
+        size += align - (size % align);
+    }
+    return size;
 }
 
 
+//
+// Allows for make([] i32).
 #overload
 builtin.__make_overload :: macro (_: ^[] $T, count: u32, allocator := context.allocator) -> [] T {
-    ret := (package core.memory).make_slice(T, count, allocator);
-    (package core.memory).set(ret.data, 0, sizeof T * count);
+    ret := #this_package.make_slice(T, count, allocator);
+    #this_package.set(ret.data, 0, sizeof T * count);
     return ret;
 }
 
+//
+// Allows for delete(^sl);
 #overload
 builtin.delete :: macro (x: ^[] $T) {
-    core.memory.free_slice(x);
+    #this_package.free_slice(x);
 }
+
index a001914397b2b68ef0fab891f9d09eead5880b96..97423ee885c2063ee8f4eebcff9672cdee62ef56 100644 (file)
@@ -24,7 +24,8 @@ any_as :: (a: any, $T: type_expr) -> ^T {
     return cast(^T) a.data;
 }
 
-// Dereference an pointer any.
+//
+// Dereference a pointer any.
 any_dereference :: (v: any) -> any {
     t := get_type_info(v.type);
     if t.kind == .Pointer {
@@ -35,6 +36,7 @@ any_dereference :: (v: any) -> any {
     return v;
 }
 
+//
 // Subscript an array-like any.
 any_subscript :: (v: any, index: i32) -> any {
     base_ptr, elem_type, count := any_as_array(v);
@@ -48,6 +50,7 @@ any_subscript :: (v: any, index: i32) -> any {
     };
 }
 
+//
 // Select a member from an any.
 any_selector :: (v: any, member_name: str) -> any {
     t := get_type_info(v.type);
@@ -61,6 +64,7 @@ any_selector :: (v: any, member_name: str) -> any {
     return .{null, void};
 }
 
+//
 // This selector works with selecting "foo.bar.joe"
 any_nested_selector :: (v: any, member_name: str) -> any {
     t := get_type_info(v.type);
@@ -82,6 +86,20 @@ any_nested_selector :: (v: any, member_name: str) -> any {
     return .{null, void};
 }
 
+//
+// Convert a structure or pointer to a structure to a Map with
+// keys representing the fields of the structure, and values
+// representing the value of each field.
+//
+//     T :: struct {
+//         x := 123;
+//         y := "test";
+//     }
+//     
+//     m := any_to_map(T.{});
+//
+// `m` would have two keys, "x" and "y".
+//
 any_to_map :: (v: any) -> (Map(str, any), success: bool) {
     vals := v;
     if get_type_info(vals.type).kind == .Pointer {
@@ -101,6 +119,7 @@ any_to_map :: (v: any) -> (Map(str, any), success: bool) {
     return out, true;
 }
 
+//
 // Creates an iterator out of an array-like any.
 any_iter :: (arr: any) -> Iterator(any) {
     base_ptr, elem_type, count := any_as_array(arr);
index 9c87fd0922b3c87ceca5bbfaef249b7717e686c1..22cf77297d575dc90975ed88185ca947a886cf95 100644 (file)
@@ -9,20 +9,18 @@ Once :: struct {
 
 #overload
 Once.exec :: (o: ^Once, f: () -> $R) {
+    scoped_mutex(^o.mutex);
     if o.done do return;
 
-    mutex_lock(^o.mutex);
     o.done = true;
     f();
-    mutex_unlock(^o.mutex);
 }
 
 #overload
 Once.exec :: (o: ^Once, ctx: $Ctx, f: (Ctx) -> $R) {
+    scoped_mutex(^o.mutex);
     if o.done do return;
 
-    mutex_lock(^o.mutex);
     o.done = true;
     f(ctx);
-    mutex_unlock(^o.mutex);
 }
index af36ece6da740aa5129f47a28fa5b73455ef54a3..7b1253bf55109ec58971cc68ccc3c4c0c17c84cc 100644 (file)
@@ -129,7 +129,7 @@ run_init_command :: (args: [] cstr) {
     }
 
     // @TODO // Validation for these fields.
-    r := io.reader_make(^stdio_stream);
+    r := io.reader_make(^stdio.stream);
     read_field("Package name: ", ^config.metadata.name, "");
     read_field("Package description: ", ^config.metadata.description, "");
     read_field("Package url: ", ^config.metadata.url, "");
@@ -374,7 +374,7 @@ run_publish_command :: (args: [] cstr) {
         return;
     }
 
-    r := io.reader_make(^stdio_stream);
+    r := io.reader_make(^stdio.stream);
 
     while true {
         printf("Is this a m[a]jor, m[i]nor, or [p]atch release? or [c]ancel? (a/i/p/c) ");