From: Brendan Hansen Date: Thu, 3 Nov 2022 02:14:02 +0000 (-0500) Subject: organized core libraries; bugfixes X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=53c5d07915357128dec7e55126d582d653a23fc3;p=onyx.git organized core libraries; bugfixes --- diff --git a/build.sh b/build.sh index 8cc45b8d..5fa26f96 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,7 @@ . ./settings.sh echo "Installing core libs" +[ -d "$CORE_DIR/core" ] && sudo rm -r "$CORE_DIR/core" sudo mkdir -p "$CORE_DIR" sudo cp -r ./core/ "$CORE_DIR" diff --git a/core/alloc.onyx b/core/alloc.onyx deleted file mode 100644 index eb2dc291..00000000 --- a/core/alloc.onyx +++ /dev/null @@ -1,47 +0,0 @@ -package core.alloc - -#load "./alloc/arena" -#load "./alloc/fixed" -#load "./alloc/heap" -#load "./alloc/ring" -#load "./alloc/pool" -#load "./alloc/logging" -#load "./alloc/gc" - -as_allocator :: #match { - macro (a: Allocator) => a -} - -// This is similar to alloca in C. -from_stack :: macro (size: u32) -> rawptr { - // This should do something about the alignment... - // Everything so far has assume that the stack is aligned to 16 bytes. - defer __stack_top = ~~(cast(^u8) __stack_top + size); - return __stack_top; -} - -array_from_stack :: macro ($T: type_expr, size: u32) -> [] T { - defer __stack_top = ~~(cast(^u8) __stack_top + size * sizeof T); - return (cast(^T) __stack_top)[0 .. size]; -} - -TEMPORARY_ALLOCATOR_SIZE :: 1 << 16; // 16Kb - -// The global heap allocator, set up upon program intialization. -heap_allocator : Allocator; - -// The global temp allocator, set up upon program intialization. -#local #thread_local -temp_state : arena.ArenaState; - -#thread_local -temp_allocator : Allocator; - -init_temp_allocator :: () { - temp_state = arena.make(heap_allocator, TEMPORARY_ALLOCATOR_SIZE); - temp_allocator = as_allocator(^temp_state); -} - -clear_temp_allocator :: () { - arena.clear(^temp_state); -} diff --git a/core/alloc/alloc.onyx b/core/alloc/alloc.onyx new file mode 100644 index 00000000..13550f78 --- /dev/null +++ b/core/alloc/alloc.onyx @@ -0,0 +1,47 @@ +package core.alloc + +#load "./arena" +#load "./fixed" +#load "./heap" +#load "./ring" +#load "./pool" +#load "./logging" +#load "./gc" + +as_allocator :: #match { + macro (a: Allocator) => a +} + +// This is similar to alloca in C. +from_stack :: macro (size: u32) -> rawptr { + // This should do something about the alignment... + // Everything so far has assume that the stack is aligned to 16 bytes. + defer __stack_top = ~~(cast(^u8) __stack_top + size); + return __stack_top; +} + +array_from_stack :: macro ($T: type_expr, size: u32) -> [] T { + defer __stack_top = ~~(cast(^u8) __stack_top + size * sizeof T); + return (cast(^T) __stack_top)[0 .. size]; +} + +TEMPORARY_ALLOCATOR_SIZE :: 1 << 16; // 16Kb + +// The global heap allocator, set up upon program intialization. +heap_allocator : Allocator; + +// The global temp allocator, set up upon program intialization. +#local #thread_local +temp_state : arena.ArenaState; + +#thread_local +temp_allocator : Allocator; + +init_temp_allocator :: () { + temp_state = arena.make(heap_allocator, TEMPORARY_ALLOCATOR_SIZE); + temp_allocator = as_allocator(^temp_state); +} + +clear_temp_allocator :: () { + arena.clear(^temp_state); +} diff --git a/core/arg_parse.onyx b/core/arg_parse.onyx deleted file mode 100644 index cde4b24e..00000000 --- a/core/arg_parse.onyx +++ /dev/null @@ -1,58 +0,0 @@ -package core.arg_parse - -use core - -arg_parse :: (c_args: [] cstr, output: any) -> bool { - arg_iter := iter.as_iterator(c_args) - |> iter.map((x) => string.from_cstr(*x)); - defer arg_iter.close(arg_iter.data); - - use runtime.info; - - ptr_type := cast(^Type_Info_Pointer) get_type_info(output.type); - if ptr_type.kind != .Pointer do return false; - - arg_type := cast(^Type_Info_Struct) get_type_info(ptr_type.to); - if arg_type.kind != .Struct do return false; - - data_base := *cast(^rawptr) output.data; - - for #no_close arg: arg_iter { - for ^member: arg_type.members { - for ^tag: member.tags { - if tag.type != str do continue; - - to_match := *cast(^str) tag.data; - if arg != to_match do continue; - - switch member.type { - case bool { - *(cast(^bool) (cast(^u8) data_base + member.offset)) = !*(cast(^bool) (cast(^u8) data_base + member.offset)); - } - - case i32 { - value_str, success := iter.take_one(arg_iter, no_close=true); - if !success do return false; - - value := conv.str_to_i64(value_str); - *(cast(^i32) (cast(^u8) data_base + member.offset)) = ~~value; - } - - case str { - value, success := iter.take_one(arg_iter, no_close=true); - if !success do return false; - - *(cast(^str) (cast(^u8) data_base + member.offset)) = value; - } - - case #default { - printf("Unsupported argument type, {}.\n", output.type); - return false; - } - } - } - } - } - - return true; -} \ No newline at end of file diff --git a/core/container/map.onyx b/core/container/map.onyx index 618e8fff..a372c3a3 100644 --- a/core/container/map.onyx +++ b/core/container/map.onyx @@ -149,11 +149,21 @@ empty :: (use map: ^Map) -> bool { } format_map :: (output: ^conv.Format_Output, format: ^conv.Format, x: ^Map($K, $V)) { - output->write("{\n"); - for^ x.entries { - conv.format(output, " {\"p} => {\"p}\n", it.key, it.value); + if format.pretty_printing { + output->write("{\n"); + for^ x.entries { + conv.format(output, " {\"p} => {\"p}\n", it.key, it.value); + } + output->write("}"); + + } else { + output->write("{ "); + for^ x.entries { + if !#first do output->write(", "); + conv.format(output, "{\"p} => {\"p}", it.key, it.value); + } + output->write(" }"); } - output->write("}"); } // diff --git a/core/conv.onyx b/core/conv.onyx deleted file mode 100644 index ef5c4768..00000000 --- a/core/conv.onyx +++ /dev/null @@ -1,992 +0,0 @@ -package core.conv - -Enable_Custom_Formatters :: true - -#local { - use core {map, string, array, math} - - custom_formatters: Map(type_expr, #type (^Format_Output, ^Format, rawptr) -> void); - custom_parsers : Map(type_expr, #type (rawptr, str, Allocator) -> bool); -} - -custom_formatters_initialized :: #init () { - map.init(^custom_formatters, default=null_proc); - map.init(^custom_parsers, default=null_proc); - - #if Enable_Custom_Formatters { - use package runtime.info; - - for type_idx: type_table.count { - type := type_table[type_idx]; - if type.kind != .Struct do continue; - - s_info := cast(^Type_Info_Struct) type; - for s_info.tags { - if it.type == Custom_Format { - custom_format := cast(^Custom_Format) it.data; - custom_formatters[cast(type_expr) type_idx] = custom_format.format; - } - - if it.type == Custom_Parse { - custom_parse := cast(^Custom_Parse) it.data; - custom_parsers[cast(type_expr) type_idx] = custom_parse.parse; - } - } - } - } -} - -register_custom_formatter :: (formatter: (^Format_Output, ^Format, ^$T) -> void) { - custom_formatters[T] = formatter; -} - -register_custom_parser :: (parser: (^$T, str, Allocator) -> bool) { - custom_parsers[T] = parser; -} - -Custom_Format :: struct { - format: (^Format_Output, ^Format, rawptr) -> void; -} - -Custom_Parse :: struct { - parse: (rawptr, str, Allocator) -> bool; -} - -str_to_i64 :: #match #local {} - -#overload -str_to_i64 :: macro (s: str, base: u32 = 10) -> i64 { - str_to_i64 :: str_to_i64; - s_ := s; - return str_to_i64(^s_, base); -} - -#overload -str_to_i64 :: (s: ^str, base: u32 = 10) -> i64 { - use package core - - value: i64 = 0; - mul := 1; - - if s.data[0] == #char "-" { - mul = -1; - string.advance(s, 1); - } - - if s.data[0] == #char "+" { - string.advance(s, 1); - } - - while !string.empty(*s) { - switch c := s.data[0]; c { - case #char "0" .. #char "9" { - value *= ~~base; - value += ~~(c - #char "0"); - } - - case #char "A" .. #char "Z" { - if base <= 10 do fallthrough; - - value *= ~~base; - value += ~~((c - #char "A") + 10); - } - - case #char "a" .. #char "z" { - if base <= 10 do fallthrough; - - value *= ~~base; - value += ~~((c - #char "a") + 10); - } - - case #default do break break; - } - - string.advance(s); - } - - return value * ~~mul; -} - -str_to_f64 :: #match #local {} - -#overload -str_to_f64 :: macro (s: str) -> f64 { - str_to_f64 :: str_to_f64; - s_ := s; - return str_to_f64(^s_); -} - -#overload -str_to_f64 :: (s: ^str) -> f64 { - use package core - - string.strip_leading_whitespace(s); - - sign := parse_sign(s); - value, _ := parse_digits(s); - - if s.data[0] == #char "." { - string.advance(s, 1); - fraction, fraction_digits := parse_digits(s); - while fraction_digits > 0 { - fraction_digits -= 1; - fraction /= 10; - } - value += fraction; - } - - value *= sign; - - if s.data[0] != #char "e" && s.data[0] != #char "E" do return value; - string.advance(s, 1); - - exponent_sign := parse_sign(s); - exponent, _ := parse_digits(s); - if exponent_sign > 0 { - while exponent > 0 { - value *= 10; - exponent -= 1; - } - } else { - while exponent > 0 { - value /= 10; - exponent -= 1; - } - } - - return value; - - - parse_sign :: (s: ^str) -> f64 { - switch s.data[0] { - case #char "-" { string.advance(s, 1); return -1; } - case #char "+" { string.advance(s, 1); return 1; } - case #default { return 1; } - } - } - - parse_digits :: (s: ^str) -> (f64, digit_count: i32) { - value: f64 = 0; - count := 0; - while s.count > 0 do switch s.data[0] { - case #char "0" .. #char "9" { - value = value * 10 + ~~cast(i32)(s.data[0] - #char "0"); - string.advance(s, 1); - count += 1; - } - - case #default do break break; - } - return value, count; - } -} - -i64_to_str :: (n: i64, base: u64, buf: [] u8, min_length := 0, prefix := false) -> str { - is_neg := false; - if n < 0 && base == 10 { - is_neg = true; - n = -n; - } - - c := ^buf[buf.count - 1]; - len := 0; - - BASE64_MAP := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; - - while n > 0 { - m := cast(u64) n % base; - - *c = BASE64_MAP[cast(u32) m]; - len += 1; - c -= 1; - - n /= base; - - } else { - *c = #char "0"; - len += 1; - c -= 1; - } - - if min_length > 0 && len < min_length { - for i: min_length - len { - *c = #char "0"; - len += 1; - c -= 1; - } - } - - if prefix { - if base == 16 { - *c = #char "x"; - len += 1; - c -= 1; - *c = #char "0"; - len += 1; - c -= 1; - } - - if base == 2 { - *c = #char "b"; - len += 1; - c -= 1; - *c = #char "0"; - len += 1; - c -= 1; - } - } - - if is_neg { - *c = #char "-"; - len += 1; - c -= 1; - } - - return str.{ data = c + 1, count = len }; -} - -u64_to_str :: (n: u64, base: u64, buf: [] u8, min_length := 0, prefix := false) -> str { - c := ^buf[buf.count - 1]; - len := 0; - - BASE64_MAP := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; - - while n > 0 { - m := cast(u64) n % base; - - *c = BASE64_MAP[cast(u32) m]; - len += 1; - c -= 1; - - n /= base; - - } else { - *c = #char "0"; - len += 1; - c -= 1; - } - - if min_length > 0 && len < min_length { - for i: min_length - len { - *c = #char "0"; - len += 1; - c -= 1; - } - } - - if prefix { - if base == 16 { - *c = #char "x"; - len += 1; - c -= 1; - *c = #char "0"; - len += 1; - c -= 1; - } - - if base == 2 { - *c = #char "b"; - len += 1; - c -= 1; - *c = #char "0"; - len += 1; - c -= 1; - } - } - - return str.{ data = c + 1, count = len }; -} - -// This is better than what used to be, but still relies on converting the integer -// part of the float to an integer, which could overflow. -f64_to_str :: (f: f64, buf: [] u8, digits_after_decimal := 4) -> str { - if math.is_nan(f) { - return format(buf, "NaN"); - } - - if math.is_inf(f) { - if f > 0 do return format(buf, "Inf"); - else do return format(buf, "-Inf"); - } - - len := 0; - - if f < 0 { - f = -f; - buf[0] = #char "-"; - len += 1; - } - - dec_part := f - math.trunc(f); - int_part := f - dec_part; - dec_part = math.abs(dec_part); - - s1 := i64_to_str(~~int_part, 10, buf); - for i: 0 .. s1.count do buf.data[i + len] = s1.data[i]; - buf.data[s1.count + len] = #char "."; - len += s1.count + 1; - - digits := "0123456789"; - - for i: digits_after_decimal { - dec_part *= 10; - v := math.trunc(dec_part); - dec_part -= v; - - buf.data[len + i] = digits[cast(i32) v]; - } - len += digits_after_decimal; - - return str.{ buf.data, len }; -} - -// @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; -} - -Format_Output :: struct { - data: ^u8; - count: u32; - capacity: u32; - - // When the data buffer fills, this procedure - // is called with the data, allowing for the - // buffer to be cleared and more to be written. - flush: Format_Flush_Callback; - - write :: #match { - (use output: ^Format_Output, c: u8) { - if count >= capacity { - if flush.func == null_proc do return; - if !flush.func(flush.data, data[0 .. count]) do return; - count = 0; - } - - data[count] = c; - count += 1; - }, - - (use output: ^Format_Output, s: str) { - for c: s { - if count >= capacity { - if flush.func == null_proc do return; - if !flush.func(flush.data, data[0 .. count]) do return; - count = 0; - } - - data[count] = c; - count += 1; - } - } - } -} - -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; - - indentation := cast(u32) 0; - base := cast(u64) 10; - minimum_width := cast(u32) 0; -} - -#local -flush_to_dynstr :: (dynstr: ^[..] u8, to_write: str) => { - array.concat(dynstr, to_write); - return true; -} - - -format :: #match {} -#match format (buffer: [] u8, format: str, va: ..any) -> str { - return format_va(buffer, format, ~~va); -} -#match format (output: ^Format_Output, format: str, va: ..any) -> str { - return format_va(output, format, ~~va); -} -#match format (buffer: ^[..] u8, format: str, va: ..any) { - buffer.count = buffer.capacity; - out := format_va(*buffer, format, ~~va); - buffer.count = out.count; -} -#match format (format: str, va: ..any) -> str { - buffer : [256] u8; - out := make([..] u8); - output := Format_Output.{ - ~~buffer, 0, buffer.count, - flush=.{ ^out, flush_to_dynstr } - }; - - final := format_va(^output, format, ~~va); - array.concat(^out, final); - - return out; -} - -format_va :: #match {} -#match format_va (buffer: [] u8, format: str, va: [] any, flush := Format_Flush_Callback.{}) -> str { - output := Format_Output.{ buffer.data, 0, buffer.count, flush }; - return format_va(^output, format, va); -} -#match format_va (buffer: ^[..] u8, format: str, va: [] any, flush := Format_Flush_Callback.{}) { - buffer.count = buffer.capacity; - out := format_va(*buffer, format, va, flush); - buffer.count = out.count; -} -#match format_va (format: [] u8, va: [] any, allocator := context.allocator) -> str { - buffer : [256] u8; - out := make([..] u8, allocator=allocator); - output := Format_Output.{ - ~~buffer, 0, buffer.count, - flush=.{ ^out, flush_to_dynstr } - }; - - final := format_va(^output, format, ~~va); - array.concat(^out, final); - - return out; -} - -#match format_va (output: ^Format_Output, format: str, va: [] any) -> str { - vararg_index := 0; - - while i := 0; i < format.count { - defer i += 1; - - ch := format[i]; - formatting := Format.{}; - - if ch == #char "{" { - if format[i + 1] == #char "{" { - output->write(#char "{"); - i += 1; - continue; - } - - i += 1; - while true { - ch = format[i]; - - switch ch { - case #char "*" { - i += 1; - formatting.dereference = true; - } - - case #char "." { - i += 1; - - digits := 0; - while format[i] >= #char "0" && format[i] <= #char "9" { - digits *= 10; - digits += ~~(format[i] - #char "0"); - i += 1; - } - - ch = format[i]; - formatting.digits_after_decimal = digits; - } - - case #char "p" { - i += 1; - formatting.pretty_printing = true; - } - - case #char "x" { - i += 1; - formatting.base = 16; - } - - case #char "b" { - i += 1; - - digits := 0; - while format[i] >= #char "0" && format[i] <= #char "9" { - digits *= 10; - digits += ~~(format[i] - #char "0"); - i += 1; - } - - formatting.base = ~~digits; - } - - case #char "w" { - i += 1; - - digits := 0; - while format[i] >= #char "0" && format[i] <= #char "9" { - digits *= 10; - digits += ~~(format[i] - #char "0"); - i += 1; - } - - formatting.minimum_width = ~~digits; - } - - case #char "!" { - i += 1; - formatting.custom_format = false; - } - - case #char "\"" { - i += 1; - formatting.quote_strings = true; - } - - case #char "'" { - i += 1; - formatting.single_quote_strings = true; - } - - case #char "d" { - i += 1; - formatting.interpret_numbers = false; - } - - case #char "}" { - arg := va[vararg_index]; - vararg_index += 1; - format_any(output, ^formatting, arg); - - break break; - } - - case #default do break break; - } - } - } - - if ch == #char "}" { - if format[i + 1] == #char "}" { - output->write(#char "}"); - i += 1; - continue; - } - - continue; - } - - output->write(ch); - } - - return .{ output.data, output.count }; -} - -format_any :: (output: ^Format_Output, formatting: ^Format, v: any) { - use package runtime.info - array :: package core.array; - - if formatting.dereference { - ti := get_type_info(v.type); - if ti.kind == .Pointer { - formatting.dereference = false; - - new_any: any; - new_any.type = (cast(^Type_Info_Pointer) ti).to; - new_any.data = *(cast(^rawptr) v.data); - format_any(output, formatting, new_any); - return; - } - } - - if formatting.custom_format && custom_formatters->has(v.type) { - custom_formatters[v.type](output, formatting, v.data); - return; - } - - switch v.type { - case bool { - value := *(cast(^bool) v.data); - if value do output->write("true"); - else do output->write("false"); - } - - case u8 { - value := *(cast(^u8) v.data); - - if value > 31 { - output->write(value); - - } else { - ibuf : [128] u8; - istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true); - output->write(istr); - } - } - - int_case :: macro (T: type_expr) { - case T { - value := *(cast(^T) v.data); - - ibuf : [128] u8; - istr := i64_to_str(~~value, formatting.base, ~~ibuf, min_length=formatting.minimum_width); - output->write(istr); - } - } - - uint_case :: macro (T: type_expr) { - case T { - value := *(cast(^T) v.data); - - ibuf : [128] u8; - istr := u64_to_str(~~value, formatting.base, ~~ibuf, min_length=formatting.minimum_width); - output->write(istr); - } - } - - int_case(i8); - int_case(i16); - int_case(i32); - int_case(i64); - uint_case(u16); - uint_case(u32); - uint_case(u64); - - case f32 { - value := *(cast(^f32) v.data); - - fbuf : [128] u8; - fstr := f64_to_str(~~value, ~~fbuf, formatting.digits_after_decimal); - output->write(fstr); - } - - case f64 { - value := *(cast(^f64) v.data); - - fbuf : [128] u8; - fstr := f64_to_str(~~value, ~~fbuf, formatting.digits_after_decimal); - output->write(fstr); - } - - case str { - if formatting.quote_strings do output->write("\""); - if formatting.single_quote_strings do output->write("'"); - width := formatting.minimum_width; - to_output := *cast(^str) v.data; - - // @Todo // escape '"' when quote_strings is enabled. - output->write(to_output); - if to_output.count < width && !(formatting.quote_strings || formatting.single_quote_strings) { - for width - to_output.count do output->write(#char " "); - } - - if formatting.quote_strings do output->write("\""); - if formatting.single_quote_strings do output->write("'"); - } - - case rawptr { - value := *(cast(^rawptr) v.data); - - if value == null { - output->write("(null)"); - } else { - ibuf : [128] u8; - istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true); - output->write(istr); - } - } - - case type_expr { - value := *(cast(^type_expr) v.data); - - io :: package core.io - - buf : [256] u8; - - // This is a little gross but the only way to output the type name for a type_expr - // is through a io.Writer. That should maybe be changed in the future? Also, I think - // 256 bytes is enough for the name of a type but I'm not entirely sure... - stream := io.buffer_stream_make(~~buf, fixed=true); - writer := io.writer_make(^stream); - write_type_name(^writer, value); - - output->write(io.buffer_stream_to_str(^stream)); - } - - case #default { - info := get_type_info(v.type); - - if info.kind == .Struct { - s := cast(^Type_Info_Struct) info; - - if s.name.count > 0 { - output->write(s.name); - output->write(" { "); - } else { - output->write("{ "); - } - - { - format := *formatting; - format.quote_strings = true; - if format.pretty_printing { - format.indentation += 4; - } - - for ^member: s.members { - if member != s.members.data do output->write(", "); - - if formatting.pretty_printing { - output->write(#char "\n"); - for i: format.indentation do output->write(#char " "); - } - - output->write(member.name); - output->write(" = "); - - format_any(output, ^format, .{ ~~(cast(^u8) v.data + member.offset), member.type }); - } - } - - if formatting.pretty_printing { - output->write(#char "\n"); - for i: formatting.indentation do output->write(#char " "); - output->write("}"); - - } else { - output->write(" }"); - } - } - - if info.kind == .Function { - output->write("func["); - - value := *(cast(^i32) v.data); - - ibuf : [128] u8; - istr := i64_to_str(~~value, 10, ~~ibuf); - output->write(istr); - - output->write("]"); - } - - if info.kind == .Pointer { - value := *(cast(^rawptr) v.data); - - ibuf : [128] u8; - istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true); - output->write(istr); - } - - // This assumes that the following type_info kinds are basically the same. - if info.kind == .Dynamic_Array || info.kind == .Slice || info.kind == .Variadic_Argument { - if formatting.pretty_printing { - output->write("["); - } else { - output->write("[ "); - } - - a := cast(^Type_Info_Dynamic_Array) info; - arr := cast(^array.Untyped_Array) v.data; - data := arr.data; - count := arr.count; - - format := *formatting; - format.quote_strings = true; - if format.pretty_printing do format.indentation += 4; - - for i: count { - if i != 0 do output->write(", "); - - if formatting.pretty_printing { - output->write("\n"); - for _: format.indentation do output->write(#char " "); - } - - format_any(output, ^format, .{ ~~(cast(^u8) data + get_type_info(a.of).size * i), a.of }); - } - - - if formatting.pretty_printing { - format.indentation -= 4; - output->write("\n"); - for _: format.indentation do output->write(#char " "); - output->write(#char "]"); - - } else { - output->write(" ]"); - } - } - - if info.kind == .Array { - output->write("[ "); - - a := cast(^Type_Info_Array) info; - data := v.data; - - for i: a.count { - if i != 0 do output->write(", "); - - format_any(output, formatting, .{ ~~(cast(^u8) data + get_type_info(a.of).size * i), a.of }); - } - - output->write(" ]"); - } - - if info.kind == .Enum { - e := cast(^Type_Info_Enum) info; - - value: u64; - switch e.backing_type { - case i8, u8 do value = cast(u64) *(cast(^u8) v.data); - case i16, u16 do value = cast(u64) *(cast(^u16) v.data); - case i32, u32 do value = cast(u64) *(cast(^u32) v.data); - case i64, u64 do value = cast(u64) *(cast(^u64) v.data); - case #default do assert(false, "Bad enum backing type"); - } - - if !formatting.interpret_numbers { - format_any(output, formatting, .{^value, u64}); - break; - } - - if !e.is_flags { - for ^member: e.members { - if value == member.value { - output->write(member.name); - break break; - } - } - - output->write("UNKNOWN"); - - } else { - first := true; - for ^member: e.members { - if value & member.value != 0 { - if !first do output->write(" | "); - output->write(member.name); - first = false; - } - } - - if first { - output->write("None"); - } - } - } - - if info.kind == .Distinct { - d := cast(^Type_Info_Distinct) info; - - if formatting.interpret_numbers { - output->write(d.name); - output->write("["); - } - - format_any(output, formatting, any.{ v.data, d.base_type }); - - if formatting.interpret_numbers { - output->write("]"); - } - } - } - } -} - -// -// This should be called with a pointer for the first argument. -// -// x: i32; -// parse_any(^x, "12.34"); -parse_any :: #match {} -#match parse_any (v: any, to_parse: str, string_allocator := context.allocator) -> bool { - use package runtime.info; - - info := get_type_info(v.type); - if info.kind != .Pointer do return false; - - data_type := (cast(^Type_Info_Pointer) info).to; - target := *cast(^rawptr) v.data; - return parse_any(target, data_type, to_parse, string_allocator); -} - -#match parse_any (target: rawptr, data_type: type_expr, to_parse: str, string_allocator := context.allocator) -> bool { - if custom_parsers->has(data_type) { - return custom_parsers[data_type](target, to_parse, string_allocator); - } - - use runtime.info; - info := get_type_info(data_type); - - switch data_type { - case bool { - dest := cast(^bool) target; - *dest = false; - if to_parse[0] == #char "t" || to_parse[0] == #char "T" { - *dest = true; - } - return true; - } - - integer_case :: macro (T: type_expr) { - case T { - dest := cast(^T) target; - *dest = cast(T) str_to_i64(to_parse); - return true; - } - } - - integer_case(i8); - integer_case(i16); - integer_case(i32); - integer_case(i64); - integer_case(u8); - integer_case(u16); - integer_case(u32); - integer_case(u64); - - case f32 { - dest := cast(^f32) target; - *dest = ~~ str_to_f64(to_parse); - return true; - } - - case f64 { - dest := cast(^f64) target; - *dest = ~~ str_to_f64(to_parse); - return true; - } - - case str { - if to_parse.count == 0 do return false; - if to_parse[0] != #char "\"" do return false; - 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; - } - - case #default { - if info.kind == .Enum { - // TEMPORARY this needs to look at the backing type for the - // enum in order to know how large this integer should be. - *cast(^u32) target = ~~ str_to_i64(to_parse); - return true; - } - - if info.kind == .Distinct { - d_info := cast(^Type_Info_Distinct) info; - return parse_any(target, d_info.base_type, to_parse, string_allocator); - } - } - } - - return false; -} - - diff --git a/core/conv/conv.onyx b/core/conv/conv.onyx new file mode 100644 index 00000000..2d475517 --- /dev/null +++ b/core/conv/conv.onyx @@ -0,0 +1,295 @@ +package core.conv + +Enable_Custom_Formatters :: true + +use core {map, string, array, math} + +str_to_i64 :: #match #local {} + +#overload +str_to_i64 :: macro (s: str, base: u32 = 10) -> i64 { + str_to_i64 :: str_to_i64; + s_ := s; + return str_to_i64(^s_, base); +} + +#overload +str_to_i64 :: (s: ^str, base: u32 = 10) -> i64 { + use package core + + value: i64 = 0; + mul := 1; + + if s.data[0] == #char "-" { + mul = -1; + string.advance(s, 1); + } + + if s.data[0] == #char "+" { + string.advance(s, 1); + } + + while !string.empty(*s) { + switch c := s.data[0]; c { + case #char "0" .. #char "9" { + value *= ~~base; + value += ~~(c - #char "0"); + } + + case #char "A" .. #char "Z" { + if base <= 10 do fallthrough; + + value *= ~~base; + value += ~~((c - #char "A") + 10); + } + + case #char "a" .. #char "z" { + if base <= 10 do fallthrough; + + value *= ~~base; + value += ~~((c - #char "a") + 10); + } + + case #default do break break; + } + + string.advance(s); + } + + return value * ~~mul; +} + +str_to_f64 :: #match #local {} + +#overload +str_to_f64 :: macro (s: str) -> f64 { + str_to_f64 :: str_to_f64; + s_ := s; + return str_to_f64(^s_); +} + +#overload +str_to_f64 :: (s: ^str) -> f64 { + use package core + + string.strip_leading_whitespace(s); + + sign := parse_sign(s); + value, _ := parse_digits(s); + + if s.data[0] == #char "." { + string.advance(s, 1); + fraction, fraction_digits := parse_digits(s); + while fraction_digits > 0 { + fraction_digits -= 1; + fraction /= 10; + } + value += fraction; + } + + value *= sign; + + if s.data[0] != #char "e" && s.data[0] != #char "E" do return value; + string.advance(s, 1); + + exponent_sign := parse_sign(s); + exponent, _ := parse_digits(s); + if exponent_sign > 0 { + while exponent > 0 { + value *= 10; + exponent -= 1; + } + } else { + while exponent > 0 { + value /= 10; + exponent -= 1; + } + } + + return value; + + + parse_sign :: (s: ^str) -> f64 { + switch s.data[0] { + case #char "-" { string.advance(s, 1); return -1; } + case #char "+" { string.advance(s, 1); return 1; } + case #default { return 1; } + } + } + + parse_digits :: (s: ^str) -> (f64, digit_count: i32) { + value: f64 = 0; + count := 0; + while s.count > 0 do switch s.data[0] { + case #char "0" .. #char "9" { + value = value * 10 + ~~cast(i32)(s.data[0] - #char "0"); + string.advance(s, 1); + count += 1; + } + + case #default do break break; + } + return value, count; + } +} + +i64_to_str :: (n: i64, base: u64, buf: [] u8, min_length := 0, prefix := false) -> str { + is_neg := false; + if n < 0 && base == 10 { + is_neg = true; + n = -n; + } + + c := ^buf[buf.count - 1]; + len := 0; + + BASE64_MAP := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; + + while n > 0 { + m := cast(u64) n % base; + + *c = BASE64_MAP[cast(u32) m]; + len += 1; + c -= 1; + + n /= base; + + } else { + *c = #char "0"; + len += 1; + c -= 1; + } + + if min_length > 0 && len < min_length { + for i: min_length - len { + *c = #char "0"; + len += 1; + c -= 1; + } + } + + if prefix { + if base == 16 { + *c = #char "x"; + len += 1; + c -= 1; + *c = #char "0"; + len += 1; + c -= 1; + } + + if base == 2 { + *c = #char "b"; + len += 1; + c -= 1; + *c = #char "0"; + len += 1; + c -= 1; + } + } + + if is_neg { + *c = #char "-"; + len += 1; + c -= 1; + } + + return str.{ data = c + 1, count = len }; +} + +u64_to_str :: (n: u64, base: u64, buf: [] u8, min_length := 0, prefix := false) -> str { + c := ^buf[buf.count - 1]; + len := 0; + + BASE64_MAP := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; + + while n > 0 { + m := cast(u64) n % base; + + *c = BASE64_MAP[cast(u32) m]; + len += 1; + c -= 1; + + n /= base; + + } else { + *c = #char "0"; + len += 1; + c -= 1; + } + + if min_length > 0 && len < min_length { + for i: min_length - len { + *c = #char "0"; + len += 1; + c -= 1; + } + } + + if prefix { + if base == 16 { + *c = #char "x"; + len += 1; + c -= 1; + *c = #char "0"; + len += 1; + c -= 1; + } + + if base == 2 { + *c = #char "b"; + len += 1; + c -= 1; + *c = #char "0"; + len += 1; + c -= 1; + } + } + + return str.{ data = c + 1, count = len }; +} + +// This is better than what used to be, but still relies on converting the integer +// part of the float to an integer, which could overflow. +f64_to_str :: (f: f64, buf: [] u8, digits_after_decimal := 4) -> str { + if math.is_nan(f) { + return format(buf, "NaN"); + } + + if math.is_inf(f) { + if f > 0 do return format(buf, "Inf"); + else do return format(buf, "-Inf"); + } + + len := 0; + + if f < 0 { + f = -f; + buf[0] = #char "-"; + len += 1; + } + + dec_part := f - math.trunc(f); + int_part := f - dec_part; + dec_part = math.abs(dec_part); + + s1 := i64_to_str(~~int_part, 10, buf); + for i: 0 .. s1.count do buf.data[i + len] = s1.data[i]; + buf.data[s1.count + len] = #char "."; + len += s1.count + 1; + + digits := "0123456789"; + + for i: digits_after_decimal { + dec_part *= 10; + v := math.trunc(dec_part); + dec_part -= v; + + buf.data[len + i] = digits[cast(i32) v]; + } + len += digits_after_decimal; + + return str.{ buf.data, len }; +} + + diff --git a/core/conv/format.onyx b/core/conv/format.onyx new file mode 100644 index 00000000..07002c93 --- /dev/null +++ b/core/conv/format.onyx @@ -0,0 +1,605 @@ +package core.conv + +use core {map, string, array, math} + +#package { + custom_formatters: Map(type_expr, #type (^Format_Output, ^Format, rawptr) -> void); + custom_parsers : Map(type_expr, #type (rawptr, str, Allocator) -> bool); +} + +custom_formatters_initialized :: #init () { + map.init(^custom_formatters, default=null_proc); + map.init(^custom_parsers, default=null_proc); + + #if Enable_Custom_Formatters { + use package runtime.info; + + for type_idx: type_table.count { + type := type_table[type_idx]; + if type.kind != .Struct do continue; + + s_info := cast(^Type_Info_Struct) type; + for s_info.tags { + if it.type == Custom_Format { + custom_format := cast(^Custom_Format) it.data; + custom_formatters[cast(type_expr) type_idx] = custom_format.format; + } + + if it.type == Custom_Parse { + custom_parse := cast(^Custom_Parse) it.data; + custom_parsers[cast(type_expr) type_idx] = custom_parse.parse; + } + } + } + } +} + +register_custom_formatter :: (formatter: (^Format_Output, ^Format, ^$T) -> void) { + custom_formatters[T] = formatter; +} + +register_custom_parser :: (parser: (^$T, str, Allocator) -> bool) { + custom_parsers[T] = parser; +} + +Custom_Format :: struct { + format: (^Format_Output, ^Format, rawptr) -> void; +} + +Custom_Parse :: struct { + parse: (rawptr, str, Allocator) -> bool; +} + +// @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; +} + +Format_Output :: struct { + data: ^u8; + count: u32; + capacity: u32; + + // When the data buffer fills, this procedure + // is called with the data, allowing for the + // buffer to be cleared and more to be written. + flush: Format_Flush_Callback; + + write :: #match { + (use output: ^Format_Output, c: u8) { + if count >= capacity { + if flush.func == null_proc do return; + if !flush.func(flush.data, data[0 .. count]) do return; + count = 0; + } + + data[count] = c; + count += 1; + }, + + (use output: ^Format_Output, s: str) { + for c: s { + if count >= capacity { + if flush.func == null_proc do return; + if !flush.func(flush.data, data[0 .. count]) do return; + count = 0; + } + + data[count] = c; + count += 1; + } + } + } +} + +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; + + indentation := cast(u32) 0; + base := cast(u64) 10; + minimum_width := cast(u32) 0; +} + +#local +flush_to_dynstr :: (dynstr: ^[..] u8, to_write: str) => { + array.concat(dynstr, to_write); + return true; +} + + +format :: #match {} +#match format (buffer: [] u8, format: str, va: ..any) -> str { + return format_va(buffer, format, ~~va); +} +#match format (output: ^Format_Output, format: str, va: ..any) -> str { + return format_va(output, format, ~~va); +} +#match format (buffer: ^[..] u8, format: str, va: ..any) { + buffer.count = buffer.capacity; + out := format_va(*buffer, format, ~~va); + buffer.count = out.count; +} +#match format (format: str, va: ..any) -> str { + buffer : [256] u8; + out := make([..] u8); + output := Format_Output.{ + ~~buffer, 0, buffer.count, + flush=.{ ^out, flush_to_dynstr } + }; + + final := format_va(^output, format, ~~va); + array.concat(^out, final); + + return out; +} + +format_va :: #match {} +#match format_va (buffer: [] u8, format: str, va: [] any, flush := Format_Flush_Callback.{}) -> str { + output := Format_Output.{ buffer.data, 0, buffer.count, flush }; + return format_va(^output, format, va); +} +#match format_va (buffer: ^[..] u8, format: str, va: [] any, flush := Format_Flush_Callback.{}) { + buffer.count = buffer.capacity; + out := format_va(*buffer, format, va, flush); + buffer.count = out.count; +} +#match format_va (format: [] u8, va: [] any, allocator := context.allocator) -> str { + buffer : [256] u8; + out := make([..] u8, allocator=allocator); + output := Format_Output.{ + ~~buffer, 0, buffer.count, + flush=.{ ^out, flush_to_dynstr } + }; + + final := format_va(^output, format, ~~va); + array.concat(^out, final); + + return out; +} + +#match format_va (output: ^Format_Output, format: str, va: [] any) -> str { + vararg_index := 0; + + while i := 0; i < format.count { + defer i += 1; + + ch := format[i]; + formatting := Format.{}; + + if ch == #char "{" { + if format[i + 1] == #char "{" { + output->write(#char "{"); + i += 1; + continue; + } + + i += 1; + while true { + ch = format[i]; + + switch ch { + case #char "*" { + i += 1; + formatting.dereference = true; + } + + case #char "." { + i += 1; + + digits := 0; + while format[i] >= #char "0" && format[i] <= #char "9" { + digits *= 10; + digits += ~~(format[i] - #char "0"); + i += 1; + } + + ch = format[i]; + formatting.digits_after_decimal = digits; + } + + case #char "p" { + i += 1; + formatting.pretty_printing = true; + } + + case #char "x" { + i += 1; + formatting.base = 16; + } + + case #char "b" { + i += 1; + + digits := 0; + while format[i] >= #char "0" && format[i] <= #char "9" { + digits *= 10; + digits += ~~(format[i] - #char "0"); + i += 1; + } + + formatting.base = ~~digits; + } + + case #char "w" { + i += 1; + + digits := 0; + while format[i] >= #char "0" && format[i] <= #char "9" { + digits *= 10; + digits += ~~(format[i] - #char "0"); + i += 1; + } + + formatting.minimum_width = ~~digits; + } + + case #char "!" { + i += 1; + formatting.custom_format = false; + } + + case #char "\"" { + i += 1; + formatting.quote_strings = true; + } + + case #char "'" { + i += 1; + formatting.single_quote_strings = true; + } + + case #char "d" { + i += 1; + formatting.interpret_numbers = false; + } + + case #char "}" { + arg := va[vararg_index]; + vararg_index += 1; + format_any(output, ^formatting, arg); + + break break; + } + + case #default do break break; + } + } + } + + if ch == #char "}" { + if format[i + 1] == #char "}" { + output->write(#char "}"); + i += 1; + continue; + } + + continue; + } + + output->write(ch); + } + + return .{ output.data, output.count }; +} + +format_any :: (output: ^Format_Output, formatting: ^Format, v: any) { + use package runtime.info + array :: package core.array; + + if formatting.dereference { + ti := get_type_info(v.type); + if ti.kind == .Pointer { + formatting.dereference = false; + + new_any: any; + new_any.type = (cast(^Type_Info_Pointer) ti).to; + new_any.data = *(cast(^rawptr) v.data); + format_any(output, formatting, new_any); + return; + } + } + + if formatting.custom_format && custom_formatters->has(v.type) { + custom_formatters[v.type](output, formatting, v.data); + return; + } + + switch v.type { + case bool { + value := *(cast(^bool) v.data); + if value do output->write("true"); + else do output->write("false"); + } + + case u8 { + value := *(cast(^u8) v.data); + + if value > 31 { + output->write(value); + + } else { + ibuf : [128] u8; + istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true); + output->write(istr); + } + } + + int_case :: macro (T: type_expr) { + case T { + value := *(cast(^T) v.data); + + ibuf : [128] u8; + istr := i64_to_str(~~value, formatting.base, ~~ibuf, min_length=formatting.minimum_width); + output->write(istr); + } + } + + uint_case :: macro (T: type_expr) { + case T { + value := *(cast(^T) v.data); + + ibuf : [128] u8; + istr := u64_to_str(~~value, formatting.base, ~~ibuf, min_length=formatting.minimum_width); + output->write(istr); + } + } + + int_case(i8); + int_case(i16); + int_case(i32); + int_case(i64); + uint_case(u16); + uint_case(u32); + uint_case(u64); + + case f32 { + value := *(cast(^f32) v.data); + + fbuf : [128] u8; + fstr := f64_to_str(~~value, ~~fbuf, formatting.digits_after_decimal); + output->write(fstr); + } + + case f64 { + value := *(cast(^f64) v.data); + + fbuf : [128] u8; + fstr := f64_to_str(~~value, ~~fbuf, formatting.digits_after_decimal); + output->write(fstr); + } + + case str { + if formatting.quote_strings do output->write("\""); + if formatting.single_quote_strings do output->write("'"); + width := formatting.minimum_width; + to_output := *cast(^str) v.data; + + // @Todo // escape '"' when quote_strings is enabled. + output->write(to_output); + if to_output.count < width && !(formatting.quote_strings || formatting.single_quote_strings) { + for width - to_output.count do output->write(#char " "); + } + + if formatting.quote_strings do output->write("\""); + if formatting.single_quote_strings do output->write("'"); + } + + case rawptr { + value := *(cast(^rawptr) v.data); + + if value == null { + output->write("(null)"); + } else { + ibuf : [128] u8; + istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true); + output->write(istr); + } + } + + case type_expr { + value := *(cast(^type_expr) v.data); + + io :: package core.io + + buf : [256] u8; + + // This is a little gross but the only way to output the type name for a type_expr + // is through a io.Writer. That should maybe be changed in the future? Also, I think + // 256 bytes is enough for the name of a type but I'm not entirely sure... + stream := io.buffer_stream_make(~~buf, fixed=true); + writer := io.writer_make(^stream); + write_type_name(^writer, value); + + output->write(io.buffer_stream_to_str(^stream)); + } + + case #default { + info := get_type_info(v.type); + + if info.kind == .Struct { + s := cast(^Type_Info_Struct) info; + + if s.name.count > 0 { + output->write(s.name); + output->write(" { "); + } else { + output->write("{ "); + } + + { + format := *formatting; + format.quote_strings = true; + if format.pretty_printing { + format.indentation += 4; + } + + for ^member: s.members { + if member != s.members.data do output->write(", "); + + if formatting.pretty_printing { + output->write(#char "\n"); + for i: format.indentation do output->write(#char " "); + } + + output->write(member.name); + output->write(" = "); + + format_any(output, ^format, .{ ~~(cast(^u8) v.data + member.offset), member.type }); + } + } + + if formatting.pretty_printing { + output->write(#char "\n"); + for i: formatting.indentation do output->write(#char " "); + output->write("}"); + + } else { + output->write(" }"); + } + } + + if info.kind == .Function { + output->write("func["); + + value := *(cast(^i32) v.data); + + ibuf : [128] u8; + istr := i64_to_str(~~value, 10, ~~ibuf); + output->write(istr); + + output->write("]"); + } + + if info.kind == .Pointer { + value := *(cast(^rawptr) v.data); + + ibuf : [128] u8; + istr := i64_to_str(~~value, 16, ~~ibuf, prefix=true); + output->write(istr); + } + + // This assumes that the following type_info kinds are basically the same. + if info.kind == .Dynamic_Array || info.kind == .Slice || info.kind == .Variadic_Argument { + if formatting.pretty_printing { + output->write("["); + } else { + output->write("[ "); + } + + a := cast(^Type_Info_Dynamic_Array) info; + arr := cast(^array.Untyped_Array) v.data; + data := arr.data; + count := arr.count; + + format := *formatting; + format.quote_strings = true; + if format.pretty_printing do format.indentation += 4; + + for i: count { + if i != 0 do output->write(", "); + + if formatting.pretty_printing { + output->write("\n"); + for _: format.indentation do output->write(#char " "); + } + + format_any(output, ^format, .{ ~~(cast(^u8) data + get_type_info(a.of).size * i), a.of }); + } + + + if formatting.pretty_printing { + format.indentation -= 4; + output->write("\n"); + for _: format.indentation do output->write(#char " "); + output->write(#char "]"); + + } else { + output->write(" ]"); + } + } + + if info.kind == .Array { + output->write("[ "); + + a := cast(^Type_Info_Array) info; + data := v.data; + + for i: a.count { + if i != 0 do output->write(", "); + + format_any(output, formatting, .{ ~~(cast(^u8) data + get_type_info(a.of).size * i), a.of }); + } + + output->write(" ]"); + } + + if info.kind == .Enum { + e := cast(^Type_Info_Enum) info; + + value: u64; + switch e.backing_type { + case i8, u8 do value = cast(u64) *(cast(^u8) v.data); + case i16, u16 do value = cast(u64) *(cast(^u16) v.data); + case i32, u32 do value = cast(u64) *(cast(^u32) v.data); + case i64, u64 do value = cast(u64) *(cast(^u64) v.data); + case #default do assert(false, "Bad enum backing type"); + } + + if !formatting.interpret_numbers { + format_any(output, formatting, .{^value, u64}); + break; + } + + if !e.is_flags { + for ^member: e.members { + if value == member.value { + output->write(member.name); + break break; + } + } + + output->write("UNKNOWN"); + + } else { + first := true; + for ^member: e.members { + if value & member.value != 0 { + if !first do output->write(" | "); + output->write(member.name); + first = false; + } + } + + if first { + output->write("None"); + } + } + } + + if info.kind == .Distinct { + d := cast(^Type_Info_Distinct) info; + + if formatting.interpret_numbers { + output->write(d.name); + output->write("["); + } + + format_any(output, formatting, any.{ v.data, d.base_type }); + + if formatting.interpret_numbers { + output->write("]"); + } + } + } + } +} diff --git a/core/conv/parse.onyx b/core/conv/parse.onyx new file mode 100644 index 00000000..babc51b9 --- /dev/null +++ b/core/conv/parse.onyx @@ -0,0 +1,98 @@ +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"); +parse_any :: #match {} +#match parse_any (v: any, to_parse: str, string_allocator := context.allocator) -> bool { + use package runtime.info; + + info := get_type_info(v.type); + if info.kind != .Pointer do return false; + + data_type := (cast(^Type_Info_Pointer) info).to; + target := *cast(^rawptr) v.data; + return parse_any(target, data_type, to_parse, string_allocator); +} + +#match parse_any (target: rawptr, data_type: type_expr, to_parse: str, string_allocator := context.allocator) -> bool { + if custom_parsers->has(data_type) { + return custom_parsers[data_type](target, to_parse, string_allocator); + } + + use runtime.info; + info := get_type_info(data_type); + + switch data_type { + case bool { + dest := cast(^bool) target; + *dest = false; + if to_parse[0] == #char "t" || to_parse[0] == #char "T" { + *dest = true; + } + return true; + } + + integer_case :: macro (T: type_expr) { + case T { + dest := cast(^T) target; + *dest = cast(T) str_to_i64(to_parse); + return true; + } + } + + integer_case(i8); + integer_case(i16); + integer_case(i32); + integer_case(i64); + integer_case(u8); + integer_case(u16); + integer_case(u32); + integer_case(u64); + + case f32 { + dest := cast(^f32) target; + *dest = ~~ str_to_f64(to_parse); + return true; + } + + case f64 { + dest := cast(^f64) target; + *dest = ~~ str_to_f64(to_parse); + return true; + } + + case str { + if to_parse.count == 0 do return false; + if to_parse[0] != #char "\"" do return false; + 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; + } + + case #default { + if info.kind == .Enum { + // TEMPORARY this needs to look at the backing type for the + // enum in order to know how large this integer should be. + *cast(^u32) target = ~~ str_to_i64(to_parse); + return true; + } + + if info.kind == .Distinct { + d_info := cast(^Type_Info_Distinct) info; + return parse_any(target, d_info.base_type, to_parse, string_allocator); + } + } + } + + return false; +} diff --git a/core/hash.onyx b/core/hash.onyx deleted file mode 100644 index 364ef6ae..00000000 --- a/core/hash.onyx +++ /dev/null @@ -1,28 +0,0 @@ -package core.hash - -to_u32 :: #match { - (key: rawptr) -> u32 { return 0xcbf29ce7 ^ cast(u32) key; }, - (key: i8) -> u32 { return ~~ key; }, - (key: i16) -> u32 { return 0x9ce7 ^ cast(u32) key; }, - (key: i32) -> u32 { return 0xcbf29ce7 ^ cast(u32) key; }, - (key: i64) -> u32 { return cast(u32) (cast(u64) 0xcbf29ce7 ^ cast(u64) key); }, - (key: str) -> u32 { - hash: u32 = 5381; - for ch: key do hash += (hash << 5) + ~~ch; - return hash; - }, - (key: type_expr) -> u32 { return to_u32(cast(u32) key); }, - - #precedence 10000 - macro (key: $T/HasHashMethod) => key->hash() -} - -Hashable :: interface (t: $T) { - { to_u32(t) } -> u32; -} - -#local -HasHashMethod :: interface (t: $T) { - { t->hash() } -> u32; -} - diff --git a/core/hash/hash.onyx b/core/hash/hash.onyx new file mode 100644 index 00000000..364ef6ae --- /dev/null +++ b/core/hash/hash.onyx @@ -0,0 +1,28 @@ +package core.hash + +to_u32 :: #match { + (key: rawptr) -> u32 { return 0xcbf29ce7 ^ cast(u32) key; }, + (key: i8) -> u32 { return ~~ key; }, + (key: i16) -> u32 { return 0x9ce7 ^ cast(u32) key; }, + (key: i32) -> u32 { return 0xcbf29ce7 ^ cast(u32) key; }, + (key: i64) -> u32 { return cast(u32) (cast(u64) 0xcbf29ce7 ^ cast(u64) key); }, + (key: str) -> u32 { + hash: u32 = 5381; + for ch: key do hash += (hash << 5) + ~~ch; + return hash; + }, + (key: type_expr) -> u32 { return to_u32(cast(u32) key); }, + + #precedence 10000 + macro (key: $T/HasHashMethod) => key->hash() +} + +Hashable :: interface (t: $T) { + { to_u32(t) } -> u32; +} + +#local +HasHashMethod :: interface (t: $T) { + { t->hash() } -> u32; +} + diff --git a/core/io/reader.onyx b/core/io/reader.onyx index c69aa790..f695029b 100644 --- a/core/io/reader.onyx +++ b/core/io/reader.onyx @@ -20,7 +20,9 @@ Reader :: struct { // that is available. Only set this if you know that the data // will always be available. greedy : bool; +} +#inject Reader { is_empty :: reader_empty; empty :: reader_empty; read_all :: read_all; diff --git a/core/io/stdio.onyx b/core/io/stdio.onyx new file mode 100644 index 00000000..b79f5737 --- /dev/null +++ b/core/io/stdio.onyx @@ -0,0 +1,173 @@ +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. + + +#if runtime.runtime == .Custom { + #error "'stdio' can only be included in the 'wasi' or 'js' runtime." +} + +stdio_stream: io.Stream; + +auto_flush_stdio := true + +print :: #match #locked { + (x: str) { + io.write(^stdio.print_writer, x); + if x[x.count - 1] == #char "\n" && auto_flush_stdio do __flush_stdio(); + }, + + (x) => { io.write(^stdio.print_writer, x); }, + (x, y) => { io.write(^stdio.print_writer, x, y); }, +} + +println :: (x) => { + print(x); + print("\n"); +} + +// Standard formatted print. +printf :: (format: str, va: ..any) { + flush :: (_, to_output) => { + io.write(^stdio.print_writer, to_output); + __flush_stdio(); + return true; + } + + buffer: [1024] u8; + print(conv.format_va(buffer, format, va, .{null, flush})); +} + +// Print to standard error, if available. +#if #defined(runtime.__output_error) { + eprintf :: (format: str, va: ..any) -> str { + flush :: (_, to_output) => { + runtime.__output_error(to_output); + return true; + } + + buffer: [1024] u8; + runtime.__output_error(conv.format_va(buffer, format, va, .{null, flush})); + } +} + +// Print to a dynamically allocated 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. +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) { + temp: [3] u8; + + u8_ptr := cast(^u8) ptr; + for i: byte_count { + val := u8_ptr[i]; + + temp[0] = map_to_ascii(val >> 4); + temp[1] = map_to_ascii(val & 15); + temp[2] = #char " "; + + runtime.__output_string(~~temp); + + if i % bytes_per_line == (bytes_per_line - 1) do runtime.__output_string("\n"); + } + + + map_to_ascii :: (x: u8) -> u8 { + switch x { + case 0 do return #char "0"; + case 1 do return #char "1"; + case 2 do return #char "2"; + case 3 do return #char "3"; + case 4 do return #char "4"; + case 5 do return #char "5"; + case 6 do return #char "6"; + case 7 do return #char "7"; + case 8 do return #char "8"; + case 9 do return #char "9"; + case 10 do return #char "A"; + case 11 do return #char "B"; + case 12 do return #char "C"; + case 13 do return #char "D"; + case 14 do return #char "E"; + case 15 do return #char "F"; + case #default do return #char "X"; + } + } +} + + +// +// 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); + + // This shouldn't need to be here, but because ^stdin_vtable is not a compile-time + // known value (even through it should be). + stdio_stream.vtable = ^stdio_vtable; +} + + +__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(); +} + +#local stdio_vtable := io.Stream_Vtable.{ + read = (_: ^io.Stream, buffer: [] u8) -> (io.Error, u32) { + __flush_stdio(); + bytes_read := runtime.__read_from_input(buffer); + if bytes_read == 0 do return .ReadPending, 0; + if bytes_read < 0 do return .EOF, 0; + + return .None, bytes_read; + }, + + read_byte = (_: ^io.Stream) -> (io.Error, u8) { + __flush_stdio(); + buf: [1] u8; + bytes_read := runtime.__read_from_input(buf); + if bytes_read <= 0 do return .EOF, 0; + + return .None, buf[0]; + }, + + write = (_: ^io.Stream, buffer: [] u8) -> (io.Error, u32) { + return io.stream_write(^stdio.print_stream, buffer); + }, + + write_byte = (_: ^io.Stream, byte: u8) -> io.Error { + return io.stream_write_byte(^stdio.print_stream, byte); + }, + + flush = (_: ^io.Stream) -> io.Error { + __flush_stdio(); + return .None; + } +} diff --git a/core/math.onyx b/core/math.onyx deleted file mode 100644 index 73a829e3..00000000 --- a/core/math.onyx +++ /dev/null @@ -1,398 +0,0 @@ -package core.math - -use core.intrinsics {wasm} - -// Things that are useful in any math library: -// - Trigonometry -// - modf, fmod - -// Other things that can be useful: -// - Vector math -// - Matrix math -// - Why not tensor math?? -// - Complex numbers -// - Dual numbers - - -E :: 2.71828182845904523536f; -PI :: 3.14159265f; -TAU :: 6.28318330f; -SQRT_2 :: 1.414213562f; - -// -// Trigonometry -// Basic trig functions have been implemented using taylor series approximations. The -// approximations are very accurate, but rather computationally expensive. Programs that -// rely heavily on trig functions would greatly benefit from improvements to the -// implementations of these functions. -// - -sin :: (t: f32) -> f32 { - while t >= PI do t -= TAU; - while t <= -PI do t += TAU; - - res := 0.0f; - - plus_minus := 1.0f; - n := 13; - while n > 1 { - res += plus_minus; - res *= t * t / cast(f32) (n * n - n); - - plus_minus = -plus_minus; - n -= 2; - } - - res += 1.0f; - res *= t; - return res; -} - -cos :: (t: f32) -> f32 { - while t >= PI do t -= TAU; - while t <= -PI do t += TAU; - - res := 0.0f; - - plus_minus := 1.0f; - n := 12; - while n > 1 { - res += plus_minus; - res *= t * t / cast(f32) (n * n - n); - - plus_minus = -plus_minus; - n -= 2; - } - - res += 1.0f; - return res; -} - -asin :: (t: f32) -> f32 { - assert(false, "asin is not implemented yet!"); - return 0; -} - -acos :: (t: f32) -> f32 { - assert(false, "acos is not implemented yet!"); - return 0; -} - -atan :: (t: f32) -> f32 { - assert(false, "atan is not implemented yet!"); - return 0; -} - -atan2 :: (t: f32) -> f32 { - assert(false, "atan2 is not implemented yet!"); - return 0; -} - - - - -// -// Hyperbolic trigonometry. -// The hyperbolic trigonometry functions are implemented using the naive -// definitions. There may be fancier, faster and far more precise methods -// of implementing these, but these definitions should suffice. -// - -sinh :: (t: $T) -> T { - et := exp(t); - return (et - (1 / et)) / 2; -} - -cosh :: (t: $T) -> T { - et := exp(t); - return (et + (1 / et)) / 2; -} - -tanh :: (t: $T) -> T { - et := exp(t); - one_over_et := 1 / et; - return (et - one_over_et) / (et + one_over_et); -} - -asinh :: (t: $T) -> T { - return ~~ ln(cast(f32) (t + sqrt(t * t + 1))); -} - -acosh :: (t: $T) -> T { - return ~~ ln(cast(f32) (t + sqrt(t * t - 1))); -} - -atanh :: (t: $T) -> T { - return ~~ ln(cast(f32) ((1 + t) / (1 - t))) / 2; -} - - - -// -// 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 -// 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, -// -// ln(x) = ln(2^n * v) = n * ln(2) + ln(v), v is in [1, 2] -// - -// FIX: This definition is very wrong. It casts E to be whatever the type of the argument is, -// which if it is an integer, will be 2! This should always return a floating point number! -exp :: (p: $T) -> T do return pow(base = cast(T) E, p = p); - -pow :: #match { - // Fast implementation of power when raising to an integer power. - (base: $T, p: i32) -> T { - if base == 0 do return 0; - if p == 0 do return 1; - - a: T = 1; - while p > 0 { - if p % 2 == 1 do a *= base; - p = p >> 1; - base *= base; - } - - return a; - }, - - // Also make the implementation work for 64-bit integers. - (base: $T, p: i64) -> T do return pow(base, cast(i32) p);, - - // Generic power implementation for integers using square roots. - (base: $T, p: T) -> T { - if p == 0 do return 1; - if p < 0 do return 1 / pow(base, -p); - - if p >= 1 { - tmp := pow(p = p / 2, base = base); - return tmp * tmp; - } - - low : T = 0; - high : T = 1; - - sqr := sqrt(base); - acc := sqr; - mid := high / 2; - - while abs(mid - p) > 0.00001 { - sqr = sqrt(sqr); - - if mid <= p { - low = mid; - acc *= sqr; - } else { - high = mid; - acc /= sqr; - } - - mid = (low + high) / 2; - } - - return acc; - } -} - -power_mod :: (base: u32, exp: u32, mod: u32) -> u32 { - t: u64 = 1; - e: u64 = ~~exp; - b: u64 = ~~base; - m: u64 = ~~mod; - - while e > 0 { - if e % 2 != 0 do t = (t * b) % m; - - b = (b * b) % m; - e /= 2; - } - - return ~~(t % m); -} - -ln :: (a: f32) -> f32 { - // FIX: This is probably not the most numerically stable solution. - if a < 1 { - return -ln(1 / a); - // log2 := 63 - cast(i32) clz_i64(cast(i64) (1 / a)); - // x := a / cast(f32) (1 << log2); - // res := -8.6731532f + (129.946172f + (-558.971892f + (843.967330f - 409.109529f * x) * x) * x) * x; - // return res + cast(f32) log2 * 0.69314718f; // ln(2) = 0.69314718 - } - - log2 := 63 - cast(i32) wasm.clz_i64(cast(i64) a); - x := a / cast(f32) (1 << log2); - res := -1.7417939f + (2.8212026f + (-1.4699568f + (0.44717955f - 0.056570851f * x) * x) * x) * x; - res += cast(f32) log2 * 0.69314718; // ln(2) = 0.69314718 - return res; -} - -log :: (a: $T, base: $R) -> T { - if a <= 0 || base <= 0 do return 0; - return ~~(ln(cast(f32) a) / ln(cast(f32) base)); -} - - - - -// These function are overloaded in order to use the builtin WASM intrinsics for the -// operation first, and then default to a polymorphic function that works on any type. -// The clunky part about these at the moment is that, if you wanted to pass 'max' to -// a procedure, you would have to pass 'max_poly' instead, because overloaded functions -// are not resolved when used by value, i.e. foo : (f32, f32) -> f32 = math.max; Even if -// they would be however, the fact that these overloads are intrinsic means they cannot -// be reference from the element section and therefore cannot be passed around or used -// as values. -max :: #match #local { wasm.max_f32, wasm.max_f64, max_poly } -max_poly :: (a: $T, b: T) -> T { - return a if a >= b else b; -} - -min :: #match #local { wasm.min_f32, wasm.min_f64, min_poly } -min_poly :: (a: $T, b: T) -> T { - return a if a <= b else b; -} - -clamp :: (v: $T, lo: T, hi: T) -> T { - if v < lo do return lo; - if v > hi do return hi; - return v; -} - -sqrt :: #match #local { wasm.sqrt_f32, wasm.sqrt_f64, sqrt_poly } -sqrt_poly :: (x: $T) -> T { - return ~~ wasm.sqrt_f64(~~ x); -} - -abs :: #match #local { wasm.abs_f32, wasm.abs_f64, abs_poly } -abs_poly :: (x: $T) -> T { - return x if x >= 0 else -x; -} - -sign :: (x: $T) -> T { - if x > 0 do return 1; - if x < 0 do return cast(T) -1; - return 0; -} - -copysign :: #match #local { wasm.copysign_f32, wasm.copysign_f64, copysign_poly } -copysign_poly :: (x: $T, y: T) -> T { - return abs(x) * sign(y); -} - - - - -// -// Floating point rounding -// - -ceil :: #match #local { wasm.ceil_f32, wasm.ceil_f64 } -floor :: #match #local { wasm.floor_f32, wasm.floor_f64 } -trunc :: #match #local { wasm.trunc_f32, wasm.trunc_f64 } -nearest :: #match #local { wasm.nearest_f32, wasm.nearest_f64 } - - - -// -// Integer operations -// - -clz :: #match #local { wasm.clz_i32, wasm.clz_i64 } -ctz :: #match #local { wasm.ctz_i32, wasm.ctz_i64 } -popcnt :: #match #local { wasm.popcnt_i32, wasm.popcnt_i64 } -rotate_left :: #match #local { wasm.rotl_i32, wasm.rotl_i64 } -rotate_right :: #match #local { wasm.rotr_i32, wasm.rotr_i64 } - - - -lerp :: (t: f32, a: $T, b: T) -> T { - return ~~(~~a * (1 - t) + ~~b * t); -} - -choose :: (n: $T, k: T) -> T { - assert(T == i32 || T == i64 || T == u32 || T == u64, "bad type for choose function"); - - ret := 1; - for i: (n - k + 1) .. (n + 1) { - ret *= i; - } - - for i: 1 .. (k + 1) { - ret /= i; - } - - return ret; -} - -gcd :: (a: $T, b: T) -> T { - if a < 0 do a = -a; - if b < 0 do b = -b; - - if b == 0 do return a; - return gcd(b, a % b); -} - -lcm :: (a: $T, b: T) -> T { - return (a * b) / gcd(a, b); -} - -is_nan :: #match #local {} - -#overload -is_nan :: (x: f32) -> bool { - v := x; - i := *cast(^u32) ^v; - return (i & 0x7f800000) == 0x7f800000 - && (i & 0x007fffff) != 0; -} - -#overload -is_nan :: (x: f64) -> bool { - v := x; - i := *cast(^u64) ^v; - return (i & 0x7ff0000000000000) == 0x7ff0000000000000 - && (i & 0x000fffffffffffff) != 0; -} - - -is_inf :: #match #local {} - -#overload -is_inf :: (x: f32) -> bool { - v := x; - i := *cast(^u32) ^v; - return (i & 0x7f800000) == 0x7f800000 - && (i & 0x007fffff) == 0; -} - -#overload -is_inf :: (x: f64) -> bool { - v := x; - i := *cast(^u64) ^v; - return (i & 0x7ff0000000000000) == 0x7ff0000000000000 - && (i & 0x000fffffffffffff) == 0; -} - - - -// -// Tests -// - -#if #defined(core.Running_Tests) { - -use core {test} - -@test.test.{"GCD works"} -(t: ^test.T) { - t->assert(gcd(35, 18) == 1, "gcd(35, 18) == 1"); - t->assert(gcd(35, 15) == 5, "gcd(35, 15) == 5"); - t->assert(gcd(35, 21) == 7, "gcd(35, 21) == 7"); - t->assert(gcd(35, 70) == 35, "gcd(35, 70) == 35"); -} - -} diff --git a/core/math/math.onyx b/core/math/math.onyx new file mode 100644 index 00000000..73a829e3 --- /dev/null +++ b/core/math/math.onyx @@ -0,0 +1,398 @@ +package core.math + +use core.intrinsics {wasm} + +// Things that are useful in any math library: +// - Trigonometry +// - modf, fmod + +// Other things that can be useful: +// - Vector math +// - Matrix math +// - Why not tensor math?? +// - Complex numbers +// - Dual numbers + + +E :: 2.71828182845904523536f; +PI :: 3.14159265f; +TAU :: 6.28318330f; +SQRT_2 :: 1.414213562f; + +// +// Trigonometry +// Basic trig functions have been implemented using taylor series approximations. The +// approximations are very accurate, but rather computationally expensive. Programs that +// rely heavily on trig functions would greatly benefit from improvements to the +// implementations of these functions. +// + +sin :: (t: f32) -> f32 { + while t >= PI do t -= TAU; + while t <= -PI do t += TAU; + + res := 0.0f; + + plus_minus := 1.0f; + n := 13; + while n > 1 { + res += plus_minus; + res *= t * t / cast(f32) (n * n - n); + + plus_minus = -plus_minus; + n -= 2; + } + + res += 1.0f; + res *= t; + return res; +} + +cos :: (t: f32) -> f32 { + while t >= PI do t -= TAU; + while t <= -PI do t += TAU; + + res := 0.0f; + + plus_minus := 1.0f; + n := 12; + while n > 1 { + res += plus_minus; + res *= t * t / cast(f32) (n * n - n); + + plus_minus = -plus_minus; + n -= 2; + } + + res += 1.0f; + return res; +} + +asin :: (t: f32) -> f32 { + assert(false, "asin is not implemented yet!"); + return 0; +} + +acos :: (t: f32) -> f32 { + assert(false, "acos is not implemented yet!"); + return 0; +} + +atan :: (t: f32) -> f32 { + assert(false, "atan is not implemented yet!"); + return 0; +} + +atan2 :: (t: f32) -> f32 { + assert(false, "atan2 is not implemented yet!"); + return 0; +} + + + + +// +// Hyperbolic trigonometry. +// The hyperbolic trigonometry functions are implemented using the naive +// definitions. There may be fancier, faster and far more precise methods +// of implementing these, but these definitions should suffice. +// + +sinh :: (t: $T) -> T { + et := exp(t); + return (et - (1 / et)) / 2; +} + +cosh :: (t: $T) -> T { + et := exp(t); + return (et + (1 / et)) / 2; +} + +tanh :: (t: $T) -> T { + et := exp(t); + one_over_et := 1 / et; + return (et - one_over_et) / (et + one_over_et); +} + +asinh :: (t: $T) -> T { + return ~~ ln(cast(f32) (t + sqrt(t * t + 1))); +} + +acosh :: (t: $T) -> T { + return ~~ ln(cast(f32) (t + sqrt(t * t - 1))); +} + +atanh :: (t: $T) -> T { + return ~~ ln(cast(f32) ((1 + t) / (1 - t))) / 2; +} + + + +// +// 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 +// 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, +// +// ln(x) = ln(2^n * v) = n * ln(2) + ln(v), v is in [1, 2] +// + +// FIX: This definition is very wrong. It casts E to be whatever the type of the argument is, +// which if it is an integer, will be 2! This should always return a floating point number! +exp :: (p: $T) -> T do return pow(base = cast(T) E, p = p); + +pow :: #match { + // Fast implementation of power when raising to an integer power. + (base: $T, p: i32) -> T { + if base == 0 do return 0; + if p == 0 do return 1; + + a: T = 1; + while p > 0 { + if p % 2 == 1 do a *= base; + p = p >> 1; + base *= base; + } + + return a; + }, + + // Also make the implementation work for 64-bit integers. + (base: $T, p: i64) -> T do return pow(base, cast(i32) p);, + + // Generic power implementation for integers using square roots. + (base: $T, p: T) -> T { + if p == 0 do return 1; + if p < 0 do return 1 / pow(base, -p); + + if p >= 1 { + tmp := pow(p = p / 2, base = base); + return tmp * tmp; + } + + low : T = 0; + high : T = 1; + + sqr := sqrt(base); + acc := sqr; + mid := high / 2; + + while abs(mid - p) > 0.00001 { + sqr = sqrt(sqr); + + if mid <= p { + low = mid; + acc *= sqr; + } else { + high = mid; + acc /= sqr; + } + + mid = (low + high) / 2; + } + + return acc; + } +} + +power_mod :: (base: u32, exp: u32, mod: u32) -> u32 { + t: u64 = 1; + e: u64 = ~~exp; + b: u64 = ~~base; + m: u64 = ~~mod; + + while e > 0 { + if e % 2 != 0 do t = (t * b) % m; + + b = (b * b) % m; + e /= 2; + } + + return ~~(t % m); +} + +ln :: (a: f32) -> f32 { + // FIX: This is probably not the most numerically stable solution. + if a < 1 { + return -ln(1 / a); + // log2 := 63 - cast(i32) clz_i64(cast(i64) (1 / a)); + // x := a / cast(f32) (1 << log2); + // res := -8.6731532f + (129.946172f + (-558.971892f + (843.967330f - 409.109529f * x) * x) * x) * x; + // return res + cast(f32) log2 * 0.69314718f; // ln(2) = 0.69314718 + } + + log2 := 63 - cast(i32) wasm.clz_i64(cast(i64) a); + x := a / cast(f32) (1 << log2); + res := -1.7417939f + (2.8212026f + (-1.4699568f + (0.44717955f - 0.056570851f * x) * x) * x) * x; + res += cast(f32) log2 * 0.69314718; // ln(2) = 0.69314718 + return res; +} + +log :: (a: $T, base: $R) -> T { + if a <= 0 || base <= 0 do return 0; + return ~~(ln(cast(f32) a) / ln(cast(f32) base)); +} + + + + +// These function are overloaded in order to use the builtin WASM intrinsics for the +// operation first, and then default to a polymorphic function that works on any type. +// The clunky part about these at the moment is that, if you wanted to pass 'max' to +// a procedure, you would have to pass 'max_poly' instead, because overloaded functions +// are not resolved when used by value, i.e. foo : (f32, f32) -> f32 = math.max; Even if +// they would be however, the fact that these overloads are intrinsic means they cannot +// be reference from the element section and therefore cannot be passed around or used +// as values. +max :: #match #local { wasm.max_f32, wasm.max_f64, max_poly } +max_poly :: (a: $T, b: T) -> T { + return a if a >= b else b; +} + +min :: #match #local { wasm.min_f32, wasm.min_f64, min_poly } +min_poly :: (a: $T, b: T) -> T { + return a if a <= b else b; +} + +clamp :: (v: $T, lo: T, hi: T) -> T { + if v < lo do return lo; + if v > hi do return hi; + return v; +} + +sqrt :: #match #local { wasm.sqrt_f32, wasm.sqrt_f64, sqrt_poly } +sqrt_poly :: (x: $T) -> T { + return ~~ wasm.sqrt_f64(~~ x); +} + +abs :: #match #local { wasm.abs_f32, wasm.abs_f64, abs_poly } +abs_poly :: (x: $T) -> T { + return x if x >= 0 else -x; +} + +sign :: (x: $T) -> T { + if x > 0 do return 1; + if x < 0 do return cast(T) -1; + return 0; +} + +copysign :: #match #local { wasm.copysign_f32, wasm.copysign_f64, copysign_poly } +copysign_poly :: (x: $T, y: T) -> T { + return abs(x) * sign(y); +} + + + + +// +// Floating point rounding +// + +ceil :: #match #local { wasm.ceil_f32, wasm.ceil_f64 } +floor :: #match #local { wasm.floor_f32, wasm.floor_f64 } +trunc :: #match #local { wasm.trunc_f32, wasm.trunc_f64 } +nearest :: #match #local { wasm.nearest_f32, wasm.nearest_f64 } + + + +// +// Integer operations +// + +clz :: #match #local { wasm.clz_i32, wasm.clz_i64 } +ctz :: #match #local { wasm.ctz_i32, wasm.ctz_i64 } +popcnt :: #match #local { wasm.popcnt_i32, wasm.popcnt_i64 } +rotate_left :: #match #local { wasm.rotl_i32, wasm.rotl_i64 } +rotate_right :: #match #local { wasm.rotr_i32, wasm.rotr_i64 } + + + +lerp :: (t: f32, a: $T, b: T) -> T { + return ~~(~~a * (1 - t) + ~~b * t); +} + +choose :: (n: $T, k: T) -> T { + assert(T == i32 || T == i64 || T == u32 || T == u64, "bad type for choose function"); + + ret := 1; + for i: (n - k + 1) .. (n + 1) { + ret *= i; + } + + for i: 1 .. (k + 1) { + ret /= i; + } + + return ret; +} + +gcd :: (a: $T, b: T) -> T { + if a < 0 do a = -a; + if b < 0 do b = -b; + + if b == 0 do return a; + return gcd(b, a % b); +} + +lcm :: (a: $T, b: T) -> T { + return (a * b) / gcd(a, b); +} + +is_nan :: #match #local {} + +#overload +is_nan :: (x: f32) -> bool { + v := x; + i := *cast(^u32) ^v; + return (i & 0x7f800000) == 0x7f800000 + && (i & 0x007fffff) != 0; +} + +#overload +is_nan :: (x: f64) -> bool { + v := x; + i := *cast(^u64) ^v; + return (i & 0x7ff0000000000000) == 0x7ff0000000000000 + && (i & 0x000fffffffffffff) != 0; +} + + +is_inf :: #match #local {} + +#overload +is_inf :: (x: f32) -> bool { + v := x; + i := *cast(^u32) ^v; + return (i & 0x7f800000) == 0x7f800000 + && (i & 0x007fffff) == 0; +} + +#overload +is_inf :: (x: f64) -> bool { + v := x; + i := *cast(^u64) ^v; + return (i & 0x7ff0000000000000) == 0x7ff0000000000000 + && (i & 0x000fffffffffffff) == 0; +} + + + +// +// Tests +// + +#if #defined(core.Running_Tests) { + +use core {test} + +@test.test.{"GCD works"} +(t: ^test.T) { + t->assert(gcd(35, 18) == 1, "gcd(35, 18) == 1"); + t->assert(gcd(35, 15) == 5, "gcd(35, 15) == 5"); + t->assert(gcd(35, 21) == 7, "gcd(35, 21) == 7"); + t->assert(gcd(35, 70) == 35, "gcd(35, 70) == 35"); +} + +} diff --git a/core/memory.onyx b/core/memory.onyx deleted file mode 100644 index 3c04d26a..00000000 --- a/core/memory.onyx +++ /dev/null @@ -1,67 +0,0 @@ -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; - } -} - -copy :: core.intrinsics.wasm.memory_copy -set :: core.intrinsics.wasm.memory_fill - -alloc_slice :: (sl: ^[] $T, count: i32, allocator := context.allocator) { - sl.data = raw_alloc(allocator, sizeof T * count); - sl.count = count; -} - -make_slice :: ($T: type_expr, count: i32, allocator := context.allocator) -> [] T { - return .{ - data = raw_alloc(allocator, sizeof T * count), - count = count - }; -} - -free_slice :: (sl: ^[] $T, allocator := context.allocator) { - if sl.data == null do return; - - raw_free(allocator, sl.data); - sl.data = null; - sl.count = 0; -} - -copy_slice :: (sl: [] $T, allocator := context.allocator) -> [] T { - data := raw_alloc(allocator, sl.count * sizeof T); - copy(data, sl.data, sl.count * sizeof 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; - - copy(new_slice.data, sl.data, sl.count * sizeof T); - - return new_slice; -} - - -#overload -builtin.__make_overload :: macro (_: ^[] $T, count: u32, allocator := context.allocator) -> [] T { - return (package core.memory).make_slice(T, count, allocator); -} - -#overload -builtin.delete :: macro (x: ^[] $T) { - core.memory.free_slice(x); -} diff --git a/core/memory/memory.onyx b/core/memory/memory.onyx new file mode 100644 index 00000000..3c04d26a --- /dev/null +++ b/core/memory/memory.onyx @@ -0,0 +1,67 @@ +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; + } +} + +copy :: core.intrinsics.wasm.memory_copy +set :: core.intrinsics.wasm.memory_fill + +alloc_slice :: (sl: ^[] $T, count: i32, allocator := context.allocator) { + sl.data = raw_alloc(allocator, sizeof T * count); + sl.count = count; +} + +make_slice :: ($T: type_expr, count: i32, allocator := context.allocator) -> [] T { + return .{ + data = raw_alloc(allocator, sizeof T * count), + count = count + }; +} + +free_slice :: (sl: ^[] $T, allocator := context.allocator) { + if sl.data == null do return; + + raw_free(allocator, sl.data); + sl.data = null; + sl.count = 0; +} + +copy_slice :: (sl: [] $T, allocator := context.allocator) -> [] T { + data := raw_alloc(allocator, sl.count * sizeof T); + copy(data, sl.data, sl.count * sizeof 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; + + copy(new_slice.data, sl.data, sl.count * sizeof T); + + return new_slice; +} + + +#overload +builtin.__make_overload :: macro (_: ^[] $T, count: u32, allocator := context.allocator) -> [] T { + return (package core.memory).make_slice(T, count, allocator); +} + +#overload +builtin.delete :: macro (x: ^[] $T) { + core.memory.free_slice(x); +} diff --git a/core/misc/arg_parse.onyx b/core/misc/arg_parse.onyx new file mode 100644 index 00000000..cde4b24e --- /dev/null +++ b/core/misc/arg_parse.onyx @@ -0,0 +1,58 @@ +package core.arg_parse + +use core + +arg_parse :: (c_args: [] cstr, output: any) -> bool { + arg_iter := iter.as_iterator(c_args) + |> iter.map((x) => string.from_cstr(*x)); + defer arg_iter.close(arg_iter.data); + + use runtime.info; + + ptr_type := cast(^Type_Info_Pointer) get_type_info(output.type); + if ptr_type.kind != .Pointer do return false; + + arg_type := cast(^Type_Info_Struct) get_type_info(ptr_type.to); + if arg_type.kind != .Struct do return false; + + data_base := *cast(^rawptr) output.data; + + for #no_close arg: arg_iter { + for ^member: arg_type.members { + for ^tag: member.tags { + if tag.type != str do continue; + + to_match := *cast(^str) tag.data; + if arg != to_match do continue; + + switch member.type { + case bool { + *(cast(^bool) (cast(^u8) data_base + member.offset)) = !*(cast(^bool) (cast(^u8) data_base + member.offset)); + } + + case i32 { + value_str, success := iter.take_one(arg_iter, no_close=true); + if !success do return false; + + value := conv.str_to_i64(value_str); + *(cast(^i32) (cast(^u8) data_base + member.offset)) = ~~value; + } + + case str { + value, success := iter.take_one(arg_iter, no_close=true); + if !success do return false; + + *(cast(^str) (cast(^u8) data_base + member.offset)) = value; + } + + case #default { + printf("Unsupported argument type, {}.\n", output.type); + return false; + } + } + } + } + } + + return true; +} \ No newline at end of file diff --git a/core/net/net.onyx b/core/net/net.onyx index 9f0d9d57..7a84b0f0 100644 --- a/core/net/net.onyx +++ b/core/net/net.onyx @@ -10,6 +10,10 @@ Socket :: struct { type: SocketType; family: SocketDomain; +} + +// Inject methods for the socket +#inject Socket { close :: socket_close setting :: socket_setting is_alive :: socket_is_alive diff --git a/core/net/tcp.onyx b/core/net/tcp.onyx index 2a3349f0..8885b5a8 100644 --- a/core/net/tcp.onyx +++ b/core/net/tcp.onyx @@ -1,6 +1,6 @@ package core.net -use core {sync, thread, array, memory, alloc, os} +use core {sync, thread, array, memory, alloc, os, iter} #if !runtime.Multi_Threading_Enabled { #error "Expected multi-threading to be enabled for TCP server."; @@ -57,35 +57,32 @@ TCP_Event :: struct { } } -tcp_get_events :: (use conn: ^TCP_Connection) -> Iterator(TCP_Event) { - next :: (use conn: ^TCP_Connection) -> (TCP_Event, bool) { - if event_cursor == events.count do return .{}, false; +tcp_get_events :: (use conn: ^TCP_Connection) => { + conn.event_cursor = 0; - defer event_cursor += 1; - return events[event_cursor], true; - } + return iter.generator( + conn, - close :: (use conn: ^TCP_Connection) { - for events { - switch it.kind { - case .Data { - raw_free(event_allocator, (cast(^TCP_Event.Data) it.data).contents.data); - } - } + (use conn: ^TCP_Connection) -> (TCP_Event, bool) { + if event_cursor == events.count do return .{}, false; - raw_free(event_allocator, it.data); - } + defer event_cursor += 1; + return events[event_cursor], true; + }, - array.clear(^events); - } + (use conn: ^TCP_Connection) { + for events { + switch it.kind { + case .Data { + raw_free(event_allocator, (cast(^TCP_Event.Data) it.data).contents.data); + } + } - conn.event_cursor = 0; + raw_free(event_allocator, it.data); + } - return .{ - data = conn, - next = next, - close = close, - }; + array.clear(^events); + }); } @@ -96,24 +93,6 @@ tcp_get_events :: (use conn: ^TCP_Connection) -> Iterator(TCP_Event) { TCP_Server :: struct { use connection: TCP_Connection; - Client :: struct { - use socket : Socket; - address : Socket_Address; - state : State; - - recv_ready_event_present := false; - - State :: enum { - Alive; - Being_Killed; - Dying; - Dead; - } - - read_complete :: (use this: ^Client) { - recv_ready_event_present = false; - } - } client_allocator: Allocator; clients: [] ^Client; @@ -136,6 +115,29 @@ TCP_Server :: struct { kill_client :: tcp_server_kill_client } +#inject TCP_Server { + Client :: struct { + use socket : Socket; + address : Socket_Address; + state : State; + + recv_ready_event_present := false; + + State :: enum { + Alive; + Being_Killed; + Dying; + Dead; + } + } +} + +#inject TCP_Server.Client { + read_complete :: (use this: ^TCP_Server.Client) { + recv_ready_event_present = false; + } +} + tcp_server_make :: (max_clients := 32, allocator := context.allocator) -> ^TCP_Server { socket, err := socket_create(.Inet, .Stream); // IPv6? if err != .None do return null; diff --git a/core/random.onyx b/core/random.onyx deleted file mode 100644 index 527fb16f..00000000 --- a/core/random.onyx +++ /dev/null @@ -1,42 +0,0 @@ -package core.random - -#local seed : i64 = 8675309 - -#local RANDOM_MULTIPLIER :: 25214903917 -#local RANDOM_INCREMENT :: cast(i64) 11 -// #local RANDOM_MODULUS :: 1 << 32 - -set_seed :: #match { - (s: u32) do seed = ~~s; , - (s: u64) do seed = s; , -} - -int :: (s := ^seed) -> u32 { - *s = *s * RANDOM_MULTIPLIER + RANDOM_INCREMENT; - return cast(u32) ((*s >> 16) & ~~0xffffffff); -} - -between :: (lo: i32, hi: i32) -> i32 do return int () % (hi + 1 - lo) + lo; - -float :: (lo := 0.0f, hi := 1.0f) -> f32 { - return (cast(f32) (int() % (1 << 20)) / cast(f32) (1 << 20)) * (hi - lo) + lo; -} - -choice :: (a: [] $T) -> T { - return a[between(0, a.count - 1)]; -} - -string :: (bytes_long: u32, alpha_numeric := false, allocator := context.allocator) -> str { - memory :: package core.memory - - s := memory.make_slice(u8, bytes_long, allocator=allocator); - for^ s { - if alpha_numeric { - #persist alpha_numeral := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - *it = choice(alpha_numeral); - } else { - *it = ~~between(32, 127); - } - } - return s; -} diff --git a/core/random/random.onyx b/core/random/random.onyx new file mode 100644 index 00000000..527fb16f --- /dev/null +++ b/core/random/random.onyx @@ -0,0 +1,42 @@ +package core.random + +#local seed : i64 = 8675309 + +#local RANDOM_MULTIPLIER :: 25214903917 +#local RANDOM_INCREMENT :: cast(i64) 11 +// #local RANDOM_MODULUS :: 1 << 32 + +set_seed :: #match { + (s: u32) do seed = ~~s; , + (s: u64) do seed = s; , +} + +int :: (s := ^seed) -> u32 { + *s = *s * RANDOM_MULTIPLIER + RANDOM_INCREMENT; + return cast(u32) ((*s >> 16) & ~~0xffffffff); +} + +between :: (lo: i32, hi: i32) -> i32 do return int () % (hi + 1 - lo) + lo; + +float :: (lo := 0.0f, hi := 1.0f) -> f32 { + return (cast(f32) (int() % (1 << 20)) / cast(f32) (1 << 20)) * (hi - lo) + lo; +} + +choice :: (a: [] $T) -> T { + return a[between(0, a.count - 1)]; +} + +string :: (bytes_long: u32, alpha_numeric := false, allocator := context.allocator) -> str { + memory :: package core.memory + + s := memory.make_slice(u8, bytes_long, allocator=allocator); + for^ s { + if alpha_numeric { + #persist alpha_numeral := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + *it = choice(alpha_numeral); + } else { + *it = ~~between(32, 127); + } + } + return s; +} diff --git a/core/std.onyx b/core/std.onyx index 0b2329a6..669887c5 100644 --- a/core/std.onyx +++ b/core/std.onyx @@ -1,8 +1,8 @@ package core -#load "./alloc" -#load "./memory" +#load "./alloc/alloc" +#load "./memory/memory" #load "./container/array" #load "./container/avl_tree" @@ -13,12 +13,15 @@ package core #load "./container/bucket_array" #load "./container/heap" -#load "./conv" -#load "./math" -#load "./random" -#load "./hash" +#load "./conv/conv" +#load "./conv/format" +#load "./conv/parse" -#load "./string" +#load "./math/math" +#load "./random/random" +#load "./hash/hash" + +#load "./string/string" #load "./string/reader" #load "./string/buffer" #load "./string/char_utils" @@ -36,10 +39,10 @@ package core #load "./runtime/build_opts" #load "./runtime/common" #load "./runtime/default_link_options" -#load "./arg_parse" #load "./test/testing" +#load "./misc/arg_parse" #load "./misc/any_utils" #local runtime :: package runtime @@ -73,7 +76,7 @@ package core #if runtime.runtime == .Js { #load "./runtime/js" } #if runtime.runtime != .Custom { #load "./runtime/info/helper" - #load "./stdio" + #load "./io/stdio" #load "./encoding/base64" #load "./encoding/csv" diff --git a/core/stdio.onyx b/core/stdio.onyx deleted file mode 100644 index b79f5737..00000000 --- a/core/stdio.onyx +++ /dev/null @@ -1,173 +0,0 @@ -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. - - -#if runtime.runtime == .Custom { - #error "'stdio' can only be included in the 'wasi' or 'js' runtime." -} - -stdio_stream: io.Stream; - -auto_flush_stdio := true - -print :: #match #locked { - (x: str) { - io.write(^stdio.print_writer, x); - if x[x.count - 1] == #char "\n" && auto_flush_stdio do __flush_stdio(); - }, - - (x) => { io.write(^stdio.print_writer, x); }, - (x, y) => { io.write(^stdio.print_writer, x, y); }, -} - -println :: (x) => { - print(x); - print("\n"); -} - -// Standard formatted print. -printf :: (format: str, va: ..any) { - flush :: (_, to_output) => { - io.write(^stdio.print_writer, to_output); - __flush_stdio(); - return true; - } - - buffer: [1024] u8; - print(conv.format_va(buffer, format, va, .{null, flush})); -} - -// Print to standard error, if available. -#if #defined(runtime.__output_error) { - eprintf :: (format: str, va: ..any) -> str { - flush :: (_, to_output) => { - runtime.__output_error(to_output); - return true; - } - - buffer: [1024] u8; - runtime.__output_error(conv.format_va(buffer, format, va, .{null, flush})); - } -} - -// Print to a dynamically allocated 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. -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) { - temp: [3] u8; - - u8_ptr := cast(^u8) ptr; - for i: byte_count { - val := u8_ptr[i]; - - temp[0] = map_to_ascii(val >> 4); - temp[1] = map_to_ascii(val & 15); - temp[2] = #char " "; - - runtime.__output_string(~~temp); - - if i % bytes_per_line == (bytes_per_line - 1) do runtime.__output_string("\n"); - } - - - map_to_ascii :: (x: u8) -> u8 { - switch x { - case 0 do return #char "0"; - case 1 do return #char "1"; - case 2 do return #char "2"; - case 3 do return #char "3"; - case 4 do return #char "4"; - case 5 do return #char "5"; - case 6 do return #char "6"; - case 7 do return #char "7"; - case 8 do return #char "8"; - case 9 do return #char "9"; - case 10 do return #char "A"; - case 11 do return #char "B"; - case 12 do return #char "C"; - case 13 do return #char "D"; - case 14 do return #char "E"; - case 15 do return #char "F"; - case #default do return #char "X"; - } - } -} - - -// -// 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); - - // This shouldn't need to be here, but because ^stdin_vtable is not a compile-time - // known value (even through it should be). - stdio_stream.vtable = ^stdio_vtable; -} - - -__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(); -} - -#local stdio_vtable := io.Stream_Vtable.{ - read = (_: ^io.Stream, buffer: [] u8) -> (io.Error, u32) { - __flush_stdio(); - bytes_read := runtime.__read_from_input(buffer); - if bytes_read == 0 do return .ReadPending, 0; - if bytes_read < 0 do return .EOF, 0; - - return .None, bytes_read; - }, - - read_byte = (_: ^io.Stream) -> (io.Error, u8) { - __flush_stdio(); - buf: [1] u8; - bytes_read := runtime.__read_from_input(buf); - if bytes_read <= 0 do return .EOF, 0; - - return .None, buf[0]; - }, - - write = (_: ^io.Stream, buffer: [] u8) -> (io.Error, u32) { - return io.stream_write(^stdio.print_stream, buffer); - }, - - write_byte = (_: ^io.Stream, byte: u8) -> io.Error { - return io.stream_write_byte(^stdio.print_stream, byte); - }, - - flush = (_: ^io.Stream) -> io.Error { - __flush_stdio(); - return .None; - } -} diff --git a/core/string.onyx b/core/string.onyx deleted file mode 100644 index 1e37901f..00000000 --- a/core/string.onyx +++ /dev/null @@ -1,543 +0,0 @@ -package core.string - -use core - -as_str :: #match {} - -free :: (s: str, allocator := context.allocator) do raw_free(allocator, s.data); - -alloc_copy :: (original: str, allocator := context.allocator) -> str { - new_str : str; - new_str.data = raw_alloc(allocator, sizeof u8 * original.count); - new_str.count = original.count; - copy(original, new_str); - return new_str; -} - -temp_copy :: (original: str) -> str { - new_str := make([] u8, original.count, allocator=context.temp_allocator); - copy(original, new_str); - return new_str; -} - -copy :: (orig: str, dest: str) { - len := orig.count; - if dest.count < len do len = dest.count; - - memory.copy(dest.data, orig.data, len); -} - -#match as_str from_cstr -from_cstr :: (s: cstr) -> str { - return .{ data = s, count = length(s) }; -} - - -length :: #match #local {} - -#overload -length :: (s: str) => s.count; - -#overload -length :: (s: cstr) -> u32 { - len := 0; - c := s; - while *c != #char "\0" { - len += 1; - c += 1; - } - - return len; -} - - -concat :: #match #local {} - -#overload -concat :: (s1: str, s2: str, allocator := context.allocator) -> str { - len1 := length(s1); - len2 := length(s2); - - data := cast(^u8) raw_alloc(allocator, len1 + len2); - memory.copy(data, s1.data, len1); - memory.copy(data + len1, s2.data, len2); - - return str.{ data, len1 + len2 }; -} - -// @Cleanup // Don't love that the allocator is necessary here, -// but it is impossible to specify a default value for the -// allocator while having a variadic number of strings. This -// is only due to the languages constraints however. This -// could easily be changed since there is no ambiguity. -#overload -concat :: (allocator: Allocator, strings: ..str) -> str { - total_length := 0; - for s: strings do total_length += s.count; - - data := cast(^u8) raw_alloc(allocator, total_length); - offset := 0; - for s: strings { - memory.copy(data + offset, s.data, s.count); - offset += s.count; - } - - return str.{ data, total_length }; -} - -#overload -concat :: (buffer: [] u8, strings: ..str) -> str { - total_copied := 0; - for s: strings { - // Should never greater than, but better safe than sorry. - if total_copied >= buffer.count do break; - - bytes_to_copy := math.min(s.count, buffer.count - total_copied); - memory.copy(buffer.data + total_copied, s.data, bytes_to_copy); - total_copied += bytes_to_copy; - } - - return buffer[0 .. total_copied]; -} - -#overload -concat :: (into: ^[..] u8, strings: ..str) -> str { - for s: strings { - array.ensure_capacity(into, into.count + s.count); - memory.copy(into.data + into.count, s.data, s.count); - into.count += s.count; - } - return .{ into.data, into.count }; -} - - -contains :: #match #local {} - -#overload -contains :: (s: str, c: u8) -> bool { - for ch: s do if ch == c do return true; - return false; -} - -#overload -contains :: (s: str, substr: str) -> bool { - while i := 0; i < s.count { - while j := 0; j < substr.count { - if s[i + j] != substr[j] { - i += j + 1; - continue continue; - } - - j += 1; - } - - return true; - } - - return false; -} - - -// @TODO -// Check this for edge cases and other bugs. I'm not confident -// it will work perfectly yet. - brendanfh 2020/12/21 -compare :: (str1: str, str2: str) -> i32 { - i := 0; - while i < str1.count && i < str2.count { - if str1[i] == str2[i] do i += 1; - else do break; - } - - if i == str1.count && i == str2.count do return 0; - return ~~(str1[i] - str2[i]); -} - -equal :: (str1: str, str2: str) -> bool { - if str1.count != str2.count do return false; - while i := 0; i < str1.count { - if str1[i] != str2[i] do return false; - i += 1; - } - return true; -} - -equal_insensitive :: (s1, s2: str) -> bool { - if s1.count != s2.count do return false; - while i := 0; i < s1.count { - defer i += 1; - if s1[i] == s2[i] do continue; - - c1 := s1[i]; - c2 := s2[i]; - if c1 >= #char "A" && c1 <= #char "Z" do c1 += 32; - if c2 >= #char "A" && c2 <= #char "Z" do c2 += 32; - if c1 != c2 do return false; - } - return true; -} - -#operator == equal -#operator != macro (s1: str, s2: str) => !(s1 == s2); - -starts_with :: (s: str, prefix: str) -> bool { - if s.count < prefix.count do return false; - while i := 0; i < prefix.count { - if s[i] != prefix[i] do return false; - i += 1; - } - return true; -} - -ends_with :: (s: str, suffix: str) -> bool { - if s.count < suffix.count do return false; - while i := 0; i < suffix.count { - if s[s.count - 1 - i] != suffix[suffix.count - 1 - i] do return false; - i += 1; - } - return true; -} - -empty :: (s: str) => s.count == 0 || s.data == null; - -is_empty :: (s: str) -> bool #deprecated "Use 'string.empty' instead." { - s.count == 0 || s.data == null; -} - -index_of :: (s: str, c: u8) -> i32 { - for s.count { - if s[it] == c do return it; - } - return -1; -} - -last_index_of :: (s: str, c: u8) -> i32 { - for range.{s.count, 0, -1} { - if s[it] == c do return it; - } - return -1; -} - - -strip_whitespace :: #match #local {} - -#overload -strip_whitespace :: (s: ^str) { - strip_leading_whitespace(s); - strip_trailing_whitespace(s); -} - -#overload -strip_whitespace :: (s: str) => - s |> strip_leading_whitespace() - |> strip_trailing_whitespace() - - -strip_leading_whitespace :: #match #local {} - -#overload -strip_leading_whitespace :: (s: ^str) { - while s.count > 0 do switch s.data[0] { - case #char " ", #char "\t", #char "\n", #char "\r" { - s.data += 1; - s.count -= 1; - } - - case #default do return; - } -} - -#overload -strip_leading_whitespace :: (s: str) -> str { - out := s; - strip_leading_whitespace(^out); - return out; -} - - -strip_trailing_whitespace :: #match #local {} - -#overload -strip_trailing_whitespace :: (s: ^str) { - while s.count >= 1 do switch s.data[s.count - 1] { - case #char " ", #char "\t", #char "\n", #char "\r" { - s.count -= 1; - } - - case #default do return; - } -} - -#overload -strip_trailing_whitespace :: (s: str) -> str { - out := s; - strip_trailing_whitespace(^out); - return out; -} - -to_uppercase :: (s: str) -> str { - for^ ch: s { - if *ch >= #char "a" && *ch <= #char "z" { - *ch -= 32; - } - } - - return s; -} - -to_lowercase :: (s: str) -> str { - for^ ch: s { - if *ch >= #char "A" && *ch <= #char "Z" { - *ch += 32; - } - } - - return s; -} - - -trim_start :: #match #local {} - -#overload -trim_start :: (s: ^str, char: u8) { - while s.data[0] == char { - s.data += 1; - s.count -= 1; - } -} - -#overload -trim_start :: (s: str, char: u8) -> str { - out := s; - trim_start(^out, char); - return out; -} - - -trim_end :: #match #local {} - -#overload -trim_end :: (s: ^str, char: u8) { - while s.data[s.count - 1] == char { - s.count -= 1; - } -} - -#overload -trim_end :: (s: str, char: u8) -> str { - out := s; - trim_end(^out, char); - return out; -} - - -advance :: #match #local {} - -#overload -advance :: (s: ^str, chars := 1) { - chars = math.min(chars, s.count); - - s.data += chars; - s.count -= chars; -} - -#overload -advance :: (s: str, chars := 1) -> str { - chars = math.min(chars, s.count); - out := s; - - out.data += chars; - out.count -= chars; - - return out; -} - -replace :: (s: str, to_replace: u8, replace_with: u8) { - for ^c: s { - if *c == to_replace do *c = replace_with; - } -} - -read_until :: #match #local {} - -#overload -read_until :: (s: ^str, upto: u8, skip := 0) -> str { - if s.count == 0 do return ""; - - out : str; - out.data = s.data; - out.count = 0; - - rem := skip; - for ch: *s { - if ch == upto { - if rem <= 0 do break; - else do rem -= 1; - } - - out.count += 1; - } - - s.data += out.count; - s.count -= out.count; - - return out; -} - -#overload -read_until :: (s: ^str, upto: str, skip := 0) -> str { - if s.count == 0 do return ""; - - out := str.{ data = s.data }; - - rem := skip; - i := 0; - while i <= s.count - upto.count { - match := true; - j := i; - for upto { - if s.data[j] != it { - match = false; - break; - } - - j += 1; - } - - if match { - if rem <= 0 do break; - else do rem -= 1; - } - - i += 1; - } - - if i > s.count - upto.count { - out = *s; - s.data += out.count; - s.count = 0; - - } else { - out.count = i; - s.data += out.count; - s.count -= out.count; - } - - return out; -} - -read_alphanum :: (s: ^str) -> str { - if s.count == 0 do return ""; - - out : str; - out.data = s.data; - out.count = 0; - - for ch: *s { - switch ch { - case #char "a" .. #char "z", - #char "A" .. #char "Z", - #char "0" .. #char "9" { - out.count += 1; - } - - case #default { - break break; - } - } - } - - s.data += out.count; - s.count -= out.count; - - return out; -} - -read_until_any :: (s: ^str, skip: u32, uptos: ..u8) -> str { - if s.count == 0 do return ""; - - out : str; - out.data = s.data; - out.count = 0; - - rem := skip; - for ch: *s { - for upto: uptos { - if ch == upto { - if rem <= 0 do break break; - else do rem -= 1; - } - } - - out.count += 1; - } - - s.data += out.count; - s.count -= out.count; - - return out; -} - -advance_line :: (s: ^str) { - if s.count == 0 do return; - - adv := 0; - while s.data[adv] != #char "\n" do adv += 1; - - s.data += adv + 1; - s.count -= adv + 1; -} - -split :: (s: str, delim: u8, allocator := context.allocator) -> []str { - delim_count := 0; - for i: 0 .. s.count do if s[i] == delim do delim_count += 1; - - strarr := cast(^str) raw_alloc(allocator, sizeof str * (delim_count + 1)); - - curr_str := 0; - begin := 0; - - for i: 0 .. s.count { - if s[i] == delim { - strarr[curr_str] = s.data[begin .. i]; - begin = i + 1; - curr_str += 1; - } - } - - strarr[curr_str] = s.data[begin .. s.count]; - - return strarr[0 .. delim_count + 1]; -} - -split_iter :: (s: str, delim: u8) -> Iterator(str) { - return iter.generator( - ^.{ s = s, delim = delim }, - - (ctx: ^$T) -> (str, bool) { - if string.empty(ctx.s) { - return "", false; - } - - ret: str; - ret, ctx.s = bisect(ctx.s, ctx.delim); - return ret, true; - } - ); -} - -// -// Splits a string into two parts, divided by the -// first instance of the provided character. Either -// string can be empty if the first instance of the -// character occurs at the very beginning or end of -// the string, or if it does not occur at all. -// -bisect :: (s: str, c: u8) -> (str, str) { - index := index_of(s, c); - if index == -1 { - return s, ""; - } - - return s[0 .. index], s[index+1 .. s.length]; -} - diff --git a/core/string/string.onyx b/core/string/string.onyx new file mode 100644 index 00000000..1e37901f --- /dev/null +++ b/core/string/string.onyx @@ -0,0 +1,543 @@ +package core.string + +use core + +as_str :: #match {} + +free :: (s: str, allocator := context.allocator) do raw_free(allocator, s.data); + +alloc_copy :: (original: str, allocator := context.allocator) -> str { + new_str : str; + new_str.data = raw_alloc(allocator, sizeof u8 * original.count); + new_str.count = original.count; + copy(original, new_str); + return new_str; +} + +temp_copy :: (original: str) -> str { + new_str := make([] u8, original.count, allocator=context.temp_allocator); + copy(original, new_str); + return new_str; +} + +copy :: (orig: str, dest: str) { + len := orig.count; + if dest.count < len do len = dest.count; + + memory.copy(dest.data, orig.data, len); +} + +#match as_str from_cstr +from_cstr :: (s: cstr) -> str { + return .{ data = s, count = length(s) }; +} + + +length :: #match #local {} + +#overload +length :: (s: str) => s.count; + +#overload +length :: (s: cstr) -> u32 { + len := 0; + c := s; + while *c != #char "\0" { + len += 1; + c += 1; + } + + return len; +} + + +concat :: #match #local {} + +#overload +concat :: (s1: str, s2: str, allocator := context.allocator) -> str { + len1 := length(s1); + len2 := length(s2); + + data := cast(^u8) raw_alloc(allocator, len1 + len2); + memory.copy(data, s1.data, len1); + memory.copy(data + len1, s2.data, len2); + + return str.{ data, len1 + len2 }; +} + +// @Cleanup // Don't love that the allocator is necessary here, +// but it is impossible to specify a default value for the +// allocator while having a variadic number of strings. This +// is only due to the languages constraints however. This +// could easily be changed since there is no ambiguity. +#overload +concat :: (allocator: Allocator, strings: ..str) -> str { + total_length := 0; + for s: strings do total_length += s.count; + + data := cast(^u8) raw_alloc(allocator, total_length); + offset := 0; + for s: strings { + memory.copy(data + offset, s.data, s.count); + offset += s.count; + } + + return str.{ data, total_length }; +} + +#overload +concat :: (buffer: [] u8, strings: ..str) -> str { + total_copied := 0; + for s: strings { + // Should never greater than, but better safe than sorry. + if total_copied >= buffer.count do break; + + bytes_to_copy := math.min(s.count, buffer.count - total_copied); + memory.copy(buffer.data + total_copied, s.data, bytes_to_copy); + total_copied += bytes_to_copy; + } + + return buffer[0 .. total_copied]; +} + +#overload +concat :: (into: ^[..] u8, strings: ..str) -> str { + for s: strings { + array.ensure_capacity(into, into.count + s.count); + memory.copy(into.data + into.count, s.data, s.count); + into.count += s.count; + } + return .{ into.data, into.count }; +} + + +contains :: #match #local {} + +#overload +contains :: (s: str, c: u8) -> bool { + for ch: s do if ch == c do return true; + return false; +} + +#overload +contains :: (s: str, substr: str) -> bool { + while i := 0; i < s.count { + while j := 0; j < substr.count { + if s[i + j] != substr[j] { + i += j + 1; + continue continue; + } + + j += 1; + } + + return true; + } + + return false; +} + + +// @TODO +// Check this for edge cases and other bugs. I'm not confident +// it will work perfectly yet. - brendanfh 2020/12/21 +compare :: (str1: str, str2: str) -> i32 { + i := 0; + while i < str1.count && i < str2.count { + if str1[i] == str2[i] do i += 1; + else do break; + } + + if i == str1.count && i == str2.count do return 0; + return ~~(str1[i] - str2[i]); +} + +equal :: (str1: str, str2: str) -> bool { + if str1.count != str2.count do return false; + while i := 0; i < str1.count { + if str1[i] != str2[i] do return false; + i += 1; + } + return true; +} + +equal_insensitive :: (s1, s2: str) -> bool { + if s1.count != s2.count do return false; + while i := 0; i < s1.count { + defer i += 1; + if s1[i] == s2[i] do continue; + + c1 := s1[i]; + c2 := s2[i]; + if c1 >= #char "A" && c1 <= #char "Z" do c1 += 32; + if c2 >= #char "A" && c2 <= #char "Z" do c2 += 32; + if c1 != c2 do return false; + } + return true; +} + +#operator == equal +#operator != macro (s1: str, s2: str) => !(s1 == s2); + +starts_with :: (s: str, prefix: str) -> bool { + if s.count < prefix.count do return false; + while i := 0; i < prefix.count { + if s[i] != prefix[i] do return false; + i += 1; + } + return true; +} + +ends_with :: (s: str, suffix: str) -> bool { + if s.count < suffix.count do return false; + while i := 0; i < suffix.count { + if s[s.count - 1 - i] != suffix[suffix.count - 1 - i] do return false; + i += 1; + } + return true; +} + +empty :: (s: str) => s.count == 0 || s.data == null; + +is_empty :: (s: str) -> bool #deprecated "Use 'string.empty' instead." { + s.count == 0 || s.data == null; +} + +index_of :: (s: str, c: u8) -> i32 { + for s.count { + if s[it] == c do return it; + } + return -1; +} + +last_index_of :: (s: str, c: u8) -> i32 { + for range.{s.count, 0, -1} { + if s[it] == c do return it; + } + return -1; +} + + +strip_whitespace :: #match #local {} + +#overload +strip_whitespace :: (s: ^str) { + strip_leading_whitespace(s); + strip_trailing_whitespace(s); +} + +#overload +strip_whitespace :: (s: str) => + s |> strip_leading_whitespace() + |> strip_trailing_whitespace() + + +strip_leading_whitespace :: #match #local {} + +#overload +strip_leading_whitespace :: (s: ^str) { + while s.count > 0 do switch s.data[0] { + case #char " ", #char "\t", #char "\n", #char "\r" { + s.data += 1; + s.count -= 1; + } + + case #default do return; + } +} + +#overload +strip_leading_whitespace :: (s: str) -> str { + out := s; + strip_leading_whitespace(^out); + return out; +} + + +strip_trailing_whitespace :: #match #local {} + +#overload +strip_trailing_whitespace :: (s: ^str) { + while s.count >= 1 do switch s.data[s.count - 1] { + case #char " ", #char "\t", #char "\n", #char "\r" { + s.count -= 1; + } + + case #default do return; + } +} + +#overload +strip_trailing_whitespace :: (s: str) -> str { + out := s; + strip_trailing_whitespace(^out); + return out; +} + +to_uppercase :: (s: str) -> str { + for^ ch: s { + if *ch >= #char "a" && *ch <= #char "z" { + *ch -= 32; + } + } + + return s; +} + +to_lowercase :: (s: str) -> str { + for^ ch: s { + if *ch >= #char "A" && *ch <= #char "Z" { + *ch += 32; + } + } + + return s; +} + + +trim_start :: #match #local {} + +#overload +trim_start :: (s: ^str, char: u8) { + while s.data[0] == char { + s.data += 1; + s.count -= 1; + } +} + +#overload +trim_start :: (s: str, char: u8) -> str { + out := s; + trim_start(^out, char); + return out; +} + + +trim_end :: #match #local {} + +#overload +trim_end :: (s: ^str, char: u8) { + while s.data[s.count - 1] == char { + s.count -= 1; + } +} + +#overload +trim_end :: (s: str, char: u8) -> str { + out := s; + trim_end(^out, char); + return out; +} + + +advance :: #match #local {} + +#overload +advance :: (s: ^str, chars := 1) { + chars = math.min(chars, s.count); + + s.data += chars; + s.count -= chars; +} + +#overload +advance :: (s: str, chars := 1) -> str { + chars = math.min(chars, s.count); + out := s; + + out.data += chars; + out.count -= chars; + + return out; +} + +replace :: (s: str, to_replace: u8, replace_with: u8) { + for ^c: s { + if *c == to_replace do *c = replace_with; + } +} + +read_until :: #match #local {} + +#overload +read_until :: (s: ^str, upto: u8, skip := 0) -> str { + if s.count == 0 do return ""; + + out : str; + out.data = s.data; + out.count = 0; + + rem := skip; + for ch: *s { + if ch == upto { + if rem <= 0 do break; + else do rem -= 1; + } + + out.count += 1; + } + + s.data += out.count; + s.count -= out.count; + + return out; +} + +#overload +read_until :: (s: ^str, upto: str, skip := 0) -> str { + if s.count == 0 do return ""; + + out := str.{ data = s.data }; + + rem := skip; + i := 0; + while i <= s.count - upto.count { + match := true; + j := i; + for upto { + if s.data[j] != it { + match = false; + break; + } + + j += 1; + } + + if match { + if rem <= 0 do break; + else do rem -= 1; + } + + i += 1; + } + + if i > s.count - upto.count { + out = *s; + s.data += out.count; + s.count = 0; + + } else { + out.count = i; + s.data += out.count; + s.count -= out.count; + } + + return out; +} + +read_alphanum :: (s: ^str) -> str { + if s.count == 0 do return ""; + + out : str; + out.data = s.data; + out.count = 0; + + for ch: *s { + switch ch { + case #char "a" .. #char "z", + #char "A" .. #char "Z", + #char "0" .. #char "9" { + out.count += 1; + } + + case #default { + break break; + } + } + } + + s.data += out.count; + s.count -= out.count; + + return out; +} + +read_until_any :: (s: ^str, skip: u32, uptos: ..u8) -> str { + if s.count == 0 do return ""; + + out : str; + out.data = s.data; + out.count = 0; + + rem := skip; + for ch: *s { + for upto: uptos { + if ch == upto { + if rem <= 0 do break break; + else do rem -= 1; + } + } + + out.count += 1; + } + + s.data += out.count; + s.count -= out.count; + + return out; +} + +advance_line :: (s: ^str) { + if s.count == 0 do return; + + adv := 0; + while s.data[adv] != #char "\n" do adv += 1; + + s.data += adv + 1; + s.count -= adv + 1; +} + +split :: (s: str, delim: u8, allocator := context.allocator) -> []str { + delim_count := 0; + for i: 0 .. s.count do if s[i] == delim do delim_count += 1; + + strarr := cast(^str) raw_alloc(allocator, sizeof str * (delim_count + 1)); + + curr_str := 0; + begin := 0; + + for i: 0 .. s.count { + if s[i] == delim { + strarr[curr_str] = s.data[begin .. i]; + begin = i + 1; + curr_str += 1; + } + } + + strarr[curr_str] = s.data[begin .. s.count]; + + return strarr[0 .. delim_count + 1]; +} + +split_iter :: (s: str, delim: u8) -> Iterator(str) { + return iter.generator( + ^.{ s = s, delim = delim }, + + (ctx: ^$T) -> (str, bool) { + if string.empty(ctx.s) { + return "", false; + } + + ret: str; + ret, ctx.s = bisect(ctx.s, ctx.delim); + return ret, true; + } + ); +} + +// +// Splits a string into two parts, divided by the +// first instance of the provided character. Either +// string can be empty if the first instance of the +// character occurs at the very beginning or end of +// the string, or if it does not occur at all. +// +bisect :: (s: str, c: u8) -> (str, str) { + index := index_of(s, c); + if index == -1 { + return s, ""; + } + + return s[0 .. index], s[index+1 .. s.length]; +} + diff --git a/scripts/onyx-pkg.onyx b/scripts/onyx-pkg.onyx index 926040ed..a580cdb7 100644 --- a/scripts/onyx-pkg.onyx +++ b/scripts/onyx-pkg.onyx @@ -425,6 +425,27 @@ run_list_versions :: (args: [] cstr) { } } +#tag Command.{ "run", "Run the command provided in `config.run_cmd`." } +run_run_command :: (args: [] cstr) { + if !string.empty(config.config.run_cmd) { + run_command_and_forward_output(config.config.run_cmd); + } +} + +#tag Command.{ "debug", "Run the command provided in `config.debug_cmd`." } +run_debug_command :: (args: [] cstr) { + if !string.empty(config.config.debug_cmd) { + run_command_and_forward_output(config.config.debug_cmd); + } +} + +#tag Command.{ "test", "Run the command provided in `config.test_cmd`." } +run_test_command :: (args: [] cstr) { + if !string.empty(config.config.test_cmd) { + run_command_and_forward_output(config.config.test_cmd); + } +} + #local { #if runtime.compiler_os == .Linux { @@ -562,6 +583,22 @@ run_native_library_installation :: (folder: str) -> bool { } } +run_command_and_forward_output :: (cmd: str) => { + args := string.split(cmd, #char " ", context.temp_allocator); + prog := args[0]; + args = args[1 .. args.count]; + + run_proc := os.process_spawn(prog, args); + r := io.reader_make(^run_proc); + + while !(r->empty()) { + line := r->read_line(consume_newline=true); + print(line); + } + + return os.process_wait(^run_proc); +} + #tag conv.Custom_Parse.{parse} #tag conv.Custom_Format.{format} @@ -791,6 +828,9 @@ Config :: struct { Config :: struct { lib_source_directory: str = "./lib"; lib_bin_directory: str = "./bin"; + run_cmd: str; + debug_cmd: str; + test_cmd: str; } config: Config = .{}; @@ -931,8 +971,7 @@ parse_ini_file :: (r: ^io.Reader, output_ptr: any) -> (IniParseResult, IniParseE output.type = (cast(^info.Type_Info_Pointer) t).to; } - // Set the temporary allocator to an arena that will be freed at the end. - alloc.arena.auto(32 * 1024, #(context.temp_allocator)); + defer alloc.clear_temp_allocator(); active_item_ptr := null; active_item_type := void; diff --git a/shared/lib/linux_x86_64/lib/libovmwasm.so b/shared/lib/linux_x86_64/lib/libovmwasm.so index 09751b8a..6319498f 100755 Binary files a/shared/lib/linux_x86_64/lib/libovmwasm.so and b/shared/lib/linux_x86_64/lib/libovmwasm.so differ diff --git a/tests/i32map.onyx b/tests/i32map.onyx index d7a88f12..38cb2574 100644 --- a/tests/i32map.onyx +++ b/tests/i32map.onyx @@ -19,7 +19,7 @@ main :: (args: [] cstr) { printf("{}{}\n", map.get(^imap, 50), map.get(^imap, 1234)); - printf("{*}\n", ^imap); + printf("{*p}\n", ^imap); map.delete(^imap, 50); println(map.has(^imap, 50));