From a89d22b291c4df103212756a27459bd9eb02a807 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sat, 21 Sep 2024 00:47:21 -0400 Subject: [PATCH 01/13] add `time.time_to_rfc3339`, a printer to RFC3339 dates this is the counterpart to the existing parsing function `rfc3339_to_time_utc` and others. It prints the timestamp as a string, allocated dynamically. --- core/time/rfc3339.odin | 106 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index e4c6565d6..3ed149ad1 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -187,4 +187,108 @@ scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) found_sep |= rune(s[count]) == v } return res, found_sep -} \ No newline at end of file +} + +/* +Serialize the timestamp as a RFC 3339 string. + +The boolean `ok` is false if the `time` is not a valid datetime, or if allocating the result string fails. + +**Inputs**: +- `utc_offset`: offset in minutes wrt UTC (ie. the timezone) +- `include_nanos`: whether to include nanoseconds in the result. +*/ +time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + // convert to datetime + datetime := time_to_datetime(time) or_return + + if datetime.year < 0 || datetime.year >= 10_000 { return "", false } + + temp_string := [30]u8{} + offset : uint = 0 // offset in temp_string + + print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { + i := i + width := width + for digit_idx in 0.. (res: i64, n_digits: i8) { + res = n + n_digits = 9 + for res % 10 == 0 { + res = res / 10 + n_digits -= 1 + } + return + } + + // pre-epoch times: turn, say, -400ms to +600ms for display + nanos := time._nsec % 1_000_000_000 + if nanos < 0 { + nanos += 1_000_000_000 + } + + if nanos != 0 && include_nanos { + temp_string[offset] = '.' + offset += 1 + + // remove trailing zeroes + nanos_nonzero, n_digits := strip_trailing_zeroes_nanos(nanos) + assert(nanos_nonzero != 0) + + // write digits, right-to-left + for digit_idx : i8 = n_digits-1; digit_idx >= 0; digit_idx -= 1 { + digit := u8(nanos_nonzero % 10) + temp_string[offset + uint(digit_idx)] = '0' + u8(digit) + nanos_nonzero /= 10 + } + offset += uint(n_digits) + } + + if utc_offset == 0 { + temp_string[offset] = 'Z' + offset += 1 + } else { + temp_string[offset] = utc_offset > 0 ? '+' : '-' + offset += 1; + utc_offset := abs(utc_offset) + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset % 60)) + } + + res_as_slice, res_alloc := make_slice([]u8, len=offset, allocator = allocator) + if res_alloc != nil { + return "", false + } + + copy(res_as_slice, temp_string[:offset]) + + return string(res_as_slice), true +} From d08b3d3b82145faefe315d68350878c61b218b94 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sat, 21 Sep 2024 00:48:39 -0400 Subject: [PATCH 02/13] add tests for time.time_to_rfc3339 --- tests/core/time/test_core_time.odin | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 51955b1c4..2ad3690d9 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -196,6 +196,35 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) { } } +@test +test_print_rfc3339 :: proc(t: ^testing.T) { + TestCase :: struct { + printed: string, + time: i64, + utc_offset: int + }; + + tests :: [?]TestCase { + {"1985-04-12T23:20:50.52Z", 482196050520000000, 0}, + {"1985-04-12T23:20:50.52001905Z", 482196050520019050, 0}, + {"1996-12-19T16:39:57-08:00", 851013597000000000, -480}, + {"1996-12-20T00:39:57Z", 851042397000000000, 0}, + {"1937-01-01T12:00:27.87+00:20", -1041335972130000000, +20}, + }; + + for test in tests { + timestamp := time.Time { _nsec = test.time } + printed_timestamp, ok := time.time_to_rfc3339(time=timestamp, utc_offset=test.utc_offset) + defer delete_string(printed_timestamp) + + testing.expect(t, ok, "expected printing to work fine") + + testing.expectf( + t, printed_timestamp == test.printed, + "expected is %w, printed is %w", test.printed, printed_timestamp) + } +} + @test test_parse_iso8601_string :: proc(t: ^testing.T) { for test in iso8601_tests { @@ -318,4 +347,4 @@ date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) { "Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss, ) -} \ No newline at end of file +} From 32e13f17aea7068b5621d21f97ef820c8dae16a4 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sat, 21 Sep 2024 21:08:35 -0400 Subject: [PATCH 03/13] Apply suggestions from code review Co-authored-by: flysand7 --- core/time/rfc3339.odin | 6 +++--- tests/core/time/test_core_time.odin | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 3ed149ad1..9d816b3fa 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -204,8 +204,8 @@ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, if datetime.year < 0 || datetime.year >= 10_000 { return "", false } - temp_string := [30]u8{} - offset : uint = 0 // offset in temp_string + temp_string := [36]u8{} + offset : uint = 0 print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { i := i @@ -275,7 +275,7 @@ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, offset += 1 } else { temp_string[offset] = utc_offset > 0 ? '+' : '-' - offset += 1; + offset += 1 utc_offset := abs(utc_offset) print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) temp_string[offset] = ':' diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 2ad3690d9..23044b72f 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -201,7 +201,7 @@ test_print_rfc3339 :: proc(t: ^testing.T) { TestCase :: struct { printed: string, time: i64, - utc_offset: int + utc_offset: int, }; tests :: [?]TestCase { @@ -221,7 +221,8 @@ test_print_rfc3339 :: proc(t: ^testing.T) { testing.expectf( t, printed_timestamp == test.printed, - "expected is %w, printed is %w", test.printed, printed_timestamp) + "expected is %w, printed is %w", test.printed, printed_timestamp, + ) } } From a1349d877601df166a6b4bb8a1a38126802dcc9d Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sun, 22 Sep 2024 00:08:07 -0400 Subject: [PATCH 04/13] fix vet warnings --- core/time/rfc3339.odin | 4 +++- tests/core/time/test_core_time.odin | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 9d816b3fa..20e8ea0bb 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -199,6 +199,8 @@ The boolean `ok` is false if the `time` is not a valid datetime, or if allocatin - `include_nanos`: whether to include nanoseconds in the result. */ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + utc_offset := utc_offset + // convert to datetime datetime := time_to_datetime(time) or_return @@ -276,7 +278,7 @@ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, } else { temp_string[offset] = utc_offset > 0 ? '+' : '-' offset += 1 - utc_offset := abs(utc_offset) + utc_offset = abs(utc_offset) print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) temp_string[offset] = ':' offset += 1 diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 23044b72f..424111aa3 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -202,7 +202,7 @@ test_print_rfc3339 :: proc(t: ^testing.T) { printed: string, time: i64, utc_offset: int, - }; + } tests :: [?]TestCase { {"1985-04-12T23:20:50.52Z", 482196050520000000, 0}, @@ -210,7 +210,7 @@ test_print_rfc3339 :: proc(t: ^testing.T) { {"1996-12-19T16:39:57-08:00", 851013597000000000, -480}, {"1996-12-20T00:39:57Z", 851042397000000000, 0}, {"1937-01-01T12:00:27.87+00:20", -1041335972130000000, +20}, - }; + } for test in tests { timestamp := time.Time { _nsec = test.time } From c39b934e7ffabe4b6fe1f044b193682b9a83eda0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 22 Sep 2024 13:04:23 +0100 Subject: [PATCH 05/13] Remove unused imports --- core/os/os_js.odin | 2 -- vendor/wasm/js/dom.odin | 2 +- vendor/wasm/js/events.odin | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/core/os/os_js.odin b/core/os/os_js.odin index 8bf1d988b..348554728 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -1,8 +1,6 @@ #+build js package os -import "base:runtime" - foreign import "odin_env" @(require_results) diff --git a/vendor/wasm/js/dom.odin b/vendor/wasm/js/dom.odin index 28dd32fed..ffc58a9a3 100644 --- a/vendor/wasm/js/dom.odin +++ b/vendor/wasm/js/dom.odin @@ -90,4 +90,4 @@ window_get_scroll :: proc "contextless" () -> (x, y: f64) { scroll: [2]f64 _window_get_scroll(&scroll) return scroll.x, scroll.y -} +} \ No newline at end of file diff --git a/vendor/wasm/js/events.odin b/vendor/wasm/js/events.odin index 258776fff..5a63da8ef 100644 --- a/vendor/wasm/js/events.odin +++ b/vendor/wasm/js/events.odin @@ -336,9 +336,6 @@ remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr return _remove_event_listener(id, name, user_data, callback) } -import "core:fmt" - - @(export, link_name="odin_dom_do_event_callback") do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) { @(default_calling_convention="contextless") From 096258b5d5978a05d151fbcc1a43b600fff0abd8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 22 Sep 2024 13:08:42 +0100 Subject: [PATCH 06/13] Rename `runtime.js` to `odin.js` --- vendor/wasm/README.md | 6 +++--- vendor/wasm/js/{runtime.js => odin.js} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename vendor/wasm/js/{runtime.js => odin.js} (100%) diff --git a/vendor/wasm/README.md b/vendor/wasm/README.md index d0b0a2f6f..191d45860 100644 --- a/vendor/wasm/README.md +++ b/vendor/wasm/README.md @@ -6,9 +6,9 @@ The `js_wasm32` target assumes that the WASM output will be ran within a web bro ## Example for `js_wasm32` -```js - - +```html + + diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/odin.js similarity index 100% rename from vendor/wasm/js/runtime.js rename to vendor/wasm/js/odin.js From 634fa7aa306c076a7a63047b0e448e01df156919 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 22 Sep 2024 13:13:34 +0100 Subject: [PATCH 07/13] Move `vendor:wasm/js` to `core:sys/wasm/js` --- core/sys/wasm/README.md | 15 +++++++++++++++ {vendor => core/sys}/wasm/js/dom.odin | 0 {vendor => core/sys}/wasm/js/dom_all_targets.odin | 0 {vendor => core/sys}/wasm/js/events.odin | 0 .../sys}/wasm/js/events_all_targets.odin | 0 {vendor => core/sys}/wasm/js/general.odin | 0 .../sys}/wasm/js/memory_all_targets.odin | 0 {vendor => core/sys}/wasm/js/memory_js.odin | 0 {vendor => core/sys}/wasm/js/odin.js | 0 9 files changed, 15 insertions(+) create mode 100644 core/sys/wasm/README.md rename {vendor => core/sys}/wasm/js/dom.odin (100%) rename {vendor => core/sys}/wasm/js/dom_all_targets.odin (100%) rename {vendor => core/sys}/wasm/js/events.odin (100%) rename {vendor => core/sys}/wasm/js/events_all_targets.odin (100%) rename {vendor => core/sys}/wasm/js/general.odin (100%) rename {vendor => core/sys}/wasm/js/memory_all_targets.odin (100%) rename {vendor => core/sys}/wasm/js/memory_js.odin (100%) rename {vendor => core/sys}/wasm/js/odin.js (100%) diff --git a/core/sys/wasm/README.md b/core/sys/wasm/README.md new file mode 100644 index 000000000..1aaeaa429 --- /dev/null +++ b/core/sys/wasm/README.md @@ -0,0 +1,15 @@ +# WASM on the Web + +This directory is for use when targeting the `js_wasm32` target and the packages that rely on it. + +The `js_wasm32` target assumes that the WASM output will be ran within a web browser rather than a standalone VM. In the VM cases, either `wasi_wasm32` or `freestanding_wasm32` should be used accordingly. + +## Example for `js_wasm32` + +```html + + + +``` diff --git a/vendor/wasm/js/dom.odin b/core/sys/wasm/js/dom.odin similarity index 100% rename from vendor/wasm/js/dom.odin rename to core/sys/wasm/js/dom.odin diff --git a/vendor/wasm/js/dom_all_targets.odin b/core/sys/wasm/js/dom_all_targets.odin similarity index 100% rename from vendor/wasm/js/dom_all_targets.odin rename to core/sys/wasm/js/dom_all_targets.odin diff --git a/vendor/wasm/js/events.odin b/core/sys/wasm/js/events.odin similarity index 100% rename from vendor/wasm/js/events.odin rename to core/sys/wasm/js/events.odin diff --git a/vendor/wasm/js/events_all_targets.odin b/core/sys/wasm/js/events_all_targets.odin similarity index 100% rename from vendor/wasm/js/events_all_targets.odin rename to core/sys/wasm/js/events_all_targets.odin diff --git a/vendor/wasm/js/general.odin b/core/sys/wasm/js/general.odin similarity index 100% rename from vendor/wasm/js/general.odin rename to core/sys/wasm/js/general.odin diff --git a/vendor/wasm/js/memory_all_targets.odin b/core/sys/wasm/js/memory_all_targets.odin similarity index 100% rename from vendor/wasm/js/memory_all_targets.odin rename to core/sys/wasm/js/memory_all_targets.odin diff --git a/vendor/wasm/js/memory_js.odin b/core/sys/wasm/js/memory_js.odin similarity index 100% rename from vendor/wasm/js/memory_js.odin rename to core/sys/wasm/js/memory_js.odin diff --git a/vendor/wasm/js/odin.js b/core/sys/wasm/js/odin.js similarity index 100% rename from vendor/wasm/js/odin.js rename to core/sys/wasm/js/odin.js From 26d00925ccd24b81bfe578be316c1ff2fd8faac9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 22 Sep 2024 13:20:45 +0100 Subject: [PATCH 08/13] Clean-up `libc` usage --- core/c/libc/errno.odin | 8 ++++++++ core/c/libc/stdio.odin | 2 +- core/c/libc/string.odin | 1 + core/c/libc/time.odin | 2 +- vendor/stb/image/stb_image.odin | 3 +-- vendor/stb/image/stb_image_resize.odin | 2 +- vendor/stb/image/stb_image_write.odin | 2 +- vendor/wasm/README.md | 2 +- 8 files changed, 15 insertions(+), 7 deletions(-) diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index 843b2f1b6..de429a6ec 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -98,6 +98,14 @@ when ODIN_OS == .Haiku { ERANGE :: B_POSIX_ERROR_BASE + 17 } +when ODIN_OS == .JS { + _ :: libc + _get_errno :: proc "c" () -> ^int { + @(static) errno: int + return &errno + } +} + // Odin has no way to make an identifier "errno" behave as a function call to // read the value, or to produce an lvalue such that you can assign a different // error value to errno. To work around this, just expose it as a function like diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 019389b0d..f02453bba 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -59,7 +59,7 @@ when ODIN_OS == .Windows { } // GLIBC and MUSL compatible. -when ODIN_OS == .Linux { +when ODIN_OS == .Linux || ODIN_OS == .JS { fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, } _IOFBF :: 0 diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index cde9c7e6b..4ec4f3a7a 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -12,6 +12,7 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(default_calling_convention="c") foreign libc { // 7.24.2 Copying functions memcpy :: proc(s1, s2: rawptr, n: size_t) -> rawptr --- diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 48def707e..6828793ec 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -45,7 +45,7 @@ when ODIN_OS == .Windows { } } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS { @(default_calling_convention="c") foreign libc { // 7.27.2 Time manipulation functions diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index 85d612354..0151899d9 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -1,6 +1,6 @@ package stb_image -import c "core:c/libc" +import "core:c" @(private) LIB :: ( @@ -20,7 +20,6 @@ when LIB != "" { when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { foreign import stbi "../lib/stb_image_wasm.o" - foreign import stbi { LIB } } else when LIB != "" { foreign import stbi { LIB } } else { diff --git a/vendor/stb/image/stb_image_resize.odin b/vendor/stb/image/stb_image_resize.odin index a37c2e243..241a93eb0 100644 --- a/vendor/stb/image/stb_image_resize.odin +++ b/vendor/stb/image/stb_image_resize.odin @@ -1,6 +1,6 @@ package stb_image -import c "core:c/libc" +import "core:c" @(private) RESIZE_LIB :: ( diff --git a/vendor/stb/image/stb_image_write.odin b/vendor/stb/image/stb_image_write.odin index a0c0b57a0..e86fa2b95 100644 --- a/vendor/stb/image/stb_image_write.odin +++ b/vendor/stb/image/stb_image_write.odin @@ -1,6 +1,6 @@ package stb_image -import c "core:c/libc" +import "core:c" @(private) WRITE_LIB :: ( diff --git a/vendor/wasm/README.md b/vendor/wasm/README.md index 191d45860..1aaeaa429 100644 --- a/vendor/wasm/README.md +++ b/vendor/wasm/README.md @@ -7,7 +7,7 @@ The `js_wasm32` target assumes that the WASM output will be ran within a web bro ## Example for `js_wasm32` ```html - +