From: Brendan Hansen Date: Mon, 13 Feb 2023 17:31:58 +0000 (-0600) Subject: documented more of the standard library X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=936b29d96cc4b86ff19b800cc4b73514a6efea29;p=onyx.git documented more of the standard library --- diff --git a/core/container/map.onyx b/core/container/map.onyx index 3d2bd202..e1a20f8a 100644 --- a/core/container/map.onyx +++ b/core/container/map.onyx @@ -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 diff --git a/core/conv/format.onyx b/core/conv/format.onyx index a9c5269c..388254e4 100644 --- a/core/conv/format.onyx +++ b/core/conv/format.onyx @@ -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; diff --git a/core/conv/parse.onyx b/core/conv/parse.onyx index 0ecf2d41..bb1d0a16 100644 --- a/core/conv/parse.onyx +++ b/core/conv/parse.onyx @@ -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; diff --git a/core/hash/hash.onyx b/core/hash/hash.onyx index 2a438c41..739a79b2 100644 --- a/core/hash/hash.onyx +++ b/core/hash/hash.onyx @@ -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; } diff --git a/core/io/stdio.onyx b/core/io/stdio.onyx index 6b7174f0..42355ee3 100644 --- a/core/io/stdio.onyx +++ b/core/io/stdio.onyx @@ -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(); diff --git a/core/math/math.onyx b/core/math/math.onyx index 73a829e3..74d87e0b 100644 --- a/core/math/math.onyx +++ b/core/math/math.onyx @@ -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, diff --git a/core/memory/memory.onyx b/core/memory/memory.onyx index a3ca6e70..a5537a99 100644 --- a/core/memory/memory.onyx +++ b/core/memory/memory.onyx @@ -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); } + diff --git a/core/misc/any_utils.onyx b/core/misc/any_utils.onyx index a0019143..97423ee8 100644 --- a/core/misc/any_utils.onyx +++ b/core/misc/any_utils.onyx @@ -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); diff --git a/core/sync/once.onyx b/core/sync/once.onyx index 9c87fd09..22cf7729 100644 --- a/core/sync/once.onyx +++ b/core/sync/once.onyx @@ -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); } diff --git a/scripts/onyx-pkg.onyx b/scripts/onyx-pkg.onyx index af36ece6..7b1253bf 100644 --- a/scripts/onyx-pkg.onyx +++ b/scripts/onyx-pkg.onyx @@ -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) ");