aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2024-07-22 16:29:35 +0100
committergingerBill <bill@gingerbill.org>2024-07-22 16:29:35 +0100
commit915c5c3a870007687686e8e05fed59cdeb56be56 (patch)
tree79500cc3342f3667b53cd2e5d22adfa1fceb5c51
parentfcaa47986aa5477e60a67a4ace5c7b020b214afd (diff)
parent07d2aba31037c645327f28eba179d6cdba245cca (diff)
Merge branch 'master' of https://github.com/odin-lang/Odin
-rw-r--r--core/encoding/ini/ini.odin1
-rw-r--r--core/encoding/json/unmarshal.odin3
-rw-r--r--core/reflect/reflect.odin4
-rw-r--r--core/sys/linux/bits.odin8
-rw-r--r--core/sys/linux/sys.odin12
-rw-r--r--core/sys/linux/types.odin5
-rw-r--r--core/thread/thread.odin281
-rw-r--r--core/time/datetime/constants.odin66
-rw-r--r--core/time/datetime/datetime.odin172
-rw-r--r--core/time/datetime/internal.odin1
-rw-r--r--core/time/datetime/validation.odin44
-rw-r--r--core/time/iso8601.odin88
-rw-r--r--core/time/perf.odin90
-rw-r--r--core/time/rfc3339.odin92
-rw-r--r--core/time/time.odin284
-rw-r--r--src/check_stmt.cpp2
-rw-r--r--src/main.cpp2
-rw-r--r--src/parser.cpp8
-rw-r--r--tests/core/encoding/ini/test_core_ini.odin120
-rw-r--r--tests/core/encoding/json/test_core_json.odin18
-rw-r--r--vendor/lua/5.1/lua.odin4
-rw-r--r--vendor/lua/5.2/lua.odin4
-rw-r--r--vendor/lua/5.3/lua.odin4
-rw-r--r--vendor/lua/5.4/lua.odin12
-rw-r--r--vendor/x11/xlib/xlib_procs.odin6
25 files changed, 1202 insertions, 129 deletions
diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin
index 8010e4e75..2bb7996a3 100644
--- a/core/encoding/ini/ini.odin
+++ b/core/encoding/ini/ini.odin
@@ -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)
}
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/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
diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin
index 96c3de18d..e10edf558 100644
--- a/core/sys/linux/bits.odin
+++ b/core/sys/linux/bits.odin
@@ -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,
+}
diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin
index 90db862e2..f7cacc544 100644
--- a/core/sys/linux/sys.odin
+++ b/core/sys/linux/sys.odin
@@ -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)
}
diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin
index d373f96f2..288edf879 100644
--- a/core/sys/linux/types.odin
+++ b/core/sys/linux/types.odin
@@ -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]
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) {
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,
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
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");
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, "");
}
diff --git a/src/parser.cpp b/src/parser.cpp
index 5a3fc1634..aba2b8276 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)) {
@@ -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;
}
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..6e6c8152e
--- /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
+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)
+}
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
@@ -350,6 +350,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 := `+ + * 😃 - /`
diff --git a/vendor/lua/5.1/lua.odin b/vendor/lua/5.1/lua.odin
index b53c61bb3..5b7482931 100644
--- a/vendor/lua/5.1/lua.odin
+++ b/vendor/lua/5.1/lua.odin
@@ -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"
}
}
diff --git a/vendor/lua/5.2/lua.odin b/vendor/lua/5.2/lua.odin
index 5474da95d..d5d8ec253 100644
--- a/vendor/lua/5.2/lua.odin
+++ b/vendor/lua/5.2/lua.odin
@@ -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"
}
}
diff --git a/vendor/lua/5.3/lua.odin b/vendor/lua/5.3/lua.odin
index e0975e5f8..47215a327 100644
--- a/vendor/lua/5.3/lua.odin
+++ b/vendor/lua/5.3/lua.odin
@@ -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"
}
}
diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin
index 80f7ead3a..9be8fea55 100644
--- a/vendor/lua/5.4/lua.odin
+++ b/vendor/lua/5.4/lua.odin
@@ -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"
}
}
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) ---