improved usability of 'core.time'; started documenting builtin.onyx
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 11 Feb 2023 04:37:58 +0000 (22:37 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sat, 11 Feb 2023 04:37:58 +0000 (22:37 -0600)
core/builtin.onyx
core/conv/format.onyx
core/time/time.onyx
examples/12_varargs.onyx
tests/vararg_test.onyx

index 883a29db8113cbaeb753096c3377bc8136bb8778..74e78cedb233434dfa670db3cc376b80f5d442dc 100644 (file)
@@ -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;
+}
index ddae9ea809b8a80ee086792fdb5371e82f32a286..fbac718242e2e675146e75dbb125e03409bd204f 100644 (file)
@@ -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
index 0922c2fedd64f909d4939d9ba96e40a20efd5099..afe27d98013bbefb7a1687d4611689de3ab024ed 100644 (file)
@@ -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 ---
     }
 }
index dde67ff1a47d82314acb90b2bcc3027f2234f848..12bf339ebced9f569aa8b8ea9243ae7dd5a852b3 100644 (file)
@@ -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);
 
 
 
index 899930b582698dc702146f654442a9dd81ec2dfc..d978577a60c2e3263f88fab917fc96862f4fcb8e 100644 (file)
@@ -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);
     }
 }