From 2bf00d9b3f5a694bdc6d8b2c76be7a278f02f40b Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Wed, 24 Apr 2024 15:15:38 +0200 Subject: [PATCH 01/25] [sys/linux]: Add flags parameter to execveat and fix execve on arm64 --- core/sys/linux/constants.odin | 2 ++ core/sys/linux/sys.odin | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 51f7db68f..389ff1417 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -5,6 +5,8 @@ package linux that relative paths are relative to current directory. */ AT_FDCWD :: Fd(-100) +AT_EMPTY_PATH :: 0x1000 +AT_SYMLINK_NOFOLLOW :: 0x100 /* Special value to put into timespec for utimensat() to set timestamp to the current time. diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 63fb3b776..22fe2ab50 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -769,8 +769,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) - return Errno(-ret) + return execveat(AT_FDCWD, name, argv, envp, 0) } } @@ -2803,8 +2802,8 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring) -> (Errno) { - ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: i32) -> (Errno) { + ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, flags) return Errno(-ret) } From a2ad66cd9d5e93ce8789d1a1e088b254922cd88f Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Wed, 24 Apr 2024 15:32:43 +0200 Subject: [PATCH 02/25] [sys/linux]: Add clone syscall and use it in fork for arm64 --- core/sys/linux/sys.odin | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 22fe2ab50..1653a65dd 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -729,7 +729,22 @@ getsockopt :: proc { getsockopt_base, } -// TODO(flysand): clone (probably not in this PR, maybe not ever) +/* + Creates a new ("child") process, in a manner similar to fork. + Available since Linux 1.0? (<2.4) + + Note(flysand): this syscall is not documented, but the bottom 8 bits of flags + are for exit signal +*/ +clone :: proc "contextless" (flags: u64, stack: rawptr, parent_tid, child_tid: ^i32, tls: u64) -> (i64, Errno) { + when ODIN_ARCH == .amd64 { + ret := syscall(SYS_clone, flags, stack, parent_tid, child_tid, tls) + return errno_unwrap(ret, i64) + } else { + ret := syscall(SYS_clone, flags, stack, parent_tid, tls, child_tid) + return errno_unwrap(ret, i64) + } +} /* Creates a copy of the running process. @@ -737,10 +752,9 @@ getsockopt :: proc { */ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { - // Note(flysand): this syscall is not documented, but the bottom 8 bits of flags - // are for exit signal - ret := syscall(SYS_clone, Signal.SIGCHLD) - return errno_unwrap(ret, Pid) + ret, err := clone(u64(Signal.SIGCHLD), nil, nil, nil, 0) + if err != .NONE do return Pid(ret), err + return Pid(ret), .NONE } else { ret := syscall(SYS_fork) return errno_unwrap(ret, Pid) From b0fe6212bb1f55c367991c23c22cb1fa35da34e4 Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sat, 27 Apr 2024 13:21:04 +0200 Subject: [PATCH 03/25] [sys/linux]: Fix return statement --- core/sys/linux/sys.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 1653a65dd..5b2948c8f 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -754,7 +754,7 @@ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { ret, err := clone(u64(Signal.SIGCHLD), nil, nil, nil, 0) if err != .NONE do return Pid(ret), err - return Pid(ret), .NONE + return Pid(ret), err } else { ret := syscall(SYS_fork) return errno_unwrap(ret, Pid) From 7f301790d0e1828082f8f314475a332084026dd5 Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sun, 28 Apr 2024 11:48:18 +0200 Subject: [PATCH 04/25] [sys/linux] Change flags parameter of execveat to bit_set --- core/sys/linux/bits.odin | 8 ++++++++ core/sys/linux/constants.odin | 2 -- core/sys/linux/sys.odin | 6 +++--- core/sys/linux/types.odin | 5 +++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index ad519e1cd..88c4f40c9 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -1756,3 +1756,11 @@ EPoll_Ctl_Opcode :: enum i32 { DEL = 2, MOD = 3, } + +/* + Bits for execveat(2) flags. +*/ +Execveat_Flags_Bits :: enum { + AT_SYMLINK_NOFOLLOW = 8, + AT_EMPTY_PATH = 12, +} diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 389ff1417..51f7db68f 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -5,8 +5,6 @@ package linux that relative paths are relative to current directory. */ AT_FDCWD :: Fd(-100) -AT_EMPTY_PATH :: 0x1000 -AT_SYMLINK_NOFOLLOW :: 0x100 /* Special value to put into timespec for utimensat() to set timestamp to the current time. diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 5b2948c8f..a9ec3c24e 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -783,7 +783,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - return execveat(AT_FDCWD, name, argv, envp, 0) + return execveat(AT_FDCWD, name, argv, envp, nil) } } @@ -2816,8 +2816,8 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: i32) -> (Errno) { - ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, flags) +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags) -> (Errno) { + ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 677bac7e0..e7ecfc609 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -1238,3 +1238,8 @@ EPoll_Event :: struct #packed { events: EPoll_Event_Kind, data: EPoll_Data, } + +/* + Flags for execveat(2) syscall. +*/ +Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32] From d1a205e2cfb44df31801201fc8818e02527b45dc Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sun, 28 Apr 2024 11:56:19 +0200 Subject: [PATCH 05/25] [sys/linux]: Remove clone syscall and call it directly in fork on arm64 --- core/sys/linux/sys.odin | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index a9ec3c24e..57827d45c 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -729,32 +729,14 @@ getsockopt :: proc { getsockopt_base, } -/* - Creates a new ("child") process, in a manner similar to fork. - Available since Linux 1.0? (<2.4) - - Note(flysand): this syscall is not documented, but the bottom 8 bits of flags - are for exit signal -*/ -clone :: proc "contextless" (flags: u64, stack: rawptr, parent_tid, child_tid: ^i32, tls: u64) -> (i64, Errno) { - when ODIN_ARCH == .amd64 { - ret := syscall(SYS_clone, flags, stack, parent_tid, child_tid, tls) - return errno_unwrap(ret, i64) - } else { - ret := syscall(SYS_clone, flags, stack, parent_tid, tls, child_tid) - return errno_unwrap(ret, i64) - } -} - /* Creates a copy of the running process. Available since Linux 1.0. */ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { - ret, err := clone(u64(Signal.SIGCHLD), nil, nil, nil, 0) - if err != .NONE do return Pid(ret), err - return Pid(ret), err + ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0)) + return errno_unwrap(ret, Pid) } else { ret := syscall(SYS_fork) return errno_unwrap(ret, Pid) From 37b026cb9bdd29aa657a54b76d2595bef40ff8c8 Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sun, 28 Apr 2024 12:00:40 +0200 Subject: [PATCH 06/25] [sys/linux] Directly call syscall in execve on arm64 --- core/sys/linux/sys.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 57827d45c..887feb49d 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -765,7 +765,8 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - return execveat(AT_FDCWD, name, argv, envp, nil) + ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0)) + return Errno(-ret) } } From 7237f9c9f850c8a952037938d2fdfca4daf3caa8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 19 Jul 2024 20:47:26 +0200 Subject: [PATCH 07/25] Help text default -o:none -> -o:minimal. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 00734c050..41a95338b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2172,7 +2172,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { if (LB_USE_NEW_PASS_SYSTEM) { print_usage_line(3, "-o:aggressive"); } - print_usage_line(2, "The default is -o:none."); + print_usage_line(2, "The default is -o:minimal."); print_usage_line(0, ""); } From 15997d2a90432a53dac801707c8bb1f41b834f08 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 20 Jul 2024 12:47:00 +1100 Subject: [PATCH 08/25] [vendor/x11]: Fix missing argument in XDefaultDepth, wrong types in CreateSimpleWindow --- vendor/x11/xlib/xlib_procs.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 17d172172..207b3f6bc 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -49,7 +49,7 @@ foreign xlib { DisplayString :: proc(display: ^Display) -> cstring --- // Display macros (defaults) DefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- - DefaultDepth :: proc(display: ^Display) -> i32 --- + DefaultDepth :: proc(display: ^Display, screen_no: i32) -> i32 --- DefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- DefaultRootWindow :: proc(display: ^Display) -> Window --- DefaultScreen :: proc(display: ^Display) -> i32 --- @@ -138,8 +138,8 @@ foreign xlib { width: u32, height: u32, bordersz: u32, - border: int, - bg: int, + border: uint, + bg: uint, ) -> Window --- DestroyWindow :: proc(display: ^Display, window: Window) --- DestroySubwindows :: proc(display: ^Display, window: Window) --- From 0c78cab336897080fafd3d8ff31330b939773713 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 20 Jul 2024 12:30:06 +1100 Subject: [PATCH 09/25] [time/datetime]: Document package datetime --- core/time/datetime/constants.odin | 66 ++++++++++- core/time/datetime/datetime.odin | 172 ++++++++++++++++++++++++++++- core/time/datetime/internal.odin | 1 + core/time/datetime/validation.odin | 44 +++++++- 4 files changed, 273 insertions(+), 10 deletions(-) diff --git a/core/time/datetime/constants.odin b/core/time/datetime/constants.odin index a2a02838c..5f336ef4a 100644 --- a/core/time/datetime/constants.odin +++ b/core/time/datetime/constants.odin @@ -1,16 +1,46 @@ package datetime -// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) -// | Midnight Monday, January 3, 1 A.D. (Julian) +/* +Type representing a mononotic day number corresponding to a date. + + Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) + | Midnight Monday, January 3, 1 A.D. (Julian) +*/ Ordinal :: i64 + +/* +*/ EPOCH :: Ordinal(1) -// Minimum and maximum dates and ordinals. Chosen for safe roundtripping. +/* +Minimum valid value for date. + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1} + +/* +Maximum valid value for date + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31} + +/* +Minimum value for an ordinal +*/ MIN_ORD :: Ordinal(-9_223_372_036_854_775_234) + +/* +Maximum value for an ordinal +*/ MAX_ORD :: Ordinal( 9_223_372_036_854_774_869) +/* +Possible errors returned by datetime functions. +*/ Error :: enum { None, Invalid_Year, @@ -24,12 +54,22 @@ Error :: enum { Invalid_Delta, } +/* +A type representing a date. + +The minimum and maximum values for a year can be found in `MIN_DATE` and +`MAX_DATE` constants. The `month` field can range from 1 to 12, and the day +ranges from 1 to however many days there are in the specified month. +*/ Date :: struct { year: i64, month: i8, day: i8, } +/* +A type representing a time within a single day within a nanosecond precision. +*/ Time :: struct { hour: i8, minute: i8, @@ -37,17 +77,30 @@ Time :: struct { nano: i32, } +/* +A type representing datetime. +*/ DateTime :: struct { using date: Date, using time: Time, } +/* +A type representing a difference between two instances of datetime. + +**Note**: All fields are i64 because we can also use it to add a number of +seconds or nanos to a moment, that are then normalized within their respective +ranges. +*/ Delta :: struct { - days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment, - seconds: i64, // that are then normalized within their respective ranges. + days: i64, + seconds: i64, nanos: i64, } +/* +Type representing one of the months. +*/ Month :: enum i8 { January = 1, February, @@ -63,6 +116,9 @@ Month :: enum i8 { December, } +/* +Type representing one of the weekdays. +*/ Weekday :: enum i8 { Sunday = 0, Monday, diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index 938b4a368..fc9780e3b 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -1,56 +1,113 @@ /* - Calendrical conversions using a proleptic Gregorian calendar. +Calendrical conversions using a proleptic Gregorian calendar. - Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz +Implemented using formulas from: Calendrical Calculations Ultimate Edition, +Reingold & Dershowitz */ package datetime import "base:intrinsics" -// Procedures that return an Ordinal +/* +Obtain an ordinal from a date. +This procedure converts the specified date into an ordinal. If the specified +date is not a valid date, an error is returned. +*/ date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { validate(date) or_return return unsafe_date_to_ordinal(date), .None } +/* +Obtain an ordinal from date components. + +This procedure converts the specified date, provided by its individual +components, into an ordinal. If the specified date is not a valid date, an error +is returned. +*/ components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) { validate(year, month, day) or_return return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None } -// Procedures that return a Date +/* +Obtain date using an Ordinal. +This provedure converts the specified ordinal into a date. If the ordinal is not +a valid ordinal, an error is returned. +*/ ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { validate(ordinal) or_return return unsafe_ordinal_to_date(ordinal), .None } +/* +Obtain a date from date components. + +This procedure converts date components, specified by a year, a month and a day, +into a date object. If the provided date components don't represent a valid +date, an error is returned. +*/ components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) { validate(year, month, day) or_return return Date{i64(year), i8(month), i8(day)}, .None } +/* +Obtain time from time components. + +This procedure converts time components, specified by an hour, a minute, a second +and nanoseconds, into a time object. If the provided time components don't +represent a valid time, an error is returned. +*/ components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) { validate(hour, minute, second, nanos) or_return return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None } +/* +Obtain datetime from components. + +This procedure converts date components and time components into a datetime object. +If the provided date components or time components don't represent a valid +datetime, an error is returned. +*/ components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) { date := components_to_date(year, month, day) or_return time := components_to_time(hour, minute, second, nanos) or_return return {date, time}, .None } +/* +Obtain an datetime from an ordinal. + +This procedure converts the value of an ordinal into a datetime. Since the +ordinal only has the amount of days, the resulting time in the datetime +object will always have the time equal to `00:00:00.000`. +*/ ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { d := ordinal_to_date(ordinal) or_return return {Date(d), {}}, .None } +/* +Calculate the weekday from an ordinal. + +This procedure takes the value of an ordinal and returns the day of week for +that ordinal. +*/ day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { return Weekday((ordinal - EPOCH + 1) %% 7) } +/* +Calculate the difference between two dates. + +This procedure calculates the difference between two dates `a - b`, and returns +a delta between the two dates in `days`. If either `a` or `b` is not a valid +date, an error is returned. +*/ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { ord_a := date_to_ordinal(a) or_return ord_b := date_to_ordinal(b) or_return @@ -59,6 +116,16 @@ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) return } +/* +Calculate the difference between two datetimes. + +This procedure calculates the difference between two datetimes, `a - b`, and +returns a delta between the two dates. The difference is returned in all three +fields of the `Delta` struct: the difference in days, the difference in seconds +and the difference in nanoseconds. + +If either `a` or `b` is not a valid datetime, an error is returned. +*/ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) { ord_a := date_to_ordinal(a) or_return ord_b := date_to_ordinal(b) or_return @@ -73,19 +140,42 @@ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: return } +/* +Calculate a difference between two deltas. +*/ subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) { delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos} delta = normalize_delta(delta) or_return return } + +/* +Calculate a difference between two datetimes, dates or deltas. +*/ sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} +/* +Add certain amount of days to a date. + +This procedure adds the specified amount of days to a date and returns a new +date. The new date would have happened the specified amount of days after the +specified date. +*/ add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) { ord := date_to_ordinal(a) or_return ord += days return ordinal_to_date(ord) } +/* +Add delta to a date. + +This procedure adds a delta to a date, and returns a new date. The new date +would have happened the time specified by `delta` after the specified date. + +**Note**: The delta is assumed to be normalized. That is, if it contains seconds +or milliseconds, regardless of the amount only the days will be added. +*/ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) { ord := date_to_ordinal(a) or_return // Because the input is a Date, we add only the days from the Delta. @@ -93,6 +183,13 @@ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, return ordinal_to_date(ord) } +/* +Add delta to datetime. + +This procedure adds a delta to a datetime, and returns a new datetime. The new +datetime would have happened the time specified by `delta` after the specified +datetime. +*/ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) { days := date_to_ordinal(a) or_return @@ -110,8 +207,18 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return return } + +/* +Add days to a date, delta to a date or delta to datetime. +*/ add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} +/* +Obtain the day number in a year + +This procedure returns the number of the day in a year, starting from 1. If +the date is not a valid date, an error is returned. +*/ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { validate(date) or_return @@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { return } +/* +Obtain the remaining number of days in a year. + +This procedure returns the number of days between the specified date and +December 31 of the same year. If the date is not a valid date, an error is +returned. +*/ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) { // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year validate(date) or_return @@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: return delta.days, .None } +/* +Obtain the last day of a given month on a given year. + +This procedure returns the amount of days in a specified month on a specified +date. If the specified year or month is not valid, an error is returned. +*/ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { // Not using formula 2.27 from the book. This is far simpler and gives the same answer. @@ -140,16 +260,33 @@ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) return } +/* +Obtain the new year date of a given year. + +This procedure returns the January 1st date of the specified year. If the year +is not valid, an error is returned. +*/ new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) { validate(year, 1, 1) or_return return {year, 1, 1}, .None } +/* +Obtain the end year of a given date. + +This procedure returns the December 31st date of the specified year. If the year +is not valid, an error is returned. +*/ year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) { validate(year, 12, 31) or_return return {year, 12, 31}, .None } +/* +Obtain the range of dates for a given year. + +This procedure returns dates, for every day of a given year in a slice. +*/ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) { is_leap := is_leap_year(year) @@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang return } +/* +Normalize the delta. + +This procedure normalizes the delta in such a way that the number of seconds +is between 0 and the number of seconds in the day and nanoseconds is between +0 and 10^9. + +If the value for `days` overflows during this operation, an error is returned. +*/ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) { // Distribute nanos into seconds and remainder seconds, nanos := divmod(delta.nanos, 1e9) @@ -194,6 +340,12 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: // The following procedures don't check whether their inputs are in a valid range. // They're still exported for those who know their inputs have been validated. +/* +Obtain an ordinal from a date. + +This procedure converts a date into an ordinal. If the date is not a valid date, +the result is unspecified. +*/ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { year_minus_one := date.year - 1 @@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) return } +/* +Obtain a year and a day of the year from an ordinal. + +This procedure returns the year and the day of the year of a given ordinal. +Of the ordinal is outside of its valid range, the result is unspecified. +*/ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { // Days after epoch d0 := ordinal - EPOCH @@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d return year + 1, day_ordinal } +/* +Obtain a date from an ordinal. + +This procedure converts an ordinal into a date. If the ordinal is outside of +its valid range, the result is unspecified. +*/ unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) { year, _ := unsafe_ordinal_to_year(ordinal) diff --git a/core/time/datetime/internal.odin b/core/time/datetime/internal.odin index 45c2b99ab..e7129548e 100644 --- a/core/time/datetime/internal.odin +++ b/core/time/datetime/internal.odin @@ -1,3 +1,4 @@ +//+private package datetime // Internal helper functions for calendrical conversions diff --git a/core/time/datetime/validation.odin b/core/time/datetime/validation.odin index 87d5aa1cd..0a66833b0 100644 --- a/core/time/datetime/validation.odin +++ b/core/time/datetime/validation.odin @@ -1,14 +1,29 @@ package datetime - // Validation helpers + +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Check for errors in date formation. + +This procedure validates all fields of a date, and if any of the fields is +outside of allowed range, an error is returned. +*/ validate_date :: proc "contextless" (date: Date) -> (err: Error) { return validate(date.year, date.month, date.day) } +/* +Check for errors in date formation given date components. + +This procedure checks whether a date formed by the specified year month and a +day is a valid date. If not, an error is returned. +*/ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) { if year < MIN_DATE.year || year > MAX_DATE.year { return .Invalid_Year @@ -29,6 +44,12 @@ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #a return .None } +/* +Check for errors in Ordinal + +This procedure checks if the ordinal is in a valid range for roundtrip +conversions with the dates. If not, an error is returned. +*/ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { if ordinal < MIN_ORD || ordinal > MAX_ORD { return .Invalid_Ordinal @@ -36,10 +57,22 @@ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { return } +/* +Check for errors in time formation + +This procedure checks whether time has all fields in valid ranges, and if not +an error is returned. +*/ validate_time :: proc "contextless" (time: Time) -> (err: Error) { return validate(time.hour, time.minute, time.second, time.nano) } +/* +Check for errors in time formed by its components. + +This procedure checks whether the time formed by its components is valid, and +if not an error is returned. +*/ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) { if hour < 0 || hour > 23 { return .Invalid_Hour @@ -56,12 +89,21 @@ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minut return .None } +/* +Check for errors in datetime formation. + +This procedure checks whether all fields of date and time in the specified +datetime are valid, and if not, an error is returned. +*/ validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) { validate(datetime.date) or_return validate(datetime.time) or_return return .None } +/* +Check for errors in date, time or datetime. +*/ validate :: proc{ validate_date, validate_year_month_day, From b3ca2d5e0a91be3ef3698aa2d626cdff70c9b487 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 20 Jul 2024 16:40:38 +1100 Subject: [PATCH 10/25] [time]: Document all functions --- core/time/iso8601.odin | 88 +++++++++++-- core/time/perf.odin | 92 +++++++++++-- core/time/rfc3339.odin | 92 +++++++++++-- core/time/time.odin | 284 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 512 insertions(+), 44 deletions(-) diff --git a/core/time/iso8601.odin b/core/time/iso8601.odin index 528e0b00a..f00107226 100644 --- a/core/time/iso8601.odin +++ b/core/time/iso8601.odin @@ -3,23 +3,62 @@ package time import dt "core:time/datetime" -// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it. -// Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an ISO 8601 string into a time with UTC offset applied to it. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string. In case the timezone offset +is specified in the string, that timezone is applied to time. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time represented by `iso_datetime`, with UTC offset applied. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int - res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) res._nsec += (i64(-offset) * i64(Minute)) return res, consumed } -// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Note: Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an ISO 8601 string into a time and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time in UTC. +- `utc_offset`: The UTC offset of the time, in minutes. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := iso8601_to_components(iso_datetime) if count == 0 { @@ -37,9 +76,32 @@ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) - } } -// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +/* +Parse an ISO 8601 string into a datetime and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns datetime, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed + +**Returns**: +- `res`: The parsed datetime, in UTC. +- `utc_offset`: The UTC offset, in minutes. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- This procedure performs no validation on whether components are valid, + e.g. it'll return hour = 25 if that's what it's given in the specified + string. +*/ iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) if !ok { diff --git a/core/time/perf.odin b/core/time/perf.odin index 123d67eca..784d7acd6 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -3,18 +3,39 @@ package time import "base:runtime" import "base:intrinsics" +/* +Type representing monotonic time, useful for measuring durations. +*/ Tick :: struct { _nsec: i64, // relative amount } + +/* +Obtain the current tick. +*/ tick_now :: proc "contextless" () -> Tick { return _tick_now() } +/* +Obtain the difference between ticks. +*/ tick_diff :: proc "contextless" (start, end: Tick) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Incrementally obtain durations since last tick. + +This procedure returns the duration between the current tick and the tick +stored in `prev` pointer, and then stores the current tick in location, +specified by `prev`. If the prev pointer contains an zero-initialized tick, +then the returned duration is 0. + +This procedure is meant to be used in a loop, or in other scenarios, where one +might want to obtain time between multiple ticks at specific points. +*/ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { d: Duration t := tick_now() @@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { return d } +/* +Obtain the duration since last tick. +*/ tick_since :: proc "contextless" (start: Tick) -> Duration { return tick_diff(start, tick_now()) } - +/* +Capture the duration the code in the current scope takes to execute. +*/ @(deferred_in_out=_tick_duration_end) SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { return tick_now() } - _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { d^ = tick_since(t) } @@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD { } } +/* +Check if the CPU has invariant TSC. + +This procedure checks if the CPU contains an invariant TSC (Time stamp counter). +Invariant TSC is a feature of modern processors that allows them to run their +TSC at a fixed frequency, independent of ACPI state, and CPU frequency. +*/ has_invariant_tsc :: proc "contextless" () -> bool { when ODIN_ARCH == .amd64 { return x86_has_invariant_tsc() @@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool { return false } +/* +Obtain the CPU's TSC frequency, in hertz. + +This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU +doesn't have an invariant TSC, this procedure returns with an error. Otherwise +an attempt is made to fetch the TSC frequency from the OS. If this fails, +the frequency is obtained by sleeping for the specified amount of time and +dividing the readings from TSC by the duration of the sleep. + +The duration of sleep can be controlled by `fallback_sleep` parameter. +*/ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) { if !has_invariant_tsc() { return 0, false @@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool return hz, true } -/* - Benchmark helpers -*/ +// Benchmark helpers +/* +Errors returned by the `benchmark()` procedure. +*/ Benchmark_Error :: enum { Okay = 0, Allocation_Error, } +/* +Options for benchmarking. +*/ Benchmark_Options :: struct { + // The initialization procedure. `benchmark()` will call this before taking measurements. setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The procedure to benchmark. bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The deinitialization procedure. teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), - + // Field to be used by `bench()` procedure for any purpose. rounds: int, + // Field to be used by `bench()` procedure for any purpose. bytes: int, + // Field to be used by `bench()` procedure for any purpose. input: []u8, - + // `bench()` writes to specify the count of elements processed. count: int, + // `bench()` writes to specify the number of bytes processed. processed: int, + // `bench()` can write the output slice here. output: []u8, // Unused for hash benchmarks + // `bench()` can write the output hash here. hash: u128, - - /* - Performance - */ + // `benchmark()` procedure will output the duration of benchmark duration: Duration, + // `benchmark()` procedure will output the average count of elements + // processed per second, using the `count` field of this struct. rounds_per_second: f64, + // `benchmark()` procedure will output the average number of megabytes + // processed per second, using the `processed` field of this struct. megabytes_per_second: f64, } +/* +Benchmark a procedure. + +This procedure produces a benchmark. The procedure specified in the `bench` +field of the `options` parameter will be benchmarked. The following metrics +can be obtained: + +- Run time of the procedure +- Number of elements per second processed on average +- Number of bytes per second this processed on average + +In order to obtain these metrics, the `bench()` procedure writes to `options` +struct the number of elements or bytes it has processed. +*/ benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) { assert(options != nil) assert(options.bench != nil) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 0a2d431b7..e4c6565d6 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -4,10 +4,33 @@ package time import dt "core:time/datetime" -// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it. -// Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an RFC 3339 string into time with a UTC offset applied to it. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time that was represented by the RFC 3339 string, with the UTC +offset applied to it. + +**Inputs**: +- `rfc_datetime`: An RFC 3339 string to parse. +- `is_leap`: Optional output parameter specifying whether the moment was a leap + second. + +**Returns**: +- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the RFC 3339 string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int @@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: return res, consumed } -// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Note: Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an RFC 3339 string into a time and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time, in UTC and a UTC offset, in minutes, that were represented +by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to be parsed. +- `is_leap`: Optional output parameter specifying whether the moment was a + leap second. + +**Returns**: +- `res`: The time, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime) if count == 0 { @@ -38,9 +84,31 @@ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) - } } -// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +/* +Parse an RFC 3339 string into a datetime and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the datetime, in UTC and the UTC offset, in minutes, that were +represented by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to parse. + +**Returns**: +- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: Number of bytes consumed by parsing the string. + +Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +*/ rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime) if !ok { diff --git a/core/time/time.odin b/core/time/time.odin index 4ea5afc70..ef2bc0aed 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -3,24 +3,72 @@ package time import "base:intrinsics" import dt "core:time/datetime" +/* +Type representing duration, with nanosecond precision. +*/ Duration :: distinct i64 +/* +The duration equal to one nanosecond (1e-9 seconds). +*/ Nanosecond :: Duration(1) + +/* +The duration equal to one microsecond (1e-6 seconds). +*/ Microsecond :: 1000 * Nanosecond + +/* +The duration equal to one millisecond (1e-3 seconds). +*/ Millisecond :: 1000 * Microsecond + +/* +The duration equal to one second. +*/ Second :: 1000 * Millisecond + +/* +The duration equal to one minute (60 seconds). +*/ Minute :: 60 * Second + +/* +The duration equal to one hour (3600 seconds). +*/ Hour :: 60 * Minute +/* +Minimum representable duration. +*/ MIN_DURATION :: Duration(-1 << 63) + +/* +Maximum representable duration. +*/ MAX_DURATION :: Duration(1<<63 - 1) +/* +Value specifying whether the time procedures are supported by the current +platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Specifies time since the UNIX epoch, with nanosecond precision. + +Capable of representing any time within the following range: + +- `min: 1677-09-21 00:12:44.145224192 +0000 UTC` +- `max: 2262-04-11 23:47:16.854775807 +0000 UTC` +*/ Time :: struct { _nsec: i64, // Measured in UNIX nanonseconds } +/* +Type representing a month. +*/ Month :: enum int { January = 1, February, @@ -36,6 +84,9 @@ Month :: enum int { December, } +/* +Type representing a weekday. +*/ Weekday :: enum int { Sunday = 0, Monday, @@ -46,20 +97,37 @@ Weekday :: enum int { Saturday, } +/* +Type representing a stopwatch. + +The stopwatch is used for measuring the total time in multiple "runs". When the +stopwatch is started, it starts counting time. When the stopwatch is stopped, +the difference in time between the last start and the stop is added to the +total. When the stopwatch resets, the total is reset. +*/ Stopwatch :: struct { running: bool, _start_time: Tick, _accumulation: Duration, } +/* +Obtain the current time. +*/ now :: proc "contextless" () -> Time { return _now() } +/* +Sleep for the specified duration. +*/ sleep :: proc "contextless" (d: Duration) { _sleep(d) } +/* +Start the stopwatch. +*/ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { if !stopwatch.running { stopwatch._start_time = tick_now() @@ -67,6 +135,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Stop the stopwatch. +*/ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { if stopwatch.running { stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) @@ -74,11 +145,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Reset the stopwatch. +*/ stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch._accumulation = {} stopwatch.running = false } +/* +Obtain the total time, counted by the stopwatch. + +This procedure obtains the total time, counted by the stopwatch. If the stopwatch +isn't stopped at the time of calling this procedure, the time between the last +start and the current time is also accounted for. +*/ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { if !stopwatch.running { return stopwatch._accumulation @@ -86,40 +167,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) } +/* +Calculate the duration elapsed between two times. +*/ diff :: proc "contextless" (start, end: Time) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Calculate the duration elapsed since a specific time. +*/ since :: proc "contextless" (start: Time) -> Duration { return diff(start, now()) } +/* +Obtain the number of nanoseconds in a duration. +*/ duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } + +/* +Obtain the number of microseconds in a duration. +*/ duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } + +/* +Obtain the number of milliseconds in a duration. +*/ duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } + +/* +Obtain the number of seconds in a duration. +*/ duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } + +/* +Obtain the number of minutes in a duration. +*/ duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } + +/* +Obtain the number of hours in a duration. +*/ duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } +/* +Round a duration to a specific unit. + +This procedure rounds the duration to a specific unit. + +**Inputs**: +- `d`: The duration to round. +- `m`: The unit to round to. + +**Returns**: +- The duration `d`, rounded to the unit specified by `m`. + +**Example**: + +In order to obtain the rough amount of seconds in a duration, the following call +can be used: + +``` +time.duration_round(my_duration, time.Second) +``` + +**Note**: Any duration can be supplied as a unit. +*/ duration_round :: proc "contextless" (d, m: Duration) -> Duration { _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { return u64(x)+u64(x) < u64(y) @@ -149,50 +282,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration { return MAX_DURATION } +/* +Truncate the duration to the specified unit. + +This procedure truncates the duration `d` to the unit specified by `m`. + +**Inputs**: +- `d`: The duration to truncate. +- `m`: The unit to truncate to. + +**Returns**: +- The duration `d`, truncated to the unit specified by `m`. + +**Example**: + +In order to obtain the amount of whole seconds in a duration, the following call +can be used: + +``` +time.duration_round(my_duration, time.Second) +``` + +**Note**: Any duration can be supplied as a unit. +*/ duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { return d if m <= 0 else d - d%m } +/* +Parse time into date components. +*/ date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { year, month, day, _ = _abs_date(_time_abs(t), true) return } +/* +Obtain the year of the date specified by time. +*/ year :: proc "contextless" (t: Time) -> (year: int) { year, _, _, _ = _date(t, true) return } +/* +Obtain the month of the date specified by time. +*/ month :: proc "contextless" (t: Time) -> (month: Month) { _, month, _, _ = _date(t, true) return } +/* +Obtain the day of the date specified by time. +*/ day :: proc "contextless" (t: Time) -> (day: int) { _, _, day, _ = _date(t, true) return } +/* +Obtain the week day of the date specified by time. +*/ weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) { abs := _time_abs(t) sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK return Weekday(int(sec) / SECONDS_PER_DAY) } +/* +Obtain the time components from a time, a duration or a stopwatch's total. +*/ clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } +/* +Obtain the time components from a time. +*/ clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { return clock_from_seconds(_time_abs(t)) } +/* +Obtain the time components from a duration. +*/ clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { return clock_from_seconds(u64(d/1e9)) } +/* +Obtain the time components from a stopwatch's total. +*/ clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { return clock_from_duration(stopwatch_duration(s)) } +/* +Obtain the time components from the number of seconds. +*/ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { sec = int(nsec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR @@ -202,10 +388,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { return } +/* +Read the timestamp counter of the CPU. +*/ read_cycle_counter :: proc "contextless" () -> u64 { return u64(intrinsics.read_cycle_counter()) } +/* +Obtain time from unix seconds and unix nanoseconds. +*/ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { sec, nsec := sec, nsec if nsec < 0 || nsec >= 1e9 { @@ -220,31 +412,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec)} } +/* +Obtain time from unix nanoseconds. +*/ from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { return Time{nsec} } +/* +Alias for `time_to_unix`. +*/ to_unix_seconds :: time_to_unix + +/* +Obtain the unix seconds from a time. +*/ time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 } +/* +Alias for `time_to_unix_nano`. +*/ to_unix_nanoseconds :: time_to_unix_nano + +/* +Obtain the unix nanoseconds from a time. +*/ time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec } +/* +Add duration to a time. +*/ time_add :: proc "contextless" (t: Time, d: Duration) -> Time { return Time{t._nsec + i64(d)} } -// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ -// -// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case. -// On Windows it depends but is comparable with regular sleep in the worst case. -// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to -// tell Windows to use a more accurate timer for your process. -// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`. +/* +Accurate sleep + +This procedure sleeps for the duration specified by `d`, very accurately. + +**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/) + +**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case. + +**Note(windows)**: The accuracy depends but is comparable with regular sleep in +the worst case. To get the same kind of accuracy as on Linux, have your program +call `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer +for your process. Additionally your program should call `windows.timeEndPeriod(1)` +once you're done with `accurate_sleep`. +*/ accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration @@ -362,6 +582,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon return } +/* +Convert datetime components into time. + +This procedure calculates the time from datetime components supplied in the +arguments to this procedure. If the datetime components don't represent a valid +datetime, the function returns `false` in the second argument. +*/ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) { this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec) if err != .None { @@ -370,6 +597,12 @@ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_in return compound_to_time(this_date) } +/* +Convert datetime into time. + +If the datetime represents a time outside of a valid range, `false` is returned +as the second return value. See `Time` for the representable range. +*/ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) { unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} delta, err := dt.sub(datetime, unix_epoch) @@ -387,12 +620,21 @@ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: return Time{_nsec=i64(nanoseconds)}, true } +/* +Convert datetime components into time. +*/ datetime_to_time :: proc{components_to_time, compound_to_time} +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Days before each month in a year, not counting the leap day on february 29th. +*/ @(rodata) days_before := [?]i32{ 0, @@ -410,11 +652,37 @@ days_before := [?]i32{ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, } - +/* +Number of seconds in a minute (without leap seconds). +*/ SECONDS_PER_MINUTE :: 60 + +/* +Number of seconds in an hour (without leap seconds). +*/ SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE + +/* +Number of seconds in a day (without leap seconds). +*/ SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR + +/* +Number of seconds in a week (without leap seconds). +*/ SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY + +/* +Days in 400 years, with leap days. +*/ DAYS_PER_400_YEARS :: 365*400 + 97 + +/* +Days in 100 years, with leap days. +*/ DAYS_PER_100_YEARS :: 365*100 + 24 + +/* +Days in 4 years, with leap days. +*/ DAYS_PER_4_YEARS :: 365*4 + 1 From 2385e1ddd9cdba029e0d192896c07011f70259f2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 20 Jul 2024 14:24:01 +0200 Subject: [PATCH 11/25] Update LUA imports for Darwin. --- vendor/lua/5.1/lua.odin | 8 ++++++-- vendor/lua/5.2/lua.odin | 8 ++++++-- vendor/lua/5.3/lua.odin | 8 ++++++-- vendor/lua/5.4/lua.odin | 16 ++++++---------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/vendor/lua/5.1/lua.odin b/vendor/lua/5.1/lua.odin index b53c61bb3..8ab315bcd 100644 --- a/vendor/lua/5.1/lua.odin +++ b/vendor/lua/5.1/lua.odin @@ -15,16 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.1" } else { - foreign import lib "system:liblua.so.5.1" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.1" } else { - foreign import lib "system:liblua5.1.a" + #panic("LUA import not defined for this platform") } } diff --git a/vendor/lua/5.2/lua.odin b/vendor/lua/5.2/lua.odin index 5474da95d..960b8ba36 100644 --- a/vendor/lua/5.2/lua.odin +++ b/vendor/lua/5.2/lua.odin @@ -15,16 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.2" } else { - foreign import lib "system:liblua.so.5.2" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.2" } else { - foreign import lib "system:liblua52.a" + #panic("LUA import not defined for this platform") } } diff --git a/vendor/lua/5.3/lua.odin b/vendor/lua/5.3/lua.odin index e0975e5f8..1428cc9b7 100644 --- a/vendor/lua/5.3/lua.odin +++ b/vendor/lua/5.3/lua.odin @@ -15,16 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.3" } else { - foreign import lib "system:liblua.so.5.3" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.3" } else { - foreign import lib "system:liblua53.a" + #panic("LUA import not defined for this platform") } } diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin index 80f7ead3a..9f9fc76d3 100644 --- a/vendor/lua/5.4/lua.odin +++ b/vendor/lua/5.4/lua.odin @@ -15,24 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.4" } else { - // Note(bumbread): My linux system has a few aliases for this shared object - // lublua5.4.so, liblua.so, lublua.so.5.4, liblua.so.5.4.6. I don't know - // who enforces these numbers (probably ld?), and if it can be done in a - // unix-generic way, but in any way I think the most sane thing to do is to - // keep it close to what linux does and if it breaks, just special case those - // operating systems. - // Also there was no alias for liblua54.so, that seems to suggest that way - // of specifying it isn't portable - foreign import lib "system:liblua.so.5.4" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.4" } else { - foreign import lib "system:liblua54.a" + #panic("LUA import not defined for this platform") } } From 9d6ed991cbb9ba8df592133eb401110cfb056620 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 20 Jul 2024 14:33:34 +0200 Subject: [PATCH 12/25] Remove LUA panic for non-big-3 OS --- vendor/lua/5.1/lua.odin | 8 ++------ vendor/lua/5.2/lua.odin | 8 ++------ vendor/lua/5.3/lua.odin | 8 ++------ vendor/lua/5.4/lua.odin | 8 ++------ 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/vendor/lua/5.1/lua.odin b/vendor/lua/5.1/lua.odin index 8ab315bcd..5b7482931 100644 --- a/vendor/lua/5.1/lua.odin +++ b/vendor/lua/5.1/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.1" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.1" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.1" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.1" } } diff --git a/vendor/lua/5.2/lua.odin b/vendor/lua/5.2/lua.odin index 960b8ba36..d5d8ec253 100644 --- a/vendor/lua/5.2/lua.odin +++ b/vendor/lua/5.2/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.2" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.2" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.2" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.2" } } diff --git a/vendor/lua/5.3/lua.odin b/vendor/lua/5.3/lua.odin index 1428cc9b7..47215a327 100644 --- a/vendor/lua/5.3/lua.odin +++ b/vendor/lua/5.3/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.3" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.3" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.3" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.3" } } diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin index 9f9fc76d3..9be8fea55 100644 --- a/vendor/lua/5.4/lua.odin +++ b/vendor/lua/5.4/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.4" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.4" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.4" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.4" } } From b584eeaade7cece9ff8bc5a12792693c303391e3 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 20 Jul 2024 16:53:54 +0100 Subject: [PATCH 13/25] Add encoding/ini tests --- tests/core/encoding/ini/test_core_ini.odin | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/core/encoding/ini/test_core_ini.odin diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin new file mode 100644 index 000000000..1e6cc246b --- /dev/null +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -0,0 +1,120 @@ +package test_core_ini + +import "base:runtime" +import "core:encoding/ini" +import "core:mem/virtual" +import "core:strings" +import "core:testing" + +@test +prase_ini :: proc(t: ^testing.T) { + ini_data := ` + [LOG] + level = "devel" + file = "/var/log/testing.log" + + [USER] + first_name = "John" + surname = "Smith" + ` + + m, err := ini.load_map_from_string(ini_data, context.allocator) + ini.delete_map(m) + + testing.expectf( + t, + strings.contains(m["LOG"]["level"], "devel"), + "Expected m[\"LOG\"][\"level\"] to be equal to 'devel' instead got %v", + m["LOG"]["level"], + ) + testing.expectf( + t, + strings.contains(m["LOG"]["file"], "/var/log/testing.log"), + "Expected m[\"LOG\"][\"file\"] to be equal to '/var/log/testing.log' instead got %v", + m["LOG"]["file"], + ) + testing.expectf( + t, + strings.contains(m["USER"]["first_name"], "John"), + "Expected m[\"USER\"][\"first_name\"] to be equal to 'John' instead got %v", + m["USER"]["first_name"], + ) + testing.expectf( + t, + strings.contains(m["USER"]["surname"], "Smith"), + "Expected m[\"USER\"][\"surname\"] to be equal to 'Smith' instead got %v", + m["USER"]["surname"], + ) + + testing.expectf(t, err == nil, "Expected `ini.load_map_from_string` to return a nil error, got %v", err) +} + +@test +ini_to_string :: proc(t: ^testing.T) { + m := ini.Map{ + "LEVEL" = { + "LOG" = "debug", + }, + } + + str := ini.save_map_to_string(m, context.allocator) + defer delete(str) + delete(m["LEVEL"]) + delete(m) + + testing.expectf( + t, + strings.contains(str, "[LEVEL]LOG = debug"), + "Expected `ini.save_map_to_string` to return a string equal to \"[LEVEL]LOG = debug\", got %v", + str, + ) +} + +@test +ini_iterator :: proc(t: ^testing.T) { + ini_data := ` + [LOG] + level = "devel" + file = "/var/log/testing.log" + + [USER] + first_name = "John" + surname = "Smith" + ` + + i := 0 + iterator := ini.iterator_from_string(ini_data) + for key, value in ini.iterate(&iterator) { + if strings.contains(key, "level") { + testing.expectf( + t, + strings.contains(value, "devel"), + "Expected 'level' to be equal to 'devel' instead got '%v'", + value, + ) + } else if strings.contains(key, "file") { + testing.expectf( + t, + strings.contains(value, "/var/log/testing.log"), + "Expected 'file' to be equal to '/var/log/testing.log' instead got '%v'", + value, + ) + } else if strings.contains(key, "first_name") { + testing.expectf( + t, + strings.contains(value, "John"), + "Expected 'first_name' to be equal to 'John' instead got '%v'", + value, + ) + } else if strings.contains(key, "surname") { + testing.expectf( + t, + strings.contains(value, "Smith"), + "Expected 'surname' to be equal to 'Smith' instead got '%v'", + value, + ) + } + i += 1 + } + testing.expectf(t, i == 4, "Expected to loop 4 times, only looped %v times", i) +} From f560b14d105b25e30c08fed76f8e5324d378ee90 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 20 Jul 2024 17:10:19 +0100 Subject: [PATCH 14/25] Fix typo in name of test --- tests/core/encoding/ini/test_core_ini.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin index 1e6cc246b..9106f61bd 100644 --- a/tests/core/encoding/ini/test_core_ini.odin +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -7,7 +7,7 @@ import "core:strings" import "core:testing" @test -prase_ini :: proc(t: ^testing.T) { +parse_ini :: proc(t: ^testing.T) { ini_data := ` [LOG] level = "devel" From e0a8bd04d5cbccb003bd80486089f0ea11a1857e Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 20 Jul 2024 17:10:34 +0100 Subject: [PATCH 15/25] Ensure deletion of maybe is delayed until we're finished with it. --- tests/core/encoding/ini/test_core_ini.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin index 9106f61bd..6e6c8152e 100644 --- a/tests/core/encoding/ini/test_core_ini.odin +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -19,7 +19,7 @@ parse_ini :: proc(t: ^testing.T) { ` m, err := ini.load_map_from_string(ini_data, context.allocator) - ini.delete_map(m) + defer ini.delete_map(m) testing.expectf( t, From fc5ce30f34163ce1dfa7ad8b01e60317c8d43c01 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 21 Jul 2024 00:37:11 +0200 Subject: [PATCH 16/25] Allow json to unmarshal empty struct. --- core/encoding/json/unmarshal.odin | 3 +-- tests/core/encoding/json/test_core_json.odin | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 1c1801bcd..127bce650 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -363,8 +363,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } v := v - v = reflect.any_base(v) - ti := type_info_of(v.id) + ti := reflect.type_info_base(type_info_of(v.id)) #partial switch t in ti.variant { case reflect.Type_Info_Struct: diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 10e09df3b..42ac9ce0f 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -349,6 +349,24 @@ unmarshal_json :: proc(t: ^testing.T) { } } +@test +unmarshal_empty_struct :: proc(t: ^testing.T) { + TestStruct :: struct {} + test := make(map[string]TestStruct) + input: = `{ + "test_1": {}, + "test_2": {} + }` + err := json.unmarshal(transmute([]u8)input, &test) + defer { + for k in test { + delete(k) + } + delete(test) + } + testing.expect(t, err == nil, "Expected empty struct to unmarshal without error") +} + @test surrogate :: proc(t: ^testing.T) { input := `+ + * 😃 - /` From c3a57853e283239573ab981b6c8acc4a66480c9a Mon Sep 17 00:00:00 2001 From: Ronald Date: Sun, 21 Jul 2024 00:00:47 +0100 Subject: [PATCH 17/25] Ensure that values in ini map are unquoted --- core/encoding/ini/ini.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 8010e4e75..8cfdf8c59 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -89,6 +89,8 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options } if allocated { return v, nil + } else { + return strings.clone(v), nil } } return strings.clone(val) From 1a6885c2a3c35c7399acdd551f6d63dde79645f9 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sun, 21 Jul 2024 00:08:20 +0100 Subject: [PATCH 18/25] Tidy up code --- core/encoding/ini/ini.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 8cfdf8c59..2bb7996a3 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -89,9 +89,8 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options } if allocated { return v, nil - } else { - return strings.clone(v), nil } + return strings.clone(v), nil } return strings.clone(val) } From 431227d4c50e5b53e5c4c922a76c11ec8464baa3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 21 Jul 2024 02:52:53 +0200 Subject: [PATCH 19/25] Add NULL check in check_range_stmt Fixes #3953 --- src/check_stmt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 74397828d..6c3570cc8 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1837,7 +1837,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (rs->vals.count == 1) { Type *t = type_deref(operand.type); - if (is_type_map(t) || is_type_bit_set(t)) { + if (t != NULL && (is_type_map(t) || is_type_bit_set(t))) { gbString v = expr_to_string(rs->vals[0]); defer (gb_string_free(v)); error_line("\tSuggestion: place parentheses around the expression\n"); From b84b4c47d7f0da0f97817fa874215046eb1557cd Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 21 Jul 2024 14:34:36 +1100 Subject: [PATCH 20/25] [thread]: Document all functions in core:thread --- core/thread/thread.odin | 281 ++++++++++++++++++++++++++++++++++------ 1 file changed, 242 insertions(+), 39 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 80e60d6cf..17ba1a0a2 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -6,12 +6,26 @@ import "base:intrinsics" _ :: intrinsics +/* +Value, specifying whether `core:thread` functionality is available on the +current platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Type for a procedure that will be run in a thread, after that thread has been +started. +*/ Thread_Proc :: #type proc(^Thread) +/* +Maximum number of user arguments for polymorphic thread procedures. +*/ MAX_USER_ARGUMENTS :: 8 +/* +Type representing the state/flags of the thread. +*/ Thread_State :: enum u8 { Started, Joined, @@ -19,44 +33,48 @@ Thread_State :: enum u8 { Self_Cleanup, } +/* +Type representing a thread handle and the associated with that thread data. +*/ Thread :: struct { using specific: Thread_Os_Specific, flags: bit_set[Thread_State; u8], - id: int, - procedure: Thread_Proc, - - /* - These are values that the user can set as they wish, after the thread has been created. - This data is easily available to the thread proc. - - These fields can be assigned to directly. - - Should be set after the thread is created, but before it is started. - */ - data: rawptr, - user_index: int, - user_args: [MAX_USER_ARGUMENTS]rawptr, - - /* - The context to be used as 'context' in the thread proc. - - This field can be assigned to directly, after the thread has been created, but __before__ the thread has been started. - This field must not be changed after the thread has started. - - NOTE: If you __don't__ set this, the temp allocator will be managed for you; - If you __do__ set this, then you're expected to handle whatever allocators you set, yourself. - - IMPORTANT: - By default, the thread proc will get the same context as `main()` gets. - In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies. - ***This does NOT happen when you set `init_context`.*** - This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator, - then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually, - in order to prevent any memory leaks. - This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state! - */ + // Thread ID. + id: int, + // The thread procedure. + procedure: Thread_Proc, + // User-supplied pointer, that will be available to the thread once it is + // started. Should be set after the thread has been created, but before + // it is started. + data: rawptr, + // User-supplied integer, that will be available to the thread once it is + // started. Should be set after the thread has been created, but before + // it is started. + user_index: int, + // User-supplied array of arguments, that will be available to the thread, + // once it is started. Should be set after the thread has been created, + // but before it is started. + user_args: [MAX_USER_ARGUMENTS]rawptr, + // The thread context. + // This field can be assigned to directly, after the thread has been + // created, but __before__ the thread has been started. This field must + // not be changed after the thread has started. + // + // **Note**: If this field is **not** set, the temp allocator will be managed + // automatically. If it is set, the allocators must be handled manually. + // + // **IMPORTANT**: + // By default, the thread proc will get the same context as `main()` gets. + // In this situation, the thread will get a new temporary allocator which + // will be cleaned up when the thread dies. ***This does NOT happen when + // `init_context` field is initialized***. + // + // If `init_context` is initialized, and `temp_allocator` field is set to + // the default temp allocator, then `runtime.default_temp_allocator_destroy()` + // procedure needs to be called from the thread procedure, in order to prevent + // any memory leaks. init_context: Maybe(runtime.Context), - + // The allocator used to allocate data for the thread. creation_allocator: mem.Allocator, } @@ -64,6 +82,9 @@ when IS_SUPPORTED { #assert(size_of(Thread{}.user_index) == size_of(uintptr)) } +/* +Type representing priority of a thread. +*/ Thread_Priority :: enum { Normal, Low, @@ -71,74 +92,178 @@ Thread_Priority :: enum { } /* - Creates a thread in a suspended state with the given priority. - To start the thread, call `thread.start()`. +Create a thread in a suspended state with the given priority. - See `thread.create_and_start()`. +This procedure creates a thread that will be set to run the procedure +specified by `procedure` parameter with a specified priority. The returned +thread will be in a suspended state, until `start()` procedure is called. + +To start the thread, call `start()`. Also the `create_and_start()` +procedure can be called to create and start the thread immediately. */ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { return _create(procedure, priority) } + +/* +Wait for the thread to finish and free all data associated with it. +*/ destroy :: proc(thread: ^Thread) { _destroy(thread) } +/* +Start a suspended thread. +*/ start :: proc(thread: ^Thread) { _start(thread) } +/* +Check if the thread has finished work. +*/ is_done :: proc(thread: ^Thread) -> bool { return _is_done(thread) } - +/* +Wait for the thread to finish work. +*/ join :: proc(thread: ^Thread) { _join(thread) } - +/* +Wait for all threads to finish work. +*/ join_multiple :: proc(threads: ..^Thread) { _join_multiple(..threads) } +/* +Forcibly terminate a running thread. +*/ terminate :: proc(thread: ^Thread, exit_code: int) { _terminate(thread, exit_code) } +/* +Yield the execution of the current thread to another OS thread or process. +*/ yield :: proc() { _yield() } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start(fn, init_context, priority, true) } +/* +Run a procedure with one pointer parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start_with_data(data, fn, init_context, priority, true) } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data(data, fn, init_context, priority, true) } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data @@ -154,9 +279,22 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, return t } +/* +Run a procedure with one pointer parameter on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data @@ -176,6 +314,22 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co return t } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -201,6 +355,22 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex return t } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -232,6 +402,22 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), return t } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -264,6 +450,23 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr start(t) return t } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { From 3e618bed406bbe8c86290bbd855a742f9e45de73 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 22 Jul 2024 00:51:38 +0200 Subject: [PATCH 21/25] fix `reflect.any_base` and `reflect.any_core` with any's containing nil --- core/reflect/reflect.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index e6d2bc87a..23c0f803e 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -143,7 +143,7 @@ when !ODIN_NO_RTTI { @(require_results) any_base :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_base(v.id) } return v @@ -151,7 +151,7 @@ any_base :: proc(v: any) -> any { @(require_results) any_core :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_core(v.id) } return v From 1873f7215dd42e4ee628fcd0947579bf42557dbe Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Mon, 22 Jul 2024 11:03:25 +0200 Subject: [PATCH 22/25] [sys/linux]: Change execveat flags type to Execveat_Flags --- core/sys/linux/sys.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 03c34223c..f7cacc544 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -2814,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) { +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags = {}) -> (Errno) { ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } From 39657e4d968711c11254fa0ee26d576cfc367071 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 15:15:51 +0200 Subject: [PATCH 23/25] Fix #3473 Fix the problem where the initial package's directory name ended in .odin. --- src/parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/parser.cpp b/src/parser.cpp index 5a3fc1634..f2d2a1d15 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5609,7 +5609,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const pkg->foreign_files.allocator = permanent_allocator(); // NOTE(bill): Single file initial package - if (kind == Package_Init && string_ends_with(path, FILE_EXT)) { + if (kind == Package_Init && !path_is_directory(path) && string_ends_with(path, FILE_EXT)) { FileInfo fi = {}; fi.name = filename_from_path(path); fi.fullpath = path; @@ -6529,6 +6529,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { GB_ASSERT(init_filename.text[init_filename.len] == 0); String init_fullpath = path_to_full_path(permanent_allocator(), init_filename); + if (!path_is_directory(init_fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(init_fullpath, ext)) { @@ -6543,6 +6544,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { String short_path = filename_from_path(path); + char *cpath = alloc_cstring(temporary_allocator(), short_path); if (gb_file_exists(cpath)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); From 90a4d12b30f24c4a44a8c8e606ce56efde56cf50 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 16:11:33 +0200 Subject: [PATCH 24/25] Fix .exe path is directory check. --- src/parser.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index f2d2a1d15..eaf43b5bc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6543,10 +6543,9 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { - String short_path = filename_from_path(path); - - char *cpath = alloc_cstring(temporary_allocator(), short_path); - if (gb_file_exists(cpath)) { + String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); + char *cpath = alloc_cstring(temporary_allocator(), output_path); + if (path_is_directory(output_path) && gb_file_exists(cpath)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } From 07d2aba31037c645327f28eba179d6cdba245cca Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 16:36:21 +0200 Subject: [PATCH 25/25] Simplify exe path check. --- src/parser.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index eaf43b5bc..aba2b8276 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6544,8 +6544,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); - char *cpath = alloc_cstring(temporary_allocator(), output_path); - if (path_is_directory(output_path) && gb_file_exists(cpath)) { + if (path_is_directory(output_path)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; }