This commit is contained in:
gingerBill
2024-07-22 16:29:35 +01:00
25 changed files with 1203 additions and 130 deletions

View File

@@ -90,6 +90,7 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options
if allocated {
return v, nil
}
return strings.clone(v), nil
}
return strings.clone(val)
}

View File

@@ -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:

View File

@@ -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

View File

@@ -1815,3 +1815,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,
}

View File

@@ -749,17 +749,13 @@ getsockopt :: proc {
getsockopt_base,
}
// TODO(flysand): clone (probably not in this PR, maybe not ever)
/*
Creates a copy of the running process.
Available since Linux 1.0.
*/
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)
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)
@@ -789,8 +785,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 {
ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp)
return Errno(-ret)
ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0))
return Errno(-ret)
}
}
@@ -2818,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)
}

View File

@@ -1303,3 +1303,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]

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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)

View File

@@ -1,3 +1,4 @@
//+private
package datetime
// Internal helper functions for calendrical conversions

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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");

View File

@@ -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, "");
}

View File

@@ -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)) {
@@ -6542,9 +6543,8 @@ 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]);
if (path_is_directory(output_path)) {
error({}, "Please specify the executable name with -out:<string> as a directory exists with the same name in the current working directory");
return ParseFile_DirectoryAlreadyExists;
}

View File

@@ -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
parse_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)
defer 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)
}

View File

@@ -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 := `+ + * 😃 - /`

View File

@@ -16,7 +16,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua5.1.so"
} else {
foreign import lib "system:liblua.so.5.1"
foreign import lib "system:lua5.1"
}
} else {
when ODIN_OS == .Windows {
@@ -24,7 +24,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua5.1.a"
} else {
foreign import lib "system:liblua5.1.a"
foreign import lib "system:lua5.1"
}
}

View File

@@ -16,7 +16,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua52.so"
} else {
foreign import lib "system:liblua.so.5.2"
foreign import lib "system:lua5.2"
}
} else {
when ODIN_OS == .Windows {
@@ -24,7 +24,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua52.a"
} else {
foreign import lib "system:liblua52.a"
foreign import lib "system:lua5.2"
}
}

View File

@@ -16,7 +16,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua53.so"
} else {
foreign import lib "system:liblua.so.5.3"
foreign import lib "system:lua5.3"
}
} else {
when ODIN_OS == .Windows {
@@ -24,7 +24,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua53.a"
} else {
foreign import lib "system:liblua53.a"
foreign import lib "system:lua5.3"
}
}

View File

@@ -16,15 +16,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua54.so"
} 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"
foreign import lib "system:lua5.4"
}
} else {
when ODIN_OS == .Windows {
@@ -32,7 +24,7 @@ when LUA_SHARED {
} else when ODIN_OS == .Linux {
foreign import lib "linux/liblua54.a"
} else {
foreign import lib "system:liblua54.a"
foreign import lib "system:lua5.4"
}
}

View File

@@ -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) ---