From: Brendan Hansen Date: Sat, 11 Feb 2023 04:37:58 +0000 (-0600) Subject: improved usability of 'core.time'; started documenting builtin.onyx X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=ae3e700c1347074a06e101f70844398a715fe70c;p=onyx.git improved usability of 'core.time'; started documenting builtin.onyx --- diff --git a/core/builtin.onyx b/core/builtin.onyx index 883a29db..74e78ced 100644 --- a/core/builtin.onyx +++ b/core/builtin.onyx @@ -1,55 +1,92 @@ package builtin +// +// Explanation of `package builtin` +// +// The package "builtin" is a special package, and this file is a special file. +// This file is automatically included in EVERY Onyx compilation. It contains +// many of the core data types and "magic" functions that Onyx needs to operate. +// There is no way to not include this file, so the number of things in here +// have, and should continue, to remain limited. +// +// "builtin" is a special package. Because many of these core data types are +// needed in every single Onyx file, it would be nice if they were always +// accessible. To make this possible, the *public* scope of the builtin package +// is actually the *global* scope, the scope above every package. The global +// scope is visible to every file. By mapping builtin's public scope to the +// global scope, everything in this file can be accessed without needing to +// 'use' or prefix anything. +// + + + + + // CLEANUP: Should builtin.onyx really be including other files in the compilation? // Does that complicate things too much? #load "core/runtime/build_opts" -str :: #type []u8; -cstr :: #type ^u8; +// +// The builtin string and C-string types. +// A string is simply a slice of bytes, and a c-string is a pointer +// to byte, with a null-terminator ('\0') at the end. +str :: #type [] u8; +cstr :: #type ^ u8; + + -// @Note -// Because of many implementation details, all fields of this -// struct are required to be i32's. + +// +// This is the type of a range literal (i.e. 1 .. 5). +// This is a special type that the compiler knows how to iterator through. +// So, one can simply write: +// +// for x: 1 .. 5 { ... } +// +// Although not controllable from the literal syntax, there is a `step` +// member that allows you control how many numbers to advance each iteration. +// For example, range.{ 0, 100, 2 } would iterate over the even numbers, and +// range.{ 100, 0, -1 } would count backwards from 100 to 0 (and including 0). range :: struct { low : i32; high : i32; step : i32 = 1; } -// @Deprecated -vararg :: #type ^struct { - data: rawptr; - count: i32; -} -// @Deprecated -vararg_get :: #match { - (va: vararg, ret: ^$T) -> bool { - if va.count <= 0 do return false; - *ret = *cast(^T) va.data; - va.data = cast(rawptr) (cast(^u8) va.data + sizeof T); - va.count -= 1; - return true; - }, - - (va: vararg, $T: type_expr, default: T = 0) -> (bool, T) { - if va.count <= 0 do return false, default; - ret := *cast(^T) va.data; - va.data = cast(rawptr) (cast(^u8) va.data + sizeof T); - va.count -= 1; - return true, ret; - } -} -// @NullProcHack + +// +// `null` in Onyx is simply the address 0, as a rawptr, so it implicitly +// casts to all other pointer types. +null :: cast(rawptr) 0 + +// +// `null_proc` is a special function that breaks the normal rules of type +// checking. `null_proc`, or any procedure marked with `#null`, is assignable +// to any function type, regardless of if the types match. For example, +// +// f: (i32) -> i32 = null_proc; +// +// Even though `null_proc` is a `() -> void` function, it bypasses that check +// and gets assigned to `f`. If f is called, there will be a runtime exception. +// This is by design. null_proc :: () -> void #null --- -null :: cast(rawptr) 0; +// // I find myself wanting to return a completely nullified string like the // one below that I decided to added a builtin binding for it. This might // go away at some point and would just need to be defined in every file. null_str :: str.{ null, 0 } + + +// +// +#thread_local context : OnyxContext; + +// +// OnyxContext :: struct { allocator : Allocator; temp_allocator : Allocator; @@ -64,15 +101,8 @@ OnyxContext :: struct { user_data_type: type_expr; } - -#thread_local context : OnyxContext; - -assert :: (cond: bool, msg: str, site := #callsite) { - if !cond { - context.assert_handler(msg, site); - } -} - +// +// #inject OnyxContext { set_user_data :: macro (c: ^OnyxContext, data: ^$T) { c.user_data = data; @@ -85,6 +115,17 @@ assert :: (cond: bool, msg: str, site := #callsite) { } } + + +// +// +assert :: (cond: bool, msg: str, site := #callsite) { + if !cond { + context.assert_handler(msg, site); + } +} + + // // Basic logging // @@ -311,7 +352,9 @@ any :: struct { } -// Represents a code block. Not constructable outside of using a '#quote' directive. +// Represents a code block that can be passed around at compile-time. +// This is commonly used with macros or polymorphic procedures to create +// very power extensions to the syntax. Code :: struct {_:i32;} @@ -337,7 +380,7 @@ Code :: struct {_:i32;} __initialize_data_segments :: () -> void --- // This is also a special compiler generated procedure that calls all procedures specified with -// #init, in the specified order. It should theoritically only be called once on the main thread. +// #init, in the specified order. It should theoretically only be called once on the main thread. __run_init_procedures :: () -> void --- // This overloaded procedure allow you to define an implicit rule for how to convert any value @@ -386,3 +429,20 @@ package_id :: #distinct u32 any_package :: cast(package_id) 0 #operator == macro (p1, p2: package_id) => cast(u32) p1 == cast(u32) p2; #operator != macro (p1, p2: package_id) => cast(u32) p1 != cast(u32) p2; + + + +// +// DEPRECATED THINGS +// + +// +// This is the special type of a paramter that was declared to have the type '...'. +// This is an old feature of the language now called 'untyped varargs'. It had +// a similar construction to varargs in C/C++. Because it is incredibly unsafe +// and not programmer friendly, this way of doing it has been deprecated in +// favor of using '..any', which provides type information along with the data. +vararg :: #type ^struct { + data: rawptr; + count: i32; +} diff --git a/core/conv/format.onyx b/core/conv/format.onyx index ddae9ea8..fbac7182 100644 --- a/core/conv/format.onyx +++ b/core/conv/format.onyx @@ -31,6 +31,23 @@ custom_formatters_initialized :: #init () { } } } + + format_procedures := get_procedures_with_tag(Custom_Format_Proc); + defer delete(^format_procedures); + + for p: format_procedures { + custom_format := p.tag; + custom_formatters[custom_format.type] = *cast(^(^Format_Output, ^Format, rawptr) -> void, ^p.func); + } + + + parse_procedures := get_procedures_with_tag(Custom_Parse_Proc); + defer delete(^parse_procedures); + + for p: parse_procedures { + custom_parse := p.tag; + custom_parsers[custom_parse.type] = *cast(^(rawptr, str, Allocator) -> bool, ^p.func); + } } } @@ -46,10 +63,18 @@ Custom_Format :: struct { format: (^Format_Output, ^Format, rawptr) -> void; } +Custom_Format_Proc :: struct { + type: type_expr; +} + Custom_Parse :: struct { parse: (rawptr, str, Allocator) -> bool; } +Custom_Parse_Proc :: struct { + type: type_expr; +} + // @Remove // old aliases to not break old programs str_format :: format str_format_va :: format_va diff --git a/core/time/time.onyx b/core/time/time.onyx index 0922c2fe..afe27d98 100644 --- a/core/time/time.onyx +++ b/core/time/time.onyx @@ -1,14 +1,9 @@ package core.time -// -// This module provides a thin wrapper for the builtin POSIX -// time functionality of: -// - localtime, gmtime -// - strptime, strftime -// +use core {os, conv} #if runtime.runtime != .Onyx { - #error "'core.time' should only be used with the Onyx runtime."; + #error "'core.time' should only be used with the Onyx runtime, for now."; } // @@ -26,9 +21,73 @@ Timestamp :: struct #size (sizeof u32 * 12) { isdst: i32; } +#inject Timestamp { + as_date :: (t: Timestamp) -> Date { + return Date.make(t.year + 1900, t.mon + 1, t.mday); + } + + from_date :: (d: Date) -> Timestamp { + return .{ + year = d.year + 1900, + mday = d.day, + mon = d.month + }; + } + + to_epoch :: to_epoch +} + +@conv.Custom_Format_Proc.{ Timestamp } +(output: ^conv.Format_Output, format: ^conv.Format, time: ^Timestamp) { + time_buf: [64] u8; + to_output := strftime(time_buf, "%Y-%m-%d %H:%M:%S", time); + + output->write(to_output); +} + +@conv.Custom_Parse_Proc.{ Timestamp } +(time: ^Timestamp, data: str, _: Allocator) -> bool { + return strptime(data, "%Y-%m-%d %H:%M:%S", time); +} + + +now :: () -> Timestamp { + current_time := os.time(); + + // + // Localtime operates on seconds, while os.time + // returns milliseconds. + return localtime(current_time / 1000); +} + +to_epoch :: __time_mktime + + +localtime :: #match #local {} + +#overload localtime :: __time_localtime -gmtime :: __time_gmtime -to_epoch :: __time_mktime + +#overload +localtime :: (seconds: u64) -> Timestamp { + t: Timestamp; + __time_localtime(seconds, ^t); + return t; +} + + +gmtime :: #match #local {} + +#overload +gmtime :: __time_gmtime + +#overload +gmtime :: (seconds: u64) -> Timestamp { + t: Timestamp; + __time_gmtime(seconds, ^t); + return t; +} + strftime :: (buf: [] u8, format: [] u8, tm: ^Timestamp) -> str { f := cast(cstr) core.alloc.from_stack(format.length + 1); @@ -268,12 +327,11 @@ parse_number_and_advance :: (buf: ^[] u8, result: ^i32, low, high, offset: i32) return false; } - #local { #foreign "onyx_runtime" { __time_localtime :: (time: u64, tm: ^Timestamp) -> void --- __time_gmtime :: (time: u64, tm: ^Timestamp) -> void --- - __time_mktime :: (tm: ^Timestamp) -> u64 --- + __time_mktime :: (tm: ^Timestamp) -> i64 --- __time_strftime :: (buf: [] u8, format: cstr, tm: ^Timestamp) -> u32 --- } } diff --git a/examples/12_varargs.onyx b/examples/12_varargs.onyx index dde67ff1..12bf339e 100644 --- a/examples/12_varargs.onyx +++ b/examples/12_varargs.onyx @@ -36,6 +36,10 @@ main :: (args: [] cstr) { typed_varargs(1, 2, 3, 4); + // This part about "untyped-varargs" is now entirely deprecated. The new + // way of using '..any' is so much better. The language still has support + // for '...' as the type of a parameter, but that will go away soon. + // // This is how you specify an untyped variadic procedure. Notice the extra '.' // in the type. The access the arguments, you need to use the builtin procedure, // 'vararg_get'. This procedure has two overloads that you can use: @@ -54,18 +58,19 @@ main :: (args: [] cstr) { // // printf :: (format: str, va: ...) -> void // printf("%i %f %l %d", int, float, long, double); - untyped_varargs :: (args: ...) { - print("Untyped variadic arguments: "); + // + // untyped_varargs :: (args: ...) { + // print("Untyped variadic arguments: "); - x: i32; - while vararg_get(args, ^x) { - printf("{} ", cast(rawptr) x); - } + // x: i32; + // while vararg_get(args, ^x) { + // printf("{} ", cast(rawptr) x); + // } - print("\n"); - } + // print("\n"); + // } - untyped_varargs(1.0f, 2.0f, 3.0f, 4.0f); + // untyped_varargs(1.0f, 2.0f, 3.0f, 4.0f); diff --git a/tests/vararg_test.onyx b/tests/vararg_test.onyx index 899930b5..d978577a 100644 --- a/tests/vararg_test.onyx +++ b/tests/vararg_test.onyx @@ -9,12 +9,14 @@ old_va_test :: (prefix: str, va: ..i32) { for v: va do println(v); } -new_va_test :: (prefix: str, va: ...) { +new_va_test :: (prefix: str, va: ..any) { println(prefix); for i: 0 .. va.count { - x : i32; - vararg_get(va, ^x); + // The right way to do it is this: + // x := * misc.any_as(va[i], i32); + + x := *cast(^i32, va[i].data); println(x); } }