From ad146486d50aaf2ce630a0d0db6698f59065f5df Mon Sep 17 00:00:00 2001 From: Jae Kwon <53785+jaekwon@users.noreply.github.com> Date: Fri, 5 Aug 2022 13:56:40 -0700 Subject: [PATCH] Support UntypedBigdecType, BigdecType, Float32Type and Float64Type. (#306) --- Makefile | 5 + alloc.go | 3 + cmd/gnodev/repl.go | 3 +- cmd/gnodev/test.go | 2 +- examples/gno.land/p/rand/rand0_filetest.gno | 30 +- examples/gno.land/r/banktest/z_0_filetest.gno | 2 +- examples/gno.land/r/banktest/z_2_filetest.gno | 3 +- examples/gno.land/r/boards/z_0_filetest.gno | 4 +- .../gno.land/r/boards/z_10_c_filetest.gno | 6 +- examples/gno.land/r/boards/z_10_filetest.gno | 2 +- .../gno.land/r/boards/z_11_d_filetest.gno | 8 +- examples/gno.land/r/boards/z_11_filetest.gno | 4 +- examples/gno.land/r/boards/z_2_filetest.gno | 4 +- examples/gno.land/r/boards/z_3_filetest.gno | 4 +- examples/gno.land/r/boards/z_4_filetest.gno | 10 +- examples/gno.land/r/boards/z_5_c_filetest.gno | 4 +- examples/gno.land/r/boards/z_5_filetest.gno | 6 +- examples/gno.land/r/boards/z_6_filetest.gno | 8 +- examples/gno.land/r/boards/z_7_filetest.gno | 2 +- examples/gno.land/r/boards/z_8_filetest.gno | 4 +- examples/gno.land/r/boards/z_9_filetest.gno | 2 +- examples/gno.land/r/x/outfmt/outfmt_test.gno | 12 +- gno.proto | 4 + go.mod | 1 + go.sum | 2 + gonative.go | 158 +- kind_string.go | 35 +- nodes.go | 6 +- nodes_string.go | 2 + op_binary.go | 148 +- op_eval.go | 128 +- op_string.go | 2 +- op_unary.go | 9 + package.go | 1 + pkgs/flow/LICENSE.md | 32 + pkgs/sdk/vm/builtins.go | 7 +- pkgs/std/std.proto | 6 + preprocess.go | 53 +- realm.go | 4 + stdlibs/internal/math/math.gno | 3 + stdlibs/internal/os/os.gno | 3 + stdlibs/math/abs.gno | 19 + stdlibs/math/bits.gno | 66 + stdlibs/math/const.gno | 3 - stdlibs/math/copysign.gno | 16 + stdlibs/math/exp.gno | 207 +++ stdlibs/math/ldexp.gno | 57 + stdlibs/std/time.gno | 1 + stdlibs/stdlibs.go | 104 ++ stdlibs/time/format.gno | 1619 ++++++++++++++++ stdlibs/time/time.gno | 1625 +++++++++++++++++ stdlibs/time/timezoneinfo.gno | 692 +++++++ tests/file.go | 9 +- tests/files/float0.gno | 9 + tests/files/float1.gno | 9 + tests/files/float2.gno | 9 + tests/files/float3.gno | 9 + tests/files/float4.gno | 12 + tests/files/std1.gno | 2 +- tests/files/stdlibs.gno | 15 + tests/files/switch40.gno | 16 + tests/files2/assign0b.gno | 4 +- tests/files2/float0.gno | 9 + tests/files2/float1.gno | 9 + tests/files2/float2.gno | 9 + tests/files2/float3.gno | 9 + tests/files2/float4.gno | 12 + tests/files2/float5.gno | 18 + tests/files2/map29.gno | 2 +- tests/files2/std1.gno | 2 +- tests/files2/stdlibs.gno | 14 + tests/files2/struct13.gno | 2 +- tests/files2/time0.gno | 5 +- tests/files2/time1.gno | 3 +- tests/files2/time11.gno | 4 +- tests/files2/time12.gno | 3 +- tests/files2/time13.gno | 3 +- tests/files2/time14.gno | 3 +- tests/files2/time2.gno | 3 +- tests/files2/time3.gno | 3 +- tests/files2/time4.gno | 3 +- tests/files2/time6.gno | 3 +- tests/files2/time7.gno | 3 +- tests/files2/time9.gno | 3 +- tests/files2/type2.gno | 3 +- tests/imports.go | 486 ++--- tests/package_test.go | 2 +- transcribe.go | 9 + types.go | 98 +- values.go | 157 +- values_conversions.go | 348 +++- values_string.go | 14 + vptype_string.go | 2 +- 93 files changed, 5899 insertions(+), 580 deletions(-) create mode 100644 pkgs/flow/LICENSE.md create mode 100644 stdlibs/internal/math/math.gno create mode 100644 stdlibs/internal/os/os.gno create mode 100644 stdlibs/math/abs.gno create mode 100644 stdlibs/math/bits.gno create mode 100644 stdlibs/math/copysign.gno create mode 100644 stdlibs/math/exp.gno create mode 100644 stdlibs/math/ldexp.gno create mode 100644 stdlibs/time/format.gno create mode 100644 stdlibs/time/time.gno create mode 100644 stdlibs/time/timezoneinfo.gno create mode 100644 tests/files/float0.gno create mode 100644 tests/files/float1.gno create mode 100644 tests/files/float2.gno create mode 100644 tests/files/float3.gno create mode 100644 tests/files/float4.gno create mode 100644 tests/files/stdlibs.gno create mode 100644 tests/files/switch40.gno create mode 100644 tests/files2/float0.gno create mode 100644 tests/files2/float1.gno create mode 100644 tests/files2/float2.gno create mode 100644 tests/files2/float3.gno create mode 100644 tests/files2/float4.gno create mode 100644 tests/files2/float5.gno create mode 100644 tests/files2/stdlibs.gno diff --git a/Makefile b/Makefile index 80d6621cd94..2fd8a70c4cc 100644 --- a/Makefile +++ b/Makefile @@ -134,7 +134,12 @@ test.examples: # Code gen stringer: + stringer -type=Kind stringer -type=Op + stringer -type=TransCtrl + stringer -type=TransField + stringer -type=VPType + stringer -type=Word genproto: rm -rf proto/* diff --git a/alloc.go b/alloc.go index d6c83e7bffe..7f1f993704d 100644 --- a/alloc.go +++ b/alloc.go @@ -32,6 +32,7 @@ const ( _allocTypeValue = 16 _allocTypedValue = 40 _allocBigint = 200 // XXX + _allocBigdec = 200 // XXX _allocType = 200 // XXX _allocAny = 200 // XXX ) @@ -41,6 +42,8 @@ const ( allocStringByte = 1 allocBigint = _allocBase + _allocPointer + _allocBigint allocBigintByte = 1 + allocBigdec = _allocBase + _allocPointer + _allocBigdec + allocBigdecByte = 1 allocPointer = _allocBase allocArray = _allocBase + _allocPointer + _allocArrayValue allocArrayItem = _allocTypedValue diff --git a/cmd/gnodev/repl.go b/cmd/gnodev/repl.go index ddc3983b6f0..85adaebc633 100644 --- a/cmd/gnodev/repl.go +++ b/cmd/gnodev/repl.go @@ -48,10 +48,9 @@ func runRepl(rootDir string, verbose bool) error { stdin := os.Stdin stdout := os.Stdout stderr := os.Stderr - useNativeLibs := false // init store and machine - testStore := tests.TestStore(rootDir, "", stdin, stdout, stderr, useNativeLibs) + testStore := tests.TestStore(rootDir, "", stdin, stdout, stderr, tests.ImportModeStdlibsOnly) if verbose { testStore.SetLogStoreOps(true) } diff --git a/cmd/gnodev/test.go b/cmd/gnodev/test.go index a5a236f9290..065decb410a 100644 --- a/cmd/gnodev/test.go +++ b/cmd/gnodev/test.go @@ -103,7 +103,7 @@ func gnoTestPkg(cmd *command.Command, pkgPath string, unittestFiles, filetestFil var errs error - testStore := tests.TestStore(rootDir, "", os.Stdin, os.Stdout, os.Stderr, false) + testStore := tests.TestStore(rootDir, "", os.Stdin, os.Stdout, os.Stderr, tests.ImportModeStdlibsOnly) if verbose { testStore.SetLogStoreOps(true) } diff --git a/examples/gno.land/p/rand/rand0_filetest.gno b/examples/gno.land/p/rand/rand0_filetest.gno index a1a7ffb1193..e4c759eb97e 100644 --- a/examples/gno.land/p/rand/rand0_filetest.gno +++ b/examples/gno.land/p/rand/rand0_filetest.gno @@ -37,20 +37,20 @@ func main() { // Output: // --- -// 343 -// 682 -// 557 -// 741 -// 503 +// 571 +// 638 +// 608 +// 851 +// 455 // --- -// 343 -// 682 -// 557 -// 741 -// 503 +// 571 +// 638 +// 608 +// 851 +// 455 // --- -// 790 -// 699 -// 118 -// 747 -// 755 +// 989 +// 704 +// 782 +// 367 +// 888 diff --git a/examples/gno.land/r/banktest/z_0_filetest.gno b/examples/gno.land/r/banktest/z_0_filetest.gno index 7d996e61d6b..0444ec7f532 100644 --- a/examples/gno.land/r/banktest/z_0_filetest.gno +++ b/examples/gno.land/r/banktest/z_0_filetest.gno @@ -40,6 +40,6 @@ func main() { // main after: 300000000ugnot // ## recent activity // -// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 100000000ugnot returned, at 1970-01-01 12:00am UTC +// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 100000000ugnot returned, at 2009-02-13 11:31pm UTC // // ## total deposits diff --git a/examples/gno.land/r/banktest/z_2_filetest.gno b/examples/gno.land/r/banktest/z_2_filetest.gno index faff8f05478..a98acda635f 100644 --- a/examples/gno.land/r/banktest/z_2_filetest.gno +++ b/examples/gno.land/r/banktest/z_2_filetest.gno @@ -37,8 +37,7 @@ func main() { // main after: 255000000ugnot // ## recent activity // -// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 55000000ugnot returned, at 1970-01-01 12:00am UTC +// * g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 100000000ugnot sent, 55000000ugnot returned, at 2009-02-13 11:31pm UTC // // ## total deposits // 45000000ugnot -// diff --git a/examples/gno.land/r/boards/z_0_filetest.gno b/examples/gno.land/r/boards/z_0_filetest.gno index fc3ffb9c386..34b22e51f01 100644 --- a/examples/gno.land/r/boards/z_0_filetest.gno +++ b/examples/gno.land/r/boards/z_0_filetest.gno @@ -30,10 +30,10 @@ func main() { // ## [First Post (title)](/r/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am UTC](/r/boards:test_board/1) \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm UTC](/r/boards:test_board/1) \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) // // ---------------------------------------- // ## [Second Post (title)](/r/boards:test_board/2) // // Body of the second post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am UTC](/r/boards:test_board/2) \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] (1 replies) +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm UTC](/r/boards:test_board/2) \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] (1 replies) diff --git a/examples/gno.land/r/boards/z_10_c_filetest.gno b/examples/gno.land/r/boards/z_10_c_filetest.gno index 6e3448671d5..4c1c6842da3 100644 --- a/examples/gno.land/r/boards/z_10_c_filetest.gno +++ b/examples/gno.land/r/boards/z_10_c_filetest.gno @@ -35,14 +35,14 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] diff --git a/examples/gno.land/r/boards/z_10_filetest.gno b/examples/gno.land/r/boards/z_10_filetest.gno index 3d3eaaf2d49..4a412229372 100644 --- a/examples/gno.land/r/boards/z_10_filetest.gno +++ b/examples/gno.land/r/boards/z_10_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // ---------------------------------------------------- // thread does not exist with id: 1 diff --git a/examples/gno.land/r/boards/z_11_d_filetest.gno b/examples/gno.land/r/boards/z_11_d_filetest.gno index 7d9e2a01477..4e14926b404 100644 --- a/examples/gno.land/r/boards/z_11_d_filetest.gno +++ b/examples/gno.land/r/boards/z_11_d_filetest.gno @@ -35,18 +35,18 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > First reply of the First post // > -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] // // ---------------------------------------------------- // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > Edited: First reply of the First post // > -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] diff --git a/examples/gno.land/r/boards/z_11_filetest.gno b/examples/gno.land/r/boards/z_11_filetest.gno index 29b2c5a7882..3f1f354b27e 100644 --- a/examples/gno.land/r/boards/z_11_filetest.gno +++ b/examples/gno.land/r/boards/z_11_filetest.gno @@ -33,10 +33,10 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // ---------------------------------------------------- // # Edited: First Post in (title) // // Edited: Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] diff --git a/examples/gno.land/r/boards/z_2_filetest.gno b/examples/gno.land/r/boards/z_2_filetest.gno index 7e12cdcb354..3d0d56c99a1 100644 --- a/examples/gno.land/r/boards/z_2_filetest.gno +++ b/examples/gno.land/r/boards/z_2_filetest.gno @@ -32,7 +32,7 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] diff --git a/examples/gno.land/r/boards/z_3_filetest.gno b/examples/gno.land/r/boards/z_3_filetest.gno index 4596e448cb0..98b61f04b93 100644 --- a/examples/gno.land/r/boards/z_3_filetest.gno +++ b/examples/gno.land/r/boards/z_3_filetest.gno @@ -34,7 +34,7 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] diff --git a/examples/gno.land/r/boards/z_4_filetest.gno b/examples/gno.land/r/boards/z_4_filetest.gno index cd0bbefbb52..67bb12c5302 100644 --- a/examples/gno.land/r/boards/z_4_filetest.gno +++ b/examples/gno.land/r/boards/z_4_filetest.gno @@ -37,13 +37,13 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // // > Second reply of the second post -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/4) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/4) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] // Realm: // switchrealm["gno.land/r/users"] @@ -503,6 +503,7 @@ func main() { // } // }, // { +// "N": "0gKWSQAAAAA=", // "T": { // "@type": "/gno.RefType", // "ID": "std.Time" @@ -817,7 +818,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "a88e19f7fd69c993c13dd1ca4c0a19017f5e21f5", +// "Hash": "e94103b1714e94126f0e5332916328a4f4fd4860", // "ObjectID": "960d1737342909c1a4c32a4a93a88e680a6f79df:82" // } // } @@ -831,6 +832,7 @@ func main() { // } // }, // { +// "N": "0gKWSQAAAAA=", // "T": { // "@type": "/gno.RefType", // "ID": "std.Time" diff --git a/examples/gno.land/r/boards/z_5_c_filetest.gno b/examples/gno.land/r/boards/z_5_c_filetest.gno index 6cd4e6772e3..fb167c59590 100644 --- a/examples/gno.land/r/boards/z_5_c_filetest.gno +++ b/examples/gno.land/r/boards/z_5_c_filetest.gno @@ -33,7 +33,7 @@ func main() { // # First Post (title) // // Body of the first post. (body) -// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] +// \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] // // > Reply of the first post -// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [1970-01-01 12:00am (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] +// > \- [g1w3jhxapjta047h6lta047h6lta047h6laqcyu4](/r/users:g1w3jhxapjta047h6lta047h6lta047h6laqcyu4), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/1/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=1&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=2)] diff --git a/examples/gno.land/r/boards/z_5_filetest.gno b/examples/gno.land/r/boards/z_5_filetest.gno index a7ee3410c0f..8b5ff62432f 100644 --- a/examples/gno.land/r/boards/z_5_filetest.gno +++ b/examples/gno.land/r/boards/z_5_filetest.gno @@ -33,11 +33,11 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/4) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/4) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] diff --git a/examples/gno.land/r/boards/z_6_filetest.gno b/examples/gno.land/r/boards/z_6_filetest.gno index 6be30c6fcb4..697ca2730ee 100644 --- a/examples/gno.land/r/boards/z_6_filetest.gno +++ b/examples/gno.land/r/boards/z_6_filetest.gno @@ -35,15 +35,15 @@ func main() { // # Second Post (title) // // Body of the second post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=2&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=2)] // // > Reply of the second post -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // > // > > First reply of the first reply // > > -// > > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/5) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] +// > > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/5) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] // // > Second reply of the second post // > -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/4) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/4) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=4&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=4)] diff --git a/examples/gno.land/r/boards/z_7_filetest.gno b/examples/gno.land/r/boards/z_7_filetest.gno index 3b878078cde..d7b6e6c5ac2 100644 --- a/examples/gno.land/r/boards/z_7_filetest.gno +++ b/examples/gno.land/r/boards/z_7_filetest.gno @@ -28,4 +28,4 @@ func main() { // ## [First Post (title)](/r/boards:test_board/1) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am UTC](/r/boards:test_board/1) \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm UTC](/r/boards:test_board/1) \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=1&postid=1)] (0 replies) diff --git a/examples/gno.land/r/boards/z_8_filetest.gno b/examples/gno.land/r/boards/z_8_filetest.gno index 9c4e4a0fc3a..65f456ade05 100644 --- a/examples/gno.land/r/boards/z_8_filetest.gno +++ b/examples/gno.land/r/boards/z_8_filetest.gno @@ -35,10 +35,10 @@ func main() { // _[see thread](/r/boards:test_board/2)_ // // Reply of the second post -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/3) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=3&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=3)] // // _[see all 1 replies](/r/boards:test_board/2/3)_ // // > First reply of the first reply // > -// > \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:test_board/2/5) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] +// > \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:test_board/2/5) \[[reply](/r/boards?help&__func=CreateReply&bid=1&threadid=2&postid=5&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=1&threadid=2&postid=5)] diff --git a/examples/gno.land/r/boards/z_9_filetest.gno b/examples/gno.land/r/boards/z_9_filetest.gno index 63c674b424a..6d6c152be7e 100644 --- a/examples/gno.land/r/boards/z_9_filetest.gno +++ b/examples/gno.land/r/boards/z_9_filetest.gno @@ -34,4 +34,4 @@ func main() { // # First Post in (title) // // Body of the first post. (body) -// \- [@gnouser](/r/users:gnouser), [1970-01-01 12:00am (UTC)](/r/boards:second_board/1/1) \[[reply](/r/boards?help&__func=CreateReply&bid=2&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=2&threadid=1&postid=1)] +// \- [@gnouser](/r/users:gnouser), [2009-02-13 11:31pm (UTC)](/r/boards:second_board/1/1) \[[reply](/r/boards?help&__func=CreateReply&bid=2&threadid=1&postid=1&body.type=textarea)] \[[x](/r/boards?help&__func=DeletePost&bid=2&threadid=1&postid=1)] diff --git a/examples/gno.land/r/x/outfmt/outfmt_test.gno b/examples/gno.land/r/x/outfmt/outfmt_test.gno index 2ec7681d9ec..acab319fc38 100644 --- a/examples/gno.land/r/x/outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/outfmt/outfmt_test.gno @@ -23,8 +23,8 @@ func TestRender(t *testing.T) { { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 602 -Numbers: 38 88 +Number: 250 +Numbers: 98 25 21 45 33 32 81 ` if got != expected { t.Fatalf("expected %q, got %q.", expected, got) @@ -35,8 +35,8 @@ Numbers: 38 88 { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 418 -Numbers: 41 0 14 67 91 +Number: 550 +Numbers: 19 31 35 94 76 63 66 20 48 ` if got != expected { t.Fatalf("expected %q, got %q.", expected, got) @@ -47,7 +47,7 @@ Numbers: 41 0 14 67 91 // json { got := outfmt.Render("?fmt=json") - expected := `{"Number":467,"Text":"Hello Gnomes!","Numbers":[8,62]}` + expected := `{"Number":100,"Text":"Hello Gnomes!","Numbers":[66,24,80]}` if got != expected { t.Fatalf("expected %q, got %q.", expected, got) } @@ -57,7 +57,7 @@ Numbers: 41 0 14 67 91 // jsonp { got := outfmt.Render("?fmt=jsonp") - expected := `callback({"Number":384,"Text":"Hello Gnomes!","Numbers":[15,78]})` + expected := `callback({"Number":717,"Text":"Hello Gnomes!","Numbers":[60,1,43,24,1,21]})` if got != expected { t.Fatalf("expected %q, got %q.", expected, got) } diff --git a/gno.proto b/gno.proto index 960ebf0d07f..481e89e22de 100644 --- a/gno.proto +++ b/gno.proto @@ -21,6 +21,10 @@ message BigintValue { string Value = 1; } +message BigdecValue { + string Value = 1; +} + message PointerValue { TypedValue TV = 1; google.protobuf.Any Base = 2; diff --git a/go.mod b/go.mod index 429a428ba27..7684153f82d 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/cockroachdb/apd v1.1.0 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect diff --git a/go.sum b/go.sum index 89c2512fa2f..c46e78b458e 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/gonative.go b/gonative.go index f9769015f58..48d0ad727f6 100644 --- a/gonative.go +++ b/gonative.go @@ -2,7 +2,6 @@ package gno import ( "fmt" - "math" "reflect" ) @@ -403,41 +402,9 @@ func go2GnoValue(alloc *Allocator, rv reflect.Value) (tv TypedValue) { case reflect.Uint64: tv.SetUint64(uint64(rv.Uint())) case reflect.Float32: - fl := float32(rv.Float()) - u32 := math.Float32bits(fl) - if sv, ok := tv.V.(*StructValue); ok { // update - if debug { - if len(sv.Fields) != 1 { - panic("should not happen") - } - if sv.Fields[0].T != Uint32Type { - panic("invalid Float32Type, expected Uint32 field") - } - } - sv.Fields[0].SetUint32(u32) - } else { // create - ftv := TypedValue{T: Uint32Type} - ftv.SetUint32(u32) - tv.V = alloc.NewStructWithFields(ftv) - } + tv.SetFloat32(float32(rv.Float())) case reflect.Float64: - fl := rv.Float() - u64 := math.Float64bits(fl) - if sv, ok := tv.V.(*StructValue); ok { // update - if debug { - if len(sv.Fields) != 1 { - panic("should not happen") - } - if sv.Fields[0].T != Uint64Type { - panic("invalid Float64Type, expected Uint64 field") - } - } - sv.Fields[0].SetUint64(u64) - } else { // create - ftv := TypedValue{T: Uint64Type} - ftv.SetUint64(u64) - tv.V = alloc.NewStructWithFields(ftv) - } + tv.SetFloat64(float64(rv.Float())) case reflect.Array: tv.V = alloc.NewNative(rv) case reflect.Slice: @@ -532,8 +499,18 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv if lvl != 0 { tv.SetUint64(uint64(rv.Uint())) } + case Float32Kind: + if lvl != 0 { + tv.SetFloat32(float32(rv.Float())) + } + case Float64Kind: + if lvl != 0 { + tv.SetFloat64(float64(rv.Float())) + } case BigintKind: panic("not yet implemented") + case BigdecKind: + panic("not yet implemented") case ArrayKind: av := tv.V.(*ArrayValue) rvl := rv.Len() @@ -613,45 +590,18 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv case StructKind: st := baseOf(tv.T).(*StructType) sv := tv.V.(*StructValue) - switch st.PkgPath { - case float32PkgPath: // Special case if Float32. - fl := float32(rv.Float()) - u32 := math.Float32bits(fl) - if debug { - if len(sv.Fields) != 1 { - panic("should not happen") - } - if sv.Fields[0].T != Uint32Type { - panic("invalid Float32Type, expected Uint32 field") - } + for i := range st.Fields { + ft := st.Fields[i].Type + ftv := &sv.Fields[i] + // XXX use Assign and Realm? + if ftv.T == nil && ft.Kind() != InterfaceKind { + ftv.T = ft } - sv.Fields[0].SetUint32(u32) - case float64PkgPath: // Special case if Float64. - fl := rv.Float() - u64 := math.Float64bits(fl) - if debug { - if len(sv.Fields) != 1 { - panic("should not happen") - } - if sv.Fields[0].T != Uint64Type { - panic("invalid Float64Type, expected Uint64 field") - } - } - sv.Fields[0].SetUint64(u64) - default: // General case. - for i := range st.Fields { - ft := st.Fields[i].Type - ftv := &sv.Fields[i] - // XXX use Assign and Realm? - if ftv.T == nil && ft.Kind() != InterfaceKind { - ftv.T = ft - } - if ftv.V == nil { - ftv.V = defaultValue(alloc, ft) - } - frv := rv.Field(i) - go2GnoValueUpdate(alloc, rlm, lvl+1, ftv, frv) + if ftv.V == nil { + ftv.V = defaultValue(alloc, ft) } + frv := rv.Field(i) + go2GnoValueUpdate(alloc, rlm, lvl+1, ftv, frv) } case PackageKind: panic("not yet implemented") @@ -764,13 +714,9 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo case reflect.Uint64: tv.SetUint64(uint64(rv.Uint())) case reflect.Float32: - fl := float32(rv.Float()) - u32 := math.Float32bits(fl) - tv.SetUint32(u32) + tv.SetFloat32(float32(rv.Float())) case reflect.Float64: - fl := rv.Float() - u64 := math.Float64bits(fl) - tv.SetUint64(u64) + tv.SetFloat64(float64(rv.Float())) case reflect.Array: rvl := rv.Len() if rv.Type().Elem().Kind() == reflect.Uint8 { @@ -877,8 +823,14 @@ func gno2GoType(t Type) reflect.Type { return reflect.TypeOf(uint32(0)) case Uint64Type: return reflect.TypeOf(uint64(0)) + case Float32Type: + return reflect.TypeOf(float32(0)) + case Float64Type: + return reflect.TypeOf(float64(0)) case BigintType, UntypedBigintType: panic("not yet implemented") + case BigdecType, UntypedBigdecType: + panic("not yet implemented") default: panic("should not happen") } @@ -989,8 +941,14 @@ func gno2GoTypeMatches(t Type, rt reflect.Type) (result bool) { return rt.Kind() == reflect.Uint32 case Uint64Type: return rt.Kind() == reflect.Uint64 + case Float32Type: + return rt.Kind() == reflect.Float32 + case Float64Type: + return rt.Kind() == reflect.Float64 case BigintType, UntypedBigintType: panic("not yet implemented") + case BigdecType, UntypedBigdecType: + panic("not yet implemented") default: panic("should not happen") } @@ -1151,6 +1109,10 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { rv.SetUint(uint64(tv.GetUint32())) case Uint64Type: rv.SetUint(uint64(tv.GetUint64())) + case Float32Type: + rv.SetFloat(float64(tv.GetFloat32())) + case Float64Type: + rv.SetFloat(float64(tv.GetFloat64())) default: panic(fmt.Sprintf( "unexpected type %s", @@ -1224,42 +1186,12 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) { } // General case. sv := tv.V.(*StructValue) - switch ct.PkgPath { - case float32PkgPath: - // Special case if Float32. - if debug { - if len(sv.Fields) != 1 { - panic("should not happen") - } - if sv.Fields[0].T != Uint32Type { - panic("invalid Float32Type, expected Uint32 field") - } - } - u32 := sv.Fields[0].GetUint32() - fl := math.Float32frombits(u32) - rv.SetFloat(float64(fl)) - case float64PkgPath: - // Special case if Float64. - if debug { - if len(sv.Fields) != 1 { - panic("should not happen") - } - if sv.Fields[0].T != Uint64Type { - panic("invalid Float64Type, expected Uint64 field") - } - } - u64 := sv.Fields[0].GetUint64() - fl := math.Float64frombits(u64) - rv.SetFloat(fl) - default: - // General case. - for i := range ct.Fields { - ftv := &(sv.Fields[i]) - if ftv.IsUndefined() { - continue - } - gno2GoValue(ftv, rv.Field(i)) + for i := range ct.Fields { + ftv := &(sv.Fields[i]) + if ftv.IsUndefined() { + continue } + gno2GoValue(ftv, rv.Field(i)) } case *MapType: // If uninitialized map, return zero value. diff --git a/kind_string.go b/kind_string.go index b958de14973..c2d6ee89094 100644 --- a/kind_string.go +++ b/kind_string.go @@ -21,25 +21,28 @@ func _() { _ = x[Uint16Kind-10] _ = x[Uint32Kind-11] _ = x[Uint64Kind-12] - _ = x[BigintKind-13] - _ = x[ArrayKind-14] - _ = x[SliceKind-15] - _ = x[PointerKind-16] - _ = x[StructKind-17] - _ = x[PackageKind-18] - _ = x[InterfaceKind-19] - _ = x[ChanKind-20] - _ = x[FuncKind-21] - _ = x[MapKind-22] - _ = x[TypeKind-23] - _ = x[BlockKind-24] - _ = x[TupleKind-25] - _ = x[RefTypeKind-26] + _ = x[Float32Kind-13] + _ = x[Float64Kind-14] + _ = x[BigintKind-15] + _ = x[BigdecKind-16] + _ = x[ArrayKind-17] + _ = x[SliceKind-18] + _ = x[PointerKind-19] + _ = x[StructKind-20] + _ = x[PackageKind-21] + _ = x[InterfaceKind-22] + _ = x[ChanKind-23] + _ = x[FuncKind-24] + _ = x[MapKind-25] + _ = x[TypeKind-26] + _ = x[BlockKind-27] + _ = x[TupleKind-28] + _ = x[RefTypeKind-29] } -const _Kind_name = "InvalidKindBoolKindStringKindIntKindInt8KindInt16KindInt32KindInt64KindUintKindUint8KindUint16KindUint32KindUint64KindBigintKindArrayKindSliceKindPointerKindStructKindPackageKindInterfaceKindChanKindFuncKindMapKindTypeKindBlockKindTupleKindRefTypeKind" +const _Kind_name = "InvalidKindBoolKindStringKindIntKindInt8KindInt16KindInt32KindInt64KindUintKindUint8KindUint16KindUint32KindUint64KindFloat32KindFloat64KindBigintKindBigdecKindArrayKindSliceKindPointerKindStructKindPackageKindInterfaceKindChanKindFuncKindMapKindTypeKindBlockKindTupleKindRefTypeKind" -var _Kind_index = [...]uint8{0, 11, 19, 29, 36, 44, 53, 62, 71, 79, 88, 98, 108, 118, 128, 137, 146, 157, 167, 178, 191, 199, 207, 214, 222, 231, 240, 251} +var _Kind_index = [...]uint16{0, 11, 19, 29, 36, 44, 53, 62, 71, 79, 88, 98, 108, 118, 129, 140, 150, 160, 169, 178, 189, 199, 210, 223, 231, 239, 246, 254, 263, 272, 283} func (i Kind) String() string { if i >= Kind(len(_Kind_index)-1) { diff --git a/nodes.go b/nodes.go index cbfedead9b1..f735eb36451 100644 --- a/nodes.go +++ b/nodes.go @@ -980,7 +980,11 @@ type FuncDecl struct { } func (fd *FuncDecl) GetDeclNames() []Name { - return []Name{fd.NameExpr.Name} + if fd.IsMethod { + return nil + } else { + return []Name{fd.NameExpr.Name} + } } type ImportDecl struct { diff --git a/nodes_string.go b/nodes_string.go index f5651a9d8b9..715b2b44b52 100644 --- a/nodes_string.go +++ b/nodes_string.go @@ -124,6 +124,8 @@ func (n IndexExpr) String() string { } func (n SelectorExpr) String() string { + // NOTE: for debugging selector issues: + // return fmt.Sprintf("%s.(%v).%s", n.X, n.Path.Type, n.Sel) return fmt.Sprintf("%s.%s", n.X, n.Sel) } diff --git a/op_binary.go b/op_binary.go index b2c6b48d8b3..42114b6e206 100644 --- a/op_binary.go +++ b/op_binary.go @@ -3,6 +3,8 @@ package gno import ( "fmt" "math/big" + + "github.com/cockroachdb/apd" ) //---------------------------------------- @@ -379,10 +381,18 @@ func isEql(store Store, lv, rv *TypedValue) bool { return (lv.GetUint32() == rv.GetUint32()) case Uint64Kind: return (lv.GetUint64() == rv.GetUint64()) + case Float32Kind: + return (lv.GetFloat32() == rv.GetFloat32()) // XXX determinism? + case Float64Kind: + return (lv.GetFloat64() == rv.GetFloat64()) // XXX determinism? case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V return lb.Cmp(rb) == 0 + case BigdecKind: + lb := lv.V.(BigdecValue).V + rb := rv.V.(BigdecValue).V + return lb.Cmp(rb) == 0 case ArrayKind: la := lv.V.(*ArrayValue) ra := rv.V.(*ArrayValue) @@ -500,10 +510,18 @@ func isLss(lv, rv *TypedValue) bool { return (lv.GetUint32() < rv.GetUint32()) case Uint64Kind: return (lv.GetUint64() < rv.GetUint64()) + case Float32Kind: + return (lv.GetFloat32() < rv.GetFloat32()) // XXX determinism? + case Float64Kind: + return (lv.GetFloat64() < rv.GetFloat64()) // XXX determinism? case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V return lb.Cmp(rb) < 0 + case BigdecKind: + lb := lv.V.(BigdecValue).V + rb := rv.V.(BigdecValue).V + return lb.Cmp(rb) < 0 default: panic(fmt.Sprintf( "comparison operator < not defined for %s", @@ -536,10 +554,18 @@ func isLeq(lv, rv *TypedValue) bool { return (lv.GetUint32() <= rv.GetUint32()) case Uint64Kind: return (lv.GetUint64() <= rv.GetUint64()) + case Float32Kind: + return (lv.GetFloat32() <= rv.GetFloat32()) // XXX determinism? + case Float64Kind: + return (lv.GetFloat64() <= rv.GetFloat64()) // XXX determinism? case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V return lb.Cmp(rb) <= 0 + case BigdecKind: + lb := lv.V.(BigdecValue).V + rb := rv.V.(BigdecValue).V + return lb.Cmp(rb) <= 0 default: panic(fmt.Sprintf( "comparison operator <= not defined for %s", @@ -572,10 +598,18 @@ func isGtr(lv, rv *TypedValue) bool { return (lv.GetUint32() > rv.GetUint32()) case Uint64Kind: return (lv.GetUint64() > rv.GetUint64()) + case Float32Kind: + return (lv.GetFloat32() > rv.GetFloat32()) // XXX determinism? + case Float64Kind: + return (lv.GetFloat64() > rv.GetFloat64()) // XXX determinism? case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V return lb.Cmp(rb) > 0 + case BigdecKind: + lb := lv.V.(BigdecValue).V + rb := rv.V.(BigdecValue).V + return lb.Cmp(rb) > 0 default: panic(fmt.Sprintf( "comparison operator > not defined for %s", @@ -608,10 +642,18 @@ func isGeq(lv, rv *TypedValue) bool { return (lv.GetUint32() >= rv.GetUint32()) case Uint64Kind: return (lv.GetUint64() >= rv.GetUint64()) + case Float32Kind: + return (lv.GetFloat32() >= rv.GetFloat32()) // XXX determinism? + case Float64Kind: + return (lv.GetFloat64() >= rv.GetFloat64()) // XXX determinism? case BigintKind: lb := lv.V.(BigintValue).V rb := rv.V.(BigintValue).V return lb.Cmp(rb) >= 0 + case BigdecKind: + lb := lv.V.(BigdecValue).V + rb := rv.V.(BigdecValue).V + return lb.Cmp(rb) >= 0 default: panic(fmt.Sprintf( "comparison operator >= not defined for %s", @@ -649,10 +691,27 @@ func addAssign(alloc *Allocator, lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() + rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() + rv.GetUint64()) + case Float32Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat32(lv.GetFloat32() + rv.GetFloat32()) // XXX determinsm? + case Float64Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat64(lv.GetFloat64() + rv.GetFloat64()) // XXX determinism? case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Add(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Add(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} + case BigdecType, UntypedBigdecType: + lb := lv.GetBigDec() + rb := rv.GetBigDec() + sum := apd.New(0, 0) + cond, err := apd.BaseContext.WithPrecision(0).Add(sum, lb, rb) + if err != nil { + panic(fmt.Sprintf("bigdec addition error: %v", err)) + } else if cond.Inexact() { + panic(fmt.Sprintf("bigdec addition inexact: %v + %v", lb, rb)) + } + lv.V = BigdecValue{V: sum} default: panic(fmt.Sprintf( "operators + and += not defined for %s", @@ -688,10 +747,27 @@ func subAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() - rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() - rv.GetUint64()) + case Float32Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat32(lv.GetFloat32() - rv.GetFloat32()) // XXX determinism? + case Float64Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat64(lv.GetFloat64() - rv.GetFloat64()) // XXX determinism? case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Sub(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Sub(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} + case BigdecType, UntypedBigdecType: + lb := lv.GetBigDec() + rb := rv.GetBigDec() + diff := apd.New(0, 0) + cond, err := apd.BaseContext.WithPrecision(0).Sub(diff, lb, rb) + if err != nil { + panic(fmt.Sprintf("bigdec subtraction error: %v", err)) + } else if cond.Inexact() { + panic(fmt.Sprintf("bigdec subtraction inexact: %v + %v", lb, rb)) + } + lv.V = BigdecValue{V: diff} default: panic(fmt.Sprintf( "operators - and -= not defined for %s", @@ -727,10 +803,25 @@ func mulAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() * rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() * rv.GetUint64()) + case Float32Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat32(lv.GetFloat32() * rv.GetFloat32()) // XXX determinism? + case Float64Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat64(lv.GetFloat64() * rv.GetFloat64()) // XXX determinism? case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Mul(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Mul(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} + case BigdecType, UntypedBigdecType: + lb := lv.GetBigDec() + rb := rv.GetBigDec() + prod := apd.New(0, 0) + _, err := apd.BaseContext.WithPrecision(1024).Mul(prod, lb, rb) + if err != nil { + panic(fmt.Sprintf("bigdec multiplication error: %v", err)) + } + lv.V = BigdecValue{V: prod} default: panic(fmt.Sprintf( "operators * and *= not defined for %s", @@ -766,10 +857,27 @@ func quoAssign(lv, rv *TypedValue) { lv.SetUint32(lv.GetUint32() / rv.GetUint32()) case Uint64Type: lv.SetUint64(lv.GetUint64() / rv.GetUint64()) + case Float32Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat32(lv.GetFloat32() / rv.GetFloat32()) + // XXX FOR DETERMINISM, PANIC IF NAN. + case Float64Type: + // NOTE: gno doesn't fuse *+. + lv.SetFloat64(lv.GetFloat64() / rv.GetFloat64()) + // XXX FOR DETERMINISM, PANIC IF NAN. case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Quo(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Quo(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} + case BigdecType, UntypedBigdecType: + lb := lv.GetBigDec() + rb := rv.GetBigDec() + quo := apd.New(0, 0) + _, err := apd.BaseContext.WithPrecision(1024).Quo(quo, lb, rb) + if err != nil { + panic(fmt.Sprintf("bigdec division error: %v", err)) + } + lv.V = BigdecValue{V: quo} default: panic(fmt.Sprintf( "operators / and /= not defined for %s", @@ -806,8 +914,8 @@ func remAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() % rv.GetUint64()) case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Rem(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Rem(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -845,8 +953,8 @@ func bandAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() & rv.GetUint64()) case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).And(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).And(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -884,8 +992,8 @@ func bandnAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() &^ rv.GetUint64()) case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).AndNot(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).AndNot(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -923,8 +1031,8 @@ func borAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() | rv.GetUint64()) case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Or(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Or(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -962,8 +1070,8 @@ func xorAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() ^ rv.GetUint64()) case BigintType, UntypedBigintType: - lb := lv.GetBig() - lb = big.NewInt(0).Xor(lb, rv.GetBig()) + lb := lv.GetBigInt() + lb = big.NewInt(0).Xor(lb, rv.GetBigInt()) lv.V = BigintValue{V: lb} default: panic(fmt.Sprintf( @@ -1001,7 +1109,7 @@ func shlAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() << rv.GetUint()) case BigintType, UntypedBigintType: - lb := lv.GetBig() + lb := lv.GetBigInt() lb = big.NewInt(0).Lsh(lb, rv.GetUint()) lv.V = BigintValue{V: lb} default: @@ -1040,7 +1148,7 @@ func shrAssign(lv, rv *TypedValue) { case Uint64Type: lv.SetUint64(lv.GetUint64() >> rv.GetUint()) case BigintType, UntypedBigintType: - lb := lv.GetBig() + lb := lv.GetBigInt() lb = big.NewInt(0).Rsh(lb, rv.GetUint()) lv.V = BigintValue{V: lb} default: diff --git a/op_eval.go b/op_eval.go index fad21242171..8ac452aed31 100644 --- a/op_eval.go +++ b/op_eval.go @@ -2,10 +2,13 @@ package gno import ( "fmt" + "math" "math/big" "regexp" "strconv" "strings" + + "github.com/cockroachdb/apd" ) func (m *Machine) doOpEval() { @@ -72,51 +75,116 @@ func (m *Machine) doOpEval() { V: BigintValue{V: bi}, }) case FLOAT: - // Special case if ieee notation of integer type. - if matched, _ := regexp.MatchString(`[\-\+]?[0-9]+e[0-9]+`, x.Value); matched { + if matched, _ := regexp.MatchString(`^[0-9\.]+([eE][\-\+]?[0-9]+)?$`, x.Value); matched { value := x.Value - isNeg := false - if x.Value[0] == '-' { - isNeg = true - value = x.Value[1:] - } else if x.Value[0] == '+' { - isNeg = false - value = x.Value[1:] - } - parts := strings.SplitN(value, "e", 2) - if len(parts) != 2 { + bd, c, err := apd.NewFromString(value) + if err != nil { panic(fmt.Sprintf( - "invalid integer constant: %s", + "invalid decimal constant: %s", x.Value)) } - first, err := strconv.Atoi(parts[0]) - if err != nil { + if c.Inexact() { panic(fmt.Sprintf( - "invalid integer constant: %s", + "could not represent decimal exactly: %s", x.Value)) } - second, err := strconv.Atoi(parts[1]) + m.PushValue(TypedValue{ + T: UntypedBigdecType, + V: BigdecValue{V: bd}, + }) + return + } else if matched, _ := regexp.MatchString(`^0[xX][0-9a-fA-F\.]+([pP][\-\+]?[0-9a-fA-F]+)?$`, x.Value); matched { + originalInput := x.Value + value := x.Value[2:] + var hexString string + var exp int64 + eIndex := strings.IndexAny(value, "Pp") + if eIndex == -1 { + panic("should not happen") + } + + //---------------------------------------- + // NewFromHexString() + // TODO: move this to another function. + + // Step 1 get exp component. + expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32) if err != nil { + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + panic(fmt.Sprintf( + "can't convert %s to decimal: fractional part too long", + value)) + } panic(fmt.Sprintf( - "invalid integer constant: %s", - x.Value)) + "can't convert %s to decimal: exponent is not numeric", + value)) + } + value = value[:eIndex] + exp = expInt + // Step 2 adjust exp from dot. + pIndex := -1 + vLen := len(value) + for i := 0; i < vLen; i++ { + if value[i] == '.' { + if pIndex > -1 { + panic(fmt.Sprintf( + "can't convert %s to decimal: too many .s", + value)) + } + pIndex = i + } + } + if pIndex == -1 { + // There is no decimal point, we can just parse the original string as + // a hex + hexString = value + } else { + if pIndex+1 < vLen { + hexString = value[:pIndex] + value[pIndex+1:] + } else { + hexString = value[:pIndex] + } + expInt := -len(value[pIndex+1:]) + exp += int64(expInt) + } + bexp := apd.New(0, 0) + _, err = apd.BaseContext.WithPrecision(1024).Pow( + bexp, + apd.New(2, 0), + apd.New(exp, 0)) + if err != nil { + panic(fmt.Sprintf("error computing exponent: %v", err)) } - bi := big.NewInt(0) - bi = bi.Exp(big.NewInt(10), big.NewInt(int64(second)), nil) - bi = bi.Mul(bi, big.NewInt(int64(first))) - if isNeg { - bi = bi.Mul(bi, big.NewInt(-1)) + // Step 3 make Decimal from mantissa and exp. + var dValue *big.Int + dValue = new(big.Int) + _, ok := dValue.SetString(hexString, 16) + if !ok { + panic(fmt.Sprintf("can't convert %s to decimal", value)) + } + if exp < math.MinInt32 || exp > math.MaxInt32 { + // NOTE(vadim): I doubt a string could realistically be this long + panic(fmt.Sprintf("can't convert %s to decimal: fractional part too long", originalInput)) + } + res := apd.New(0, 0) + _, err = apd.BaseContext.WithPrecision(1024).Mul( + res, + apd.NewWithBigInt(dValue, 0), + bexp) + if err != nil { + panic(fmt.Sprintf("canot calculate hexadecimal: %v", err)) } + + // NewFromHexString() END + //---------------------------------------- + m.PushValue(TypedValue{ - T: UntypedBigintType, - V: BigintValue{V: bi}, + T: UntypedBigdecType, + V: BigdecValue{V: res}, }) return } else { - // NOTE: I suspect we won't get hardware-level - // consistency (determinism) in floating point numbers - // yet, so hold off on this until we master this. - panic(fmt.Sprintf("floats are not supported: %v", x.String())) + panic(fmt.Sprintf("unexpected decimal/float format %s", x.Value)) } case IMAG: // NOTE: this is a syntax and grammar problem, not an diff --git a/op_string.go b/op_string.go index 99e0cebc37f..0805091f26a 100644 --- a/op_string.go +++ b/op_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type Op"; DO NOT EDIT. +// Code generated by "stringer -type=Op"; DO NOT EDIT. package gno diff --git a/op_unary.go b/op_unary.go index a42ee41a771..a02c921243f 100644 --- a/op_unary.go +++ b/op_unary.go @@ -3,6 +3,8 @@ package gno import ( "fmt" "math/big" + + "github.com/cockroachdb/apd" ) func (m *Machine) doOpUpos() { @@ -43,9 +45,16 @@ func (m *Machine) doOpUneg() { xv.SetUint32(-xv.GetUint32()) case Uint64Type: xv.SetUint64(-xv.GetUint64()) + case Float32Type: + xv.SetFloat32(-xv.GetFloat32()) + case Float64Type: + xv.SetFloat64(-xv.GetFloat64()) case UntypedBigintType, BigintType: bv := xv.V.(BigintValue) xv.V = BigintValue{V: new(big.Int).Neg(bv.V)} + case UntypedBigdecType, BigdecType: + bv := xv.V.(BigdecValue) + xv.V = BigdecValue{V: apd.New(0, 0).Neg(bv.V)} case nil: // NOTE: for now only BigintValue is possible. bv := xv.V.(BigintValue) diff --git a/package.go b/package.go index 6aa90014904..51d068ddc85 100644 --- a/package.go +++ b/package.go @@ -15,6 +15,7 @@ var Package = amino.RegisterPackage(amino.NewPackage( &TypedValue{}, StringValue(""), BigintValue{}, + BigdecValue{}, // DataByteValue{} PointerValue{}, &ArrayValue{}, diff --git a/pkgs/flow/LICENSE.md b/pkgs/flow/LICENSE.md new file mode 100644 index 00000000000..2c488e7ed30 --- /dev/null +++ b/pkgs/flow/LICENSE.md @@ -0,0 +1,32 @@ +https://github.com/mxk/go-flowrate/blob/master/LICENSE +BSD 3-Clause "New" or "Revised" License + +Copyright (c) 2014 The Go-FlowRate Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + + * Neither the name of the go-flowrate project nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgs/sdk/vm/builtins.go b/pkgs/sdk/vm/builtins.go index db306f6b607..ab40bfaea23 100644 --- a/pkgs/sdk/vm/builtins.go +++ b/pkgs/sdk/vm/builtins.go @@ -26,9 +26,10 @@ func (vmk *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { } memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "", - Output: os.Stdout, - Store: store, + PkgPath: "gno.land/r/stdlibs/" + pkgPath, + // PkgPath: pkgPath, + Output: os.Stdout, + Store: store, }) return m2.RunMemPackage(memPkg, true) } diff --git a/pkgs/std/std.proto b/pkgs/std/std.proto index d3e4ce97f1f..643871438c8 100644 --- a/pkgs/std/std.proto +++ b/pkgs/std/std.proto @@ -56,6 +56,12 @@ message InvalidPubKeyError { message InsufficientCoinsError { } +message InvalidCoinsError { +} + +message InvalidGasWantedError { +} + message OutOfGasError { } diff --git a/preprocess.go b/preprocess.go index ae6bd3fbee8..3489f1c849a 100644 --- a/preprocess.go +++ b/preprocess.go @@ -392,11 +392,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // SwitchClauseStmt:TRANS_BLOCK. last.Define(n.VarName, anyValue(nil)) } - // Preprocess and convert tag if const. - if n.X != nil { - n.X = Preprocess(store, last, n.X).(Expr) - convertIfConst(store, last, n.X) - } // TRANS_BLOCK ----------------------- case *SwitchClauseStmt: @@ -412,7 +407,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if ss.IsTypeSwitch { if len(n.Cases) == 0 { // evaluate default case. - if 0 < len(ss.VarName) { + if ss.VarName != "" { // The type is the tag type. tt := evalStaticTypeOf(store, last, ss.X) last.Define( @@ -435,7 +430,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } n.Cases[i] = constType(cx, ct) // maybe type-switch def. - if 0 < len(ss.VarName) { + if ss.VarName != "" { if len(n.Cases) == 1 { // If there is only 1 case, the // define applies with type. @@ -583,6 +578,24 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } return n, TRANS_CONTINUE + //---------------------------------------- + case TRANS_BLOCK2: + + // The main TRANS_BLOCK2 switch. + switch n := n.(type) { + + // TRANS_BLOCK2 ----------------------- + case *SwitchStmt: + + // NOTE: TRANS_BLOCK2 ensures after .Init. + // Preprocess and convert tag if const. + if n.X != nil { + n.X = Preprocess(store, last, n.X).(Expr) + convertIfConst(store, last, n.X) + } + } + return n, TRANS_CONTINUE + //---------------------------------------- case TRANS_LEAVE: // mark as preprocessed so that it can be used @@ -769,6 +782,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // (the other way around would work too) // checkOrConvertType(store, last, n.Left, rcx.T, false) } else { + // convert n.Right to left type. checkOrConvertType(store, last, &n.Right, lcx.T, false) } } @@ -941,12 +955,31 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { panic("type conversion requires single argument") } n.NumArgs = 1 - if _, ok := n.Args[0].(*ConstExpr); ok { - convertIfConst(store, last, n.Args[0]) + if arg0, ok := n.Args[0].(*ConstExpr); ok { + ct := evalStaticType(store, last, n.Func) + // As a special case, if a decimal cannot + // be represented as an integer, it cannot be converted to one, + // and the error is handled here. + // Out of bounds errors are usually handled during evalConst(). + switch ct.Kind() { + case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind, + UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind, + BigintKind: + if bd, ok := arg0.TypedValue.V.(BigdecValue); ok { + if !isInteger(bd.V) { + panic(fmt.Sprintf( + "cannot convert %s to integer type", + arg0)) + } + } + } + // (const) untyped decimal -> float64. + // (const) untyped bigint -> int. + convertConst(store, last, arg0, nil) + // evaluate the new expression. cx := evalConst(store, last, n) // Though cx may be undefined if ct is interface, // the ATTR_TYPEOF_VALUE is still interface. - ct := evalStaticType(store, last, n.Func) cx.SetAttribute(ATTR_TYPEOF_VALUE, ct) return cx, TRANS_CONTINUE } else { diff --git a/realm.go b/realm.go index 7744c895b87..eeec35cf70c 100644 --- a/realm.go +++ b/realm.go @@ -815,6 +815,8 @@ func getChildObjects(val Value, more []Value) []Value { return more case BigintValue: return more + case BigdecValue: + return more case DataByteValue: panic("should not happen") case PointerValue: @@ -1058,6 +1060,8 @@ func copyValueWithRefs(parent Object, val Value) Value { return cv case BigintValue: return cv + case BigdecValue: + return cv case DataByteValue: panic("should not happen") case PointerValue: diff --git a/stdlibs/internal/math/math.gno b/stdlibs/internal/math/math.gno new file mode 100644 index 00000000000..42245392caf --- /dev/null +++ b/stdlibs/internal/math/math.gno @@ -0,0 +1,3 @@ +package math + +// XXX injected via stdlibs/stdlibs.go diff --git a/stdlibs/internal/os/os.gno b/stdlibs/internal/os/os.gno new file mode 100644 index 00000000000..8dcee3f9784 --- /dev/null +++ b/stdlibs/internal/os/os.gno @@ -0,0 +1,3 @@ +package os + +// NOTE: everything is declared in stdlibs/stdlibs.go as injectors. diff --git a/stdlibs/math/abs.gno b/stdlibs/math/abs.gno new file mode 100644 index 00000000000..7a55c0c5bd2 --- /dev/null +++ b/stdlibs/math/abs.gno @@ -0,0 +1,19 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package math + +import ( + imath "internal/math" // XXX +) + +// Abs returns the absolute value of x. +// +// Special cases are: +// +// Abs(±Inf) = +Inf +// Abs(NaN) = NaN +func Abs(x float64) float64 { + return imath.Float64frombits(imath.Float64bits(x) &^ (1 << 63)) +} diff --git a/stdlibs/math/bits.gno b/stdlibs/math/bits.gno new file mode 100644 index 00000000000..6cf646d2f5e --- /dev/null +++ b/stdlibs/math/bits.gno @@ -0,0 +1,66 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package math + +import ( + imath "internal/math" +) + +const ( + uvnan = 0x7FF8000000000001 + uvinf = 0x7FF0000000000000 + uvneginf = 0xFFF0000000000000 + uvone = 0x3FF0000000000000 + mask = 0x7FF + shift = 64 - 11 - 1 + bias = 1023 + signMask = 1 << 63 + fracMask = 1<= 0, negative infinity if sign < 0. +func Inf(sign int) float64 { + var v uint64 + if sign >= 0 { + v = uvinf + } else { + v = uvneginf + } + return imath.Float64frombits(v) +} + +// NaN returns an IEEE 754 “not-a-number” value. +func NaN() float64 { return imath.Float64frombits(uvnan) } + +// IsNaN reports whether f is an IEEE 754 “not-a-number” value. +func IsNaN(f float64) (is bool) { + // IEEE 754 says that only NaNs satisfy f != f. + // To avoid the floating-point hardware, could use: + // x := Float64bits(f); + // return uint32(x>>shift)&mask == mask && x != uvinf && x != uvneginf + return f != f +} + +// IsInf reports whether f is an infinity, according to sign. +// If sign > 0, IsInf reports whether f is positive infinity. +// If sign < 0, IsInf reports whether f is negative infinity. +// If sign == 0, IsInf reports whether f is either infinity. +func IsInf(f float64, sign int) bool { + // Test for infinity by comparing against maximum float. + // To avoid the floating-point hardware, could use: + // x := Float64bits(f); + // return sign >= 0 && x == uvinf || sign <= 0 && x == uvneginf; + return sign >= 0 && f > MaxFloat64 || sign <= 0 && f < -MaxFloat64 +} + +// normalize returns a normal number y and exponent exp +// satisfying x == y × 2**exp. It assumes x is finite and non-zero. +func normalize(x float64) (y float64, exp int) { + const SmallestNormal = 2.2250738585072014e-308 // 2**-1022 + if Abs(x) < SmallestNormal { + return x * (1 << 52), -52 + } + return x, 0 +} diff --git a/stdlibs/math/const.gno b/stdlibs/math/const.gno index a5adaf4bdce..5ea935fb425 100644 --- a/stdlibs/math/const.gno +++ b/stdlibs/math/const.gno @@ -8,8 +8,6 @@ package math // Mathematical constants. -// XXX floats removed. -/* const ( E = 2.71828182845904523536028747135266249775724709369995957496696763 // https://oeis.org/A001113 Pi = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796 @@ -36,7 +34,6 @@ const ( MaxFloat64 = 0x1p1023 * (1 + (1 - 0x1p-52)) // 1.79769313486231570814527423731704356798070e+308 SmallestNonzeroFloat64 = 0x1p-1022 * 0x1p-52 // 4.9406564584124654417656879286822137236505980e-324 ) -*/ // Integer limit values. const ( diff --git a/stdlibs/math/copysign.gno b/stdlibs/math/copysign.gno new file mode 100644 index 00000000000..a5e05427c6f --- /dev/null +++ b/stdlibs/math/copysign.gno @@ -0,0 +1,16 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package math + +import ( + imath "internal/math" +) + +// Copysign returns a value with the magnitude of f +// and the sign of sign. +func Copysign(f, sign float64) float64 { + const signBit = 1 << 63 + return imath.Float64frombits(imath.Float64bits(f)&^signBit | imath.Float64bits(sign)&signBit) +} diff --git a/stdlibs/math/exp.gno b/stdlibs/math/exp.gno new file mode 100644 index 00000000000..a4112208b62 --- /dev/null +++ b/stdlibs/math/exp.gno @@ -0,0 +1,207 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package math + +// Exp returns e**x, the base-e exponential of x. +// +// Special cases are: +// +// Exp(+Inf) = +Inf +// Exp(NaN) = NaN +// +// Very large values overflow to 0 or +Inf. +// Very small values underflow to 1. +func Exp(x float64) float64 { + /* XXX + if haveArchExp { + return archExp(x) + } + */ + return exp(x) +} + +// The original C code, the long comment, and the constants +// below are from FreeBSD's /usr/src/lib/msun/src/e_exp.c +// and came with this notice. The go code is a simplified +// version of the original C. +// +// ==================================================== +// Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. +// +// Permission to use, copy, modify, and distribute this +// software is freely granted, provided that this notice +// is preserved. +// ==================================================== +// +// +// exp(x) +// Returns the exponential of x. +// +// Method +// 1. Argument reduction: +// Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658. +// Given x, find r and integer k such that +// +// x = k*ln2 + r, |r| <= 0.5*ln2. +// +// Here r will be represented as r = hi-lo for better +// accuracy. +// +// 2. Approximation of exp(r) by a special rational function on +// the interval [0,0.34658]: +// Write +// R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... +// We use a special Remez algorithm on [0,0.34658] to generate +// a polynomial of degree 5 to approximate R. The maximum error +// of this polynomial approximation is bounded by 2**-59. In +// other words, +// R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5 +// (where z=r*r, and the values of P1 to P5 are listed below) +// and +// | 5 | -59 +// | 2.0+P1*z+...+P5*z - R(z) | <= 2 +// | | +// The computation of exp(r) thus becomes +// 2*r +// exp(r) = 1 + ------- +// R - r +// r*R1(r) +// = 1 + r + ----------- (for better accuracy) +// 2 - R1(r) +// where +// 2 4 10 +// R1(r) = r - (P1*r + P2*r + ... + P5*r ). +// +// 3. Scale back to obtain exp(x): +// From step 1, we have +// exp(x) = 2**k * exp(r) +// +// Special cases: +// exp(INF) is INF, exp(NaN) is NaN; +// exp(-INF) is 0, and +// for finite argument, only exp(0)=1 is exact. +// +// Accuracy: +// according to an error analysis, the error is always less than +// 1 ulp (unit in the last place). +// +// Misc. info. +// For IEEE double +// if x > 7.09782712893383973096e+02 then exp(x) overflow +// if x < -7.45133219101941108420e+02 then exp(x) underflow +// +// Constants: +// The hexadecimal values are the intended ones for the following +// constants. The decimal values may be used, provided that the +// compiler will convert from decimal to binary accurately enough +// to produce the hexadecimal values shown. + +func exp(x float64) float64 { + const ( + Ln2Hi = 6.93147180369123816490e-01 + Ln2Lo = 1.90821492927058770002e-10 + Log2e = 1.44269504088896338700e+00 + + Overflow = 7.09782712893383973096e+02 + Underflow = -7.45133219101941108420e+02 + NearZero = 1.0 / (1 << 28) // 2**-28 + ) + + // special cases + switch { + case IsNaN(x) || IsInf(x, 1): + return x + case IsInf(x, -1): + return 0 + case x > Overflow: + return Inf(1) + case x < Underflow: + return 0 + case -NearZero < x && x < NearZero: + return 1 + x + } + + // reduce; computed as r = hi - lo for extra precision. + var k int + switch { + case x < 0: + k = int(Log2e*x - 0.5) + case x > 0: + k = int(Log2e*x + 0.5) + } + hi := x - float64(k)*Ln2Hi + lo := float64(k) * Ln2Lo + + // compute + return expmulti(hi, lo, k) +} + +// Exp2 returns 2**x, the base-2 exponential of x. +// +// Special cases are the same as Exp. +func Exp2(x float64) float64 { + /* XXX + if haveArchExp2 { + return archExp2(x) + } + */ + return exp2(x) +} + +func exp2(x float64) float64 { + const ( + Ln2Hi = 6.93147180369123816490e-01 + Ln2Lo = 1.90821492927058770002e-10 + + Overflow = 1.0239999999999999e+03 + Underflow = -1.0740e+03 + ) + + // special cases + switch { + case IsNaN(x) || IsInf(x, 1): + return x + case IsInf(x, -1): + return 0 + case x > Overflow: + return Inf(1) + case x < Underflow: + return 0 + } + + // argument reduction; x = r×lg(e) + k with |r| ≤ ln(2)/2. + // computed as r = hi - lo for extra precision. + var k int + switch { + case x > 0: + k = int(x + 0.5) + case x < 0: + k = int(x - 0.5) + } + t := x - float64(k) + hi := t * Ln2Hi + lo := -t * Ln2Lo + + // compute + return expmulti(hi, lo, k) +} + +// exp1 returns e**r × 2**k where r = hi - lo and |r| ≤ ln(2)/2. +func expmulti(hi, lo float64, k int) float64 { + const ( + P1 = 1.66666666666666657415e-01 /* 0x3FC55555; 0x55555555 */ + P2 = -2.77777777770155933842e-03 /* 0xBF66C16C; 0x16BEBD93 */ + P3 = 6.61375632143793436117e-05 /* 0x3F11566A; 0xAF25DE2C */ + P4 = -1.65339022054652515390e-06 /* 0xBEBBBD41; 0xC5D26BF1 */ + P5 = 4.13813679705723846039e-08 /* 0x3E663769; 0x72BEA4D0 */ + ) + + r := hi - lo + t := r * r + c := r - t*(P1+t*(P2+t*(P3+t*(P4+t*P5)))) + y := 1 - ((lo - (r*c)/(2-c)) - hi) + // TODO(rsc): make sure Ldexp can handle boundary k + return Ldexp(y, k) +} diff --git a/stdlibs/math/ldexp.gno b/stdlibs/math/ldexp.gno new file mode 100644 index 00000000000..5af0cff2c3b --- /dev/null +++ b/stdlibs/math/ldexp.gno @@ -0,0 +1,57 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package math + +import ( + imath "internal/math" +) + +// Ldexp is the inverse of Frexp. +// It returns frac × 2**exp. +// +// Special cases are: +// +// Ldexp(±0, exp) = ±0 +// Ldexp(±Inf, exp) = ±Inf +// Ldexp(NaN, exp) = NaN +func Ldexp(frac float64, exp int) float64 { + /* XXX + if haveArchLdexp { + return archLdexp(frac, exp) + } + */ + return ldexp(frac, exp) +} + +func ldexp(frac float64, exp int) float64 { + // special cases + switch { + case frac == 0: + return frac // correctly return -0 + case IsInf(frac, 0) || IsNaN(frac): + return frac + } + frac, e := normalize(frac) + exp += e + x := imath.Float64bits(frac) + exp += int(x>>shift)&mask - bias + if exp < -1075 { + return Copysign(0, frac) // underflow + } + if exp > 1023 { // overflow + if frac < 0 { + return Inf(-1) + } + return Inf(1) + } + var m float64 = 1 + if exp < -1022 { // denormal + exp += 53 + m = 1.0 / (1 << 53) // 2**-53 + } + x &^= mask << shift + x |= uint64(exp+bias) << shift + return m * imath.Float64frombits(x) +} diff --git a/stdlibs/std/time.gno b/stdlibs/std/time.gno index e0bb21d3146..2fdc9dd7602 100644 --- a/stdlibs/std/time.gno +++ b/stdlibs/std/time.gno @@ -1,5 +1,6 @@ package std +// XXX DEPRECATED, use "time" instead. // Represents seconds of UTC time since // Unix epoch 1970-01-01T00:00:00Z. Must // be from 0001-01-01T00:00:00Z to diff --git a/stdlibs/stdlibs.go b/stdlibs/stdlibs.go index 443382387ef..304f5eed112 100644 --- a/stdlibs/stdlibs.go +++ b/stdlibs/stdlibs.go @@ -1,6 +1,7 @@ package stdlibs import ( + "math" "reflect" "strconv" "time" @@ -19,6 +20,78 @@ func InjectNativeMappings(store gno.Store) { func InjectPackage(store gno.Store, pn *gno.PackageNode) { switch pn.PkgPath { + case "internal/math": + pn.DefineNative("Float32bits", + gno.Flds( // params + "f", "float32", + ), + gno.Flds( // results + "b", "uint32", + ), + func(m *gno.Machine) { + arg0 := m.LastBlock().GetParams1().TV + res0 := typedUint32(math.Float32bits(arg0.GetFloat32())) + m.PushValue(res0) + }, + ) + pn.DefineNative("Float32frombits", + gno.Flds( // params + "b", "uint32", + ), + gno.Flds( // results + "f", "float32", + ), + func(m *gno.Machine) { + arg0 := m.LastBlock().GetParams1().TV + res0 := typedFloat32(math.Float32frombits(arg0.GetUint32())) + m.PushValue(res0) + }, + ) + pn.DefineNative("Float64bits", + gno.Flds( // params + "f", "float64", + ), + gno.Flds( // results + "b", "uint64", + ), + func(m *gno.Machine) { + arg0 := m.LastBlock().GetParams1().TV + res0 := typedUint64(math.Float64bits(arg0.GetFloat64())) + m.PushValue(res0) + }, + ) + pn.DefineNative("Float64frombits", + gno.Flds( // params + "b", "uint64", + ), + gno.Flds( // results + "f", "float64", + ), + func(m *gno.Machine) { + arg0 := m.LastBlock().GetParams1().TV + res0 := typedFloat64(math.Float64frombits(arg0.GetUint64())) + m.PushValue(res0) + }, + ) + case "internal/os": + pn.DefineNative("Now", + gno.Flds( // params + ), + gno.Flds( // results + "sec", "int64", + "nsec", "int32", + "mono", "int64", + ), + func(m *gno.Machine) { + ctx := m.Context.(ExecContext) + res0 := typedInt64(ctx.Timestamp) + res1 := typedInt32(0) + res2 := typedInt32(0) + m.PushValue(res0) + m.PushValue(res1) + m.PushValue(res2) + }, + ) case "strconv": pn.DefineGoNativeValue("Itoa", strconv.Itoa) pn.DefineGoNativeValue("Atoi", strconv.Atoi) @@ -263,6 +336,7 @@ func InjectPackage(store gno.Store, pn *gno.PackageNode) { m.PushValue(res0) }, ) + // XXX DEPRECATED, use stdlibs/time instead pn.DefineNative("GetTimestamp", gno.Flds( // params ), @@ -345,12 +419,42 @@ func InjectPackage(store gno.Store, pn *gno.PackageNode) { } } +func typedInt32(i32 int32) gno.TypedValue { + tv := gno.TypedValue{T: gno.Int32Type} + tv.SetInt32(i32) + return tv +} + func typedInt64(i64 int64) gno.TypedValue { tv := gno.TypedValue{T: gno.Int64Type} tv.SetInt64(i64) return tv } +func typedUint32(u32 uint32) gno.TypedValue { + tv := gno.TypedValue{T: gno.Uint32Type} + tv.SetUint32(u32) + return tv +} + +func typedUint64(u64 uint64) gno.TypedValue { + tv := gno.TypedValue{T: gno.Uint64Type} + tv.SetUint64(u64) + return tv +} + +func typedFloat32(f32 float32) gno.TypedValue { + tv := gno.TypedValue{T: gno.Float32Type} + tv.SetFloat32(f32) + return tv +} + +func typedFloat64(f64 float64) gno.TypedValue { + tv := gno.TypedValue{T: gno.Float64Type} + tv.SetFloat64(f64) + return tv +} + func typedString(s gno.StringValue) gno.TypedValue { tv := gno.TypedValue{T: gno.StringType} tv.SetString(s) diff --git a/stdlibs/time/format.gno b/stdlibs/time/format.gno new file mode 100644 index 00000000000..8431ff89b45 --- /dev/null +++ b/stdlibs/time/format.gno @@ -0,0 +1,1619 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +import "errors" + +// These are predefined layouts for use in Time.Format and time.Parse. +// The reference time used in these layouts is the specific time stamp: +// +// 01/02 03:04:05PM '06 -0700 +// +// (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). +// That value is recorded as the constant named Layout, listed below. As a Unix +// time, this is 1136239445. Since MST is GMT-0700, the reference would be +// printed by the Unix date command as: +// +// Mon Jan 2 15:04:05 MST 2006 +// +// It is a regrettable historic error that the date uses the American convention +// of putting the numerical month before the day. +// +// The example for Time.Format demonstrates the working of the layout string +// in detail and is a good reference. +// +// Note that the RFC822, RFC850, and RFC1123 formats should be applied +// only to local times. Applying them to UTC times will use "UTC" as the +// time zone abbreviation, while strictly speaking those RFCs require the +// use of "GMT" in that case. +// In general RFC1123Z should be used instead of RFC1123 for servers +// that insist on that format, and RFC3339 should be preferred for new protocols. +// RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; +// when used with time.Parse they do not accept all the time formats +// permitted by the RFCs and they do accept time formats not formally defined. +// The RFC3339Nano format removes trailing zeros from the seconds field +// and thus may not sort correctly once formatted. +// +// Most programs can use one of the defined constants as the layout passed to +// Format or Parse. The rest of this comment can be ignored unless you are +// creating a custom layout string. +// +// To define your own format, write down what the reference time would look like +// formatted your way; see the values of constants like ANSIC, StampMicro or +// Kitchen for examples. The model is to demonstrate what the reference time +// looks like so that the Format and Parse methods can apply the same +// transformation to a general time value. +// +// Here is a summary of the components of a layout string. Each element shows by +// example the formatting of an element of the reference time. Only these values +// are recognized. Text in the layout string that is not recognized as part of +// the reference time is echoed verbatim during Format and expected to appear +// verbatim in the input to Parse. +// +// Year: "2006" "06" +// Month: "Jan" "January" "01" "1" +// Day of the week: "Mon" "Monday" +// Day of the month: "2" "_2" "02" +// Day of the year: "__2" "002" +// Hour: "15" "3" "03" (PM or AM) +// Minute: "4" "04" +// Second: "5" "05" +// AM/PM mark: "PM" +// +// Numeric time zone offsets format as follows: +// +// "-0700" ±hhmm +// "-07:00" ±hh:mm +// "-07" ±hh +// "-070000" ±hhmmss +// "-07:00:00" ±hh:mm:ss +// +// Replacing the sign in the format with a Z triggers +// the ISO 8601 behavior of printing Z instead of an +// offset for the UTC zone. Thus: +// +// "Z0700" Z or ±hhmm +// "Z07:00" Z or ±hh:mm +// "Z07" Z or ±hh +// "Z070000" Z or ±hhmmss +// "Z07:00:00" Z or ±hh:mm:ss +// +// Within the format string, the underscores in "_2" and "__2" represent spaces +// that may be replaced by digits if the following number has multiple digits, +// for compatibility with fixed-width Unix time formats. A leading zero represents +// a zero-padded value. +// +// The formats __2 and 002 are space-padded and zero-padded +// three-character day of year; there is no unpadded day of year format. +// +// A comma or decimal point followed by one or more zeros represents +// a fractional second, printed to the given number of decimal places. +// A comma or decimal point followed by one or more nines represents +// a fractional second, printed to the given number of decimal places, with +// trailing zeros removed. +// For example "15:04:05,000" or "15:04:05.000" formats or parses with +// millisecond precision. +// +// Some valid layouts are invalid time values for time.Parse, due to formats +// such as _ for space padding and Z for zone information. +const ( + Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order. + ANSIC = "Mon Jan _2 15:04:05 2006" + UnixDate = "Mon Jan _2 15:04:05 MST 2006" + RubyDate = "Mon Jan 02 15:04:05 -0700 2006" + RFC822 = "02 Jan 06 15:04 MST" + RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone + RFC850 = "Monday, 02-Jan-06 15:04:05 MST" + RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" + RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone + RFC3339 = "2006-01-02T15:04:05Z07:00" + RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" + Kitchen = "3:04PM" + // Handy time stamps. + Stamp = "Jan _2 15:04:05" + StampMilli = "Jan _2 15:04:05.000" + StampMicro = "Jan _2 15:04:05.000000" + StampNano = "Jan _2 15:04:05.000000000" +) + +const ( + _ = iota + stdLongMonth = iota + stdNeedDate // "January" + stdMonth // "Jan" + stdNumMonth // "1" + stdZeroMonth // "01" + stdLongWeekDay // "Monday" + stdWeekDay // "Mon" + stdDay // "2" + stdUnderDay // "_2" + stdZeroDay // "02" + stdUnderYearDay // "__2" + stdZeroYearDay // "002" + stdHour = iota + stdNeedClock // "15" + stdHour12 // "3" + stdZeroHour12 // "03" + stdMinute // "4" + stdZeroMinute // "04" + stdSecond // "5" + stdZeroSecond // "05" + stdLongYear = iota + stdNeedDate // "2006" + stdYear // "06" + stdPM = iota + stdNeedClock // "PM" + stdpm // "pm" + stdTZ = iota // "MST" + stdISO8601TZ // "Z0700" // prints Z for UTC + stdISO8601SecondsTZ // "Z070000" + stdISO8601ShortTZ // "Z07" + stdISO8601ColonTZ // "Z07:00" // prints Z for UTC + stdISO8601ColonSecondsTZ // "Z07:00:00" + stdNumTZ // "-0700" // always numeric + stdNumSecondsTz // "-070000" + stdNumShortTZ // "-07" // always numeric + stdNumColonTZ // "-07:00" // always numeric + stdNumColonSecondsTZ // "-07:00:00" + stdFracSecond0 // ".0", ".00", ... , trailing zeros included + stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted + + stdNeedDate = 1 << 8 // need month, day, year + stdNeedClock = 2 << 8 // need hour, minute, second + stdArgShift = 16 // extra argument in high bits, above low stdArgShift + stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators + stdMask = 1<= i+3 && layout[i:i+3] == "Jan" { + if len(layout) >= i+7 && layout[i:i+7] == "January" { + return layout[0:i], stdLongMonth, layout[i+7:] + } + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdMonth, layout[i+3:] + } + } + + case 'M': // Monday, Mon, MST + if len(layout) >= i+3 { + if layout[i:i+3] == "Mon" { + if len(layout) >= i+6 && layout[i:i+6] == "Monday" { + return layout[0:i], stdLongWeekDay, layout[i+6:] + } + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdWeekDay, layout[i+3:] + } + } + if layout[i:i+3] == "MST" { + return layout[0:i], stdTZ, layout[i+3:] + } + } + + case '0': // 01, 02, 03, 04, 05, 06, 002 + if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' { + return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:] + } + if len(layout) >= i+3 && layout[i+1] == '0' && layout[i+2] == '2' { + return layout[0:i], stdZeroYearDay, layout[i+3:] + } + + case '1': // 15, 1 + if len(layout) >= i+2 && layout[i+1] == '5' { + return layout[0:i], stdHour, layout[i+2:] + } + return layout[0:i], stdNumMonth, layout[i+1:] + + case '2': // 2006, 2 + if len(layout) >= i+4 && layout[i:i+4] == "2006" { + return layout[0:i], stdLongYear, layout[i+4:] + } + return layout[0:i], stdDay, layout[i+1:] + + case '_': // _2, _2006, __2 + if len(layout) >= i+2 && layout[i+1] == '2' { + //_2006 is really a literal _, followed by stdLongYear + if len(layout) >= i+5 && layout[i+1:i+5] == "2006" { + return layout[0 : i+1], stdLongYear, layout[i+5:] + } + return layout[0:i], stdUnderDay, layout[i+2:] + } + if len(layout) >= i+3 && layout[i+1] == '_' && layout[i+2] == '2' { + return layout[0:i], stdUnderYearDay, layout[i+3:] + } + + case '3': + return layout[0:i], stdHour12, layout[i+1:] + + case '4': + return layout[0:i], stdMinute, layout[i+1:] + + case '5': + return layout[0:i], stdSecond, layout[i+1:] + + case 'P': // PM + if len(layout) >= i+2 && layout[i+1] == 'M' { + return layout[0:i], stdPM, layout[i+2:] + } + + case 'p': // pm + if len(layout) >= i+2 && layout[i+1] == 'm' { + return layout[0:i], stdpm, layout[i+2:] + } + + case '-': // -070000, -07:00:00, -0700, -07:00, -07 + if len(layout) >= i+7 && layout[i:i+7] == "-070000" { + return layout[0:i], stdNumSecondsTz, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" { + return layout[0:i], stdNumColonSecondsTZ, layout[i+9:] + } + if len(layout) >= i+5 && layout[i:i+5] == "-0700" { + return layout[0:i], stdNumTZ, layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == "-07:00" { + return layout[0:i], stdNumColonTZ, layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == "-07" { + return layout[0:i], stdNumShortTZ, layout[i+3:] + } + + case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00, + if len(layout) >= i+7 && layout[i:i+7] == "Z070000" { + return layout[0:i], stdISO8601SecondsTZ, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" { + return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:] + } + if len(layout) >= i+5 && layout[i:i+5] == "Z0700" { + return layout[0:i], stdISO8601TZ, layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" { + return layout[0:i], stdISO8601ColonTZ, layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == "Z07" { + return layout[0:i], stdISO8601ShortTZ, layout[i+3:] + } + + case '.', ',': // ,000, or .000, or ,999, or .999 - repeated digits for fractional seconds. + if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') { + ch := layout[i+1] + j := i + 1 + for j < len(layout) && layout[j] == ch { + j++ + } + // String of digits must end here - only fractional second is all digits. + if !isDigit(layout, j) { + code := stdFracSecond0 + if layout[i+1] == '9' { + code = stdFracSecond9 + } + std := stdFracSecond(code, j-(i+1), c) + return layout[0:i], std, layout[j:] + } + } + } + } + return layout, 0, "" +} + +var longDayNames = []string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +} + +var shortDayNames = []string{ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", +} + +var shortMonthNames = []string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +} + +var longMonthNames = []string{ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +} + +// match reports whether s1 and s2 match ignoring case. +// It is assumed s1 and s2 are the same length. +func match(s1, s2 string) bool { + for i := 0; i < len(s1); i++ { + c1 := s1[i] + c2 := s2[i] + if c1 != c2 { + // Switch to lower-case; 'a'-'A' is known to be a single bit. + c1 |= 'a' - 'A' + c2 |= 'a' - 'A' + if c1 != c2 || c1 < 'a' || c1 > 'z' { + return false + } + } + } + return true +} + +func lookup(tab []string, val string) (int, string, error) { + for i, v := range tab { + if len(val) >= len(v) && match(val[0:len(v)], v) { + return i, val[len(v):], nil + } + } + return -1, val, errBad +} + +// appendInt appends the decimal form of x to b and returns the result. +// If the decimal form (excluding sign) is shorter than width, the result is padded with leading 0's. +// Duplicates functionality in strconv, but avoids dependency. +func appendInt(b []byte, x int, width int) []byte { + u := uint(x) + if x < 0 { + b = append(b, '-') + u = uint(-x) + } + + // Assemble decimal in reverse order. + var buf [20]byte + i := len(buf) + for u >= 10 { + i-- + q := u / 10 + buf[i] = byte('0' + u - q*10) + u = q + } + i-- + buf[i] = byte('0' + u) + + // Add 0-padding. + for w := len(buf) - i; w < width; w++ { + b = append(b, '0') + } + + return append(b, buf[i:]...) +} + +// Never printed, just needs to be non-nil for return by atoi. +var atoiError = errors.New("time: invalid number") + +// Duplicates functionality in strconv, but avoids dependency. +func atoi(s string) (x int, err error) { + neg := false + if s != "" && (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-' + s = s[1:] + } + q, rem, err := leadingInt(s) + x = int(q) + if err != nil || rem != "" { + return 0, atoiError + } + if neg { + x = -x + } + return x, nil +} + +// The "std" value passed to formatNano contains two packed fields: the number of +// digits after the decimal and the separator character (period or comma). +// These functions pack and unpack that variable. +func stdFracSecond(code, n, c int) int { + // Use 0xfff to make the failure case even more absurd. + if c == '.' { + return code | ((n & 0xfff) << stdArgShift) + } + return code | ((n & 0xfff) << stdArgShift) | 1<> stdArgShift) & 0xfff +} + +func separator(std int) byte { + if (std >> stdSeparatorShift) == 0 { + return '.' + } + return ',' +} + +// formatNano appends a fractional second, as nanoseconds, to b +// and returns the result. +func formatNano(b []byte, nanosec uint, std int) []byte { + var ( + n = digitsLen(std) + separator = separator(std) + trim = std&stdMask == stdFracSecond9 + ) + u := nanosec + var buf [9]byte + for start := len(buf); start > 0; { + start-- + buf[start] = byte(u%10 + '0') + u /= 10 + } + + if n > 9 { + n = 9 + } + if trim { + for n > 0 && buf[n-1] == '0' { + n-- + } + if n == 0 { + return b + } + } + b = append(b, separator) + return append(b, buf[:n]...) +} + +// String returns the time formatted using the format string +// +// "2006-01-02 15:04:05.999999999 -0700 MST" +// +// If the time has a monotonic clock reading, the returned string +// includes a final field "m=±", where value is the monotonic +// clock reading formatted as a decimal number of seconds. +// +// The returned string is meant for debugging; for a stable serialized +// representation, use t.MarshalText, t.MarshalBinary, or t.Format +// with an explicit format string. +func (t Time) String() string { + s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST") + + // Format monotonic clock reading as m=±ddd.nnnnnnnnn. + if t.wall&hasMonotonic != 0 { + m2 := uint64(t.ext) + sign := byte('+') + if t.ext < 0 { + sign = '-' + m2 = -m2 + } + m1, m2 := m2/1e9, m2%1e9 + m0, m1 := m1/1e9, m1%1e9 + buf := make([]byte, 0, 24) + buf = append(buf, " m="...) + buf = append(buf, sign) + wid := 0 + if m0 != 0 { + buf = appendInt(buf, int(m0), 0) + wid = 9 + } + buf = appendInt(buf, int(m1), wid) + buf = append(buf, '.') + buf = appendInt(buf, int(m2), 9) + s += string(buf) + } + return s +} + +// GoString implements fmt.GoStringer and formats t to be printed in Go source +// code. +func (t Time) GoString() string { + buf := make([]byte, 0, 70) + buf = append(buf, "time.Date("...) + buf = appendInt(buf, t.Year(), 0) + month := t.Month() + if January <= month && month <= December { + buf = append(buf, ", time."...) + buf = append(buf, t.Month().String()...) + } else { + // It's difficult to construct a time.Time with a date outside the + // standard range but we might as well try to handle the case. + buf = appendInt(buf, int(month), 0) + } + buf = append(buf, ", "...) + buf = appendInt(buf, t.Day(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Hour(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Minute(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Second(), 0) + buf = append(buf, ", "...) + buf = appendInt(buf, t.Nanosecond(), 0) + buf = append(buf, ", "...) + switch loc := t.Location(); loc { + case UTC, nil: + buf = append(buf, "time.UTC"...) + case Local: + buf = append(buf, "time.Local"...) + default: + // there are several options for how we could display this, none of + // which are great: + // + // - use Location(loc.name), which is not technically valid syntax + // - use LoadLocation(loc.name), which will cause a syntax error when + // embedded and also would require us to escape the string without + // importing fmt or strconv + // - try to use FixedZone, which would also require escaping the name + // and would represent e.g. "America/Los_Angeles" daylight saving time + // shifts inaccurately + // - use the pointer format, which is no worse than you'd get with the + // old fmt.Sprintf("%#v", t) format. + // + // Of these, Location(loc.name) is the least disruptive. This is an edge + // case we hope not to hit too often. + buf = append(buf, `time.Location(`...) + buf = append(buf, []byte(quote(loc.name))...) + buf = append(buf, `)`...) + } + buf = append(buf, ')') + return string(buf) +} + +// Format returns a textual representation of the time value formatted according +// to the layout defined by the argument. See the documentation for the +// constant called Layout to see how to represent the layout format. +// +// The executable example for Time.Format demonstrates the working +// of the layout string in detail and is a good reference. +func (t Time) Format(layout string) string { + const bufSize = 64 + var b []byte + max := len(layout) + 10 + if max < bufSize { + var buf [bufSize]byte + b = buf[:0] + } else { + b = make([]byte, 0, max) + } + b = t.AppendFormat(b, layout) + return string(b) +} + +// AppendFormat is like Format but appends the textual +// representation to b and returns the extended buffer. +func (t Time) AppendFormat(b []byte, layout string) []byte { + var ( + name, offset, abs = t.locabs() + + year int = -1 + month Month + day int + yday int + hour int = -1 + min int + sec int + ) + // Each iteration generates one std value. + for layout != "" { + prefix, std, suffix := nextStdChunk(layout) + if prefix != "" { + b = append(b, prefix...) + } + if std == 0 { + break + } + layout = suffix + + // Compute year, month, day if needed. + if year < 0 && std&stdNeedDate != 0 { + year, month, day, yday = absDate(abs, true) + yday++ + } + + // Compute hour, minute, second if needed. + if hour < 0 && std&stdNeedClock != 0 { + hour, min, sec = absClock(abs) + } + + switch std & stdMask { + case stdYear: + y := year + if y < 0 { + y = -y + } + b = appendInt(b, y%100, 2) + case stdLongYear: + b = appendInt(b, year, 4) + case stdMonth: + b = append(b, month.String()[:3]...) + case stdLongMonth: + m := month.String() + b = append(b, m...) + case stdNumMonth: + b = appendInt(b, int(month), 0) + case stdZeroMonth: + b = appendInt(b, int(month), 2) + case stdWeekDay: + b = append(b, absWeekday(abs).String()[:3]...) + case stdLongWeekDay: + s := absWeekday(abs).String() + b = append(b, s...) + case stdDay: + b = appendInt(b, day, 0) + case stdUnderDay: + if day < 10 { + b = append(b, ' ') + } + b = appendInt(b, day, 0) + case stdZeroDay: + b = appendInt(b, day, 2) + case stdUnderYearDay: + if yday < 100 { + b = append(b, ' ') + if yday < 10 { + b = append(b, ' ') + } + } + b = appendInt(b, yday, 0) + case stdZeroYearDay: + b = appendInt(b, yday, 3) + case stdHour: + b = appendInt(b, hour, 2) + case stdHour12: + // Noon is 12PM, midnight is 12AM. + hr := hour % 12 + if hr == 0 { + hr = 12 + } + b = appendInt(b, hr, 0) + case stdZeroHour12: + // Noon is 12PM, midnight is 12AM. + hr := hour % 12 + if hr == 0 { + hr = 12 + } + b = appendInt(b, hr, 2) + case stdMinute: + b = appendInt(b, min, 0) + case stdZeroMinute: + b = appendInt(b, min, 2) + case stdSecond: + b = appendInt(b, sec, 0) + case stdZeroSecond: + b = appendInt(b, sec, 2) + case stdPM: + if hour >= 12 { + b = append(b, "PM"...) + } else { + b = append(b, "AM"...) + } + case stdpm: + if hour >= 12 { + b = append(b, "pm"...) + } else { + b = append(b, "am"...) + } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumShortTZ, stdNumColonSecondsTZ: + // Ugly special case. We cheat and take the "Z" variants + // to mean "the time zone as formatted for ISO 8601". + if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ) { + b = append(b, 'Z') + break + } + zone := offset / 60 // convert to minutes + absoffset := offset + if zone < 0 { + b = append(b, '-') + zone = -zone + absoffset = -absoffset + } else { + b = append(b, '+') + } + b = appendInt(b, zone/60, 2) + if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + b = append(b, ':') + } + if std != stdNumShortTZ && std != stdISO8601ShortTZ { + b = appendInt(b, zone%60, 2) + } + + // append seconds if appropriate + if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + b = append(b, ':') + } + b = appendInt(b, absoffset%60, 2) + } + + case stdTZ: + if name != "" { + b = append(b, name...) + break + } + // No time zone known for this time, but we must print one. + // Use the -0700 format. + zone := offset / 60 // convert to minutes + if zone < 0 { + b = append(b, '-') + zone = -zone + } else { + b = append(b, '+') + } + b = appendInt(b, zone/60, 2) + b = appendInt(b, zone%60, 2) + case stdFracSecond0, stdFracSecond9: + b = formatNano(b, uint(t.Nanosecond()), std) + } + } + return b +} + +var errBad = errors.New("bad value for field") // placeholder not passed to user + +// ParseError describes a problem parsing a time string. +type ParseError struct { + Layout string + Value string + LayoutElem string + ValueElem string + Message string +} + +// These are borrowed from unicode/utf8 and strconv and replicate behavior in +// that package, since we can't take a dependency on either. +const ( + lowerhex = "0123456789abcdef" + runeSelf = 0x80 + runeError = '\uFFFD' +) + +func quote(s string) string { + buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes + buf[0] = '"' + for i, c := range s { + if c >= runeSelf || c < ' ' { + // This means you are asking us to parse a time.Duration or + // time.Location with unprintable or non-ASCII characters in it. + // We don't expect to hit this case very often. We could try to + // reproduce strconv.Quote's behavior with full fidelity but + // given how rarely we expect to hit these edge cases, speed and + // conciseness are better. + var width int + if c == runeError { + width = 1 + if i+2 < len(s) && s[i:i+3] == string(runeError) { + width = 3 + } + } else { + width = len(string(c)) + } + for j := 0; j < width; j++ { + buf = append(buf, `\x`...) + buf = append(buf, lowerhex[s[i+j]>>4]) + buf = append(buf, lowerhex[s[i+j]&0xF]) + } + } else { + if c == '"' || c == '\\' { + buf = append(buf, '\\') + } + buf = append(buf, string(c)...) + } + } + buf = append(buf, '"') + return string(buf) +} + +// Error returns the string representation of a ParseError. +func (e *ParseError) Error() string { + if e.Message == "" { + return "parsing time " + + quote(e.Value) + " as " + + quote(e.Layout) + ": cannot parse " + + quote(e.ValueElem) + " as " + + quote(e.LayoutElem) + } + return "parsing time " + + quote(e.Value) + e.Message +} + +// isDigit reports whether s[i] is in range and is a decimal digit. +func isDigit(s string, i int) bool { + if len(s) <= i { + return false + } + c := s[i] + return '0' <= c && c <= '9' +} + +// getnum parses s[0:1] or s[0:2] (fixed forces s[0:2]) +// as a decimal integer and returns the integer and the +// remainder of the string. +func getnum(s string, fixed bool) (int, string, error) { + if !isDigit(s, 0) { + return 0, s, errBad + } + if !isDigit(s, 1) { + if fixed { + return 0, s, errBad + } + return int(s[0] - '0'), s[1:], nil + } + return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil +} + +// getnum3 parses s[0:1], s[0:2], or s[0:3] (fixed forces s[0:3]) +// as a decimal integer and returns the integer and the remainder +// of the string. +func getnum3(s string, fixed bool) (int, string, error) { + var n, i int + for i = 0; i < 3 && isDigit(s, i); i++ { + n = n*10 + int(s[i]-'0') + } + if i == 0 || fixed && i != 3 { + return 0, s, errBad + } + return n, s[i:], nil +} + +func cutspace(s string) string { + for len(s) > 0 && s[0] == ' ' { + s = s[1:] + } + return s +} + +// skip removes the given prefix from value, +// treating runs of space characters as equivalent. +func skip(value, prefix string) (string, error) { + for len(prefix) > 0 { + if prefix[0] == ' ' { + if len(value) > 0 && value[0] != ' ' { + return value, errBad + } + prefix = cutspace(prefix) + value = cutspace(value) + continue + } + if len(value) == 0 || value[0] != prefix[0] { + return value, errBad + } + prefix = prefix[1:] + value = value[1:] + } + return value, nil +} + +// Parse parses a formatted string and returns the time value it represents. +// See the documentation for the constant called Layout to see how to +// represent the format. The second argument must be parseable using +// the format string (layout) provided as the first argument. +// +// The example for Time.Format demonstrates the working of the layout string +// in detail and is a good reference. +// +// When parsing (only), the input may contain a fractional second +// field immediately after the seconds field, even if the layout does not +// signify its presence. In that case either a comma or a decimal point +// followed by a maximal series of digits is parsed as a fractional second. +// Fractional seconds are truncated to nanosecond precision. +// +// Elements omitted from the layout are assumed to be zero or, when +// zero is impossible, one, so parsing "3:04pm" returns the time +// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is +// 0, this time is before the zero Time). +// Years must be in the range 0000..9999. The day of the week is checked +// for syntax but it is otherwise ignored. +// +// For layouts specifying the two-digit year 06, a value NN >= 69 will be treated +// as 19NN and a value NN < 69 will be treated as 20NN. +// +// The remainder of this comment describes the handling of time zones. +// +// In the absence of a time zone indicator, Parse returns a time in UTC. +// +// When parsing a time with a zone offset like -0700, if the offset corresponds +// to a time zone used by the current location (Local), then Parse uses that +// location and zone in the returned time. Otherwise it records the time as +// being in a fabricated location with time fixed at the given zone offset. +// +// When parsing a time with a zone abbreviation like MST, if the zone abbreviation +// has a defined offset in the current location, then that offset is used. +// The zone abbreviation "UTC" is recognized as UTC regardless of location. +// If the zone abbreviation is unknown, Parse records the time as being +// in a fabricated location with the given zone abbreviation and a zero offset. +// This choice means that such a time can be parsed and reformatted with the +// same layout losslessly, but the exact instant used in the representation will +// differ by the actual zone offset. To avoid such problems, prefer time layouts +// that use a numeric zone offset, or use ParseInLocation. +func Parse(layout, value string) (Time, error) { + return parse(layout, value, UTC, Local) +} + +// ParseInLocation is like Parse but differs in two important ways. +// First, in the absence of time zone information, Parse interprets a time as UTC; +// ParseInLocation interprets the time as in the given location. +// Second, when given a zone offset or abbreviation, Parse tries to match it +// against the Local location; ParseInLocation uses the given location. +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return parse(layout, value, loc, loc) +} + +func parse(layout, value string, defaultLocation, local *Location) (Time, error) { + alayout, avalue := layout, value + rangeErrString := "" // set if a value is out of range + amSet := false // do we need to subtract 12 from the hour for midnight? + pmSet := false // do we need to add 12 to the hour? + + // Time being constructed. + var ( + year int + month int = -1 + day int = -1 + yday int = -1 + hour int + min int + sec int + nsec int + z *Location + zoneOffset int = -1 + zoneName string + ) + + // Each iteration processes one std value. + for { + var err error + prefix, std, suffix := nextStdChunk(layout) + stdstr := layout[len(prefix) : len(layout)-len(suffix)] + value, err = skip(value, prefix) + if err != nil { + return Time{}, &ParseError{alayout, avalue, prefix, value, ""} + } + if std == 0 { + if len(value) != 0 { + return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + quote(value)} + } + break + } + layout = suffix + var p string + switch std & stdMask { + case stdYear: + if len(value) < 2 { + err = errBad + break + } + hold := value + p, value = value[0:2], value[2:] + year, err = atoi(p) + if err != nil { + value = hold + } else if year >= 69 { // Unix time starts Dec 31 1969 in some time zones + year += 1900 + } else { + year += 2000 + } + case stdLongYear: + if len(value) < 4 || !isDigit(value, 0) { + err = errBad + break + } + p, value = value[0:4], value[4:] + year, err = atoi(p) + case stdMonth: + month, value, err = lookup(shortMonthNames, value) + month++ + case stdLongMonth: + month, value, err = lookup(longMonthNames, value) + month++ + case stdNumMonth, stdZeroMonth: + month, value, err = getnum(value, std == stdZeroMonth) + if err == nil && (month <= 0 || 12 < month) { + rangeErrString = "month" + } + case stdWeekDay: + // Ignore weekday except for error checking. + _, value, err = lookup(shortDayNames, value) + case stdLongWeekDay: + _, value, err = lookup(longDayNames, value) + case stdDay, stdUnderDay, stdZeroDay: + if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + day, value, err = getnum(value, std == stdZeroDay) + // Note that we allow any one- or two-digit day here. + // The month, day, year combination is validated after we've completed parsing. + case stdUnderYearDay, stdZeroYearDay: + for i := 0; i < 2; i++ { + if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + } + yday, value, err = getnum3(value, std == stdZeroYearDay) + // Note that we allow any one-, two-, or three-digit year-day here. + // The year-day, year combination is validated after we've completed parsing. + case stdHour: + hour, value, err = getnum(value, false) + if hour < 0 || 24 <= hour { + rangeErrString = "hour" + } + case stdHour12, stdZeroHour12: + hour, value, err = getnum(value, std == stdZeroHour12) + if hour < 0 || 12 < hour { + rangeErrString = "hour" + } + case stdMinute, stdZeroMinute: + min, value, err = getnum(value, std == stdZeroMinute) + if min < 0 || 60 <= min { + rangeErrString = "minute" + } + case stdSecond, stdZeroSecond: + sec, value, err = getnum(value, std == stdZeroSecond) + if sec < 0 || 60 <= sec { + rangeErrString = "second" + break + } + // Special case: do we have a fractional second but no + // fractional second in the format? + if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) { + _, std, _ = nextStdChunk(layout) + std &= stdMask + if std == stdFracSecond0 || std == stdFracSecond9 { + // Fractional second in the layout; proceed normally + break + } + // No fractional second in the layout but we have one in the input. + n := 2 + for ; n < len(value) && isDigit(value, n); n++ { + } + nsec, rangeErrString, err = parseNanoseconds(value, n) + value = value[n:] + } + case stdPM: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "PM": + pmSet = true + case "AM": + amSet = true + default: + err = errBad + } + case stdpm: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "pm": + pmSet = true + case "am": + amSet = true + default: + err = errBad + } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: + if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { + value = value[1:] + z = UTC + break + } + var sign, hour, min, seconds string + if std == stdISO8601ColonTZ || std == stdNumColonTZ { + if len(value) < 6 { + err = errBad + break + } + if value[3] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] + } else if std == stdNumShortTZ || std == stdISO8601ShortTZ { + if len(value) < 3 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] + } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + if len(value) < 9 { + err = errBad + break + } + if value[3] != ':' || value[6] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] + } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz { + if len(value) < 7 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] + } else { + if len(value) < 5 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] + } + var hr, mm, ss int + hr, err = atoi(hour) + if err == nil { + mm, err = atoi(min) + } + if err == nil { + ss, err = atoi(seconds) + } + zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds + switch sign[0] { + case '+': + case '-': + zoneOffset = -zoneOffset + default: + err = errBad + } + case stdTZ: + // Does it look like a time zone? + if len(value) >= 3 && value[0:3] == "UTC" { + z = UTC + value = value[3:] + break + } + n, ok := parseTimeZone(value) + if !ok { + err = errBad + break + } + zoneName, value = value[:n], value[n:] + + case stdFracSecond0: + // stdFracSecond0 requires the exact number of digits as specified in + // the layout. + ndigit := 1 + digitsLen(std) + if len(value) < ndigit { + err = errBad + break + } + nsec, rangeErrString, err = parseNanoseconds(value, ndigit) + value = value[ndigit:] + + case stdFracSecond9: + if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] { + // Fractional second omitted. + break + } + // Take any number of digits, even more than asked for, + // because it is what the stdSecond case would do. + i := 0 + for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { + i++ + } + nsec, rangeErrString, err = parseNanoseconds(value, 1+i) + value = value[1+i:] + } + if rangeErrString != "" { + return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} + } + if err != nil { + return Time{}, &ParseError{alayout, avalue, stdstr, value, ""} + } + } + if pmSet && hour < 12 { + hour += 12 + } else if amSet && hour == 12 { + hour = 0 + } + + // Convert yday to day, month. + if yday >= 0 { + var d int + var m int + if isLeap(year) { + if yday == 31+29 { + m = int(February) + d = 29 + } else if yday > 31+29 { + yday-- + } + } + if yday < 1 || yday > 365 { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year out of range"} + } + if m == 0 { + m = (yday-1)/31 + 1 + if int(daysBefore[m]) < yday { + m++ + } + d = yday - int(daysBefore[m-1]) + } + // If month, day already seen, yday's m, d must match. + // Otherwise, set them from m, d. + if month >= 0 && month != m { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match month"} + } + month = m + if day >= 0 && day != d { + return Time{}, &ParseError{alayout, avalue, "", value, ": day-of-year does not match day"} + } + day = d + } else { + if month < 0 { + month = int(January) + } + if day < 0 { + day = 1 + } + } + + // Validate the day of the month. + if day < 1 || day > daysIn(Month(month), year) { + return Time{}, &ParseError{alayout, avalue, "", value, ": day out of range"} + } + + if z != nil { + return Date(year, Month(month), day, hour, min, sec, nsec, z), nil + } + + if zoneOffset != -1 { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + t.addSec(-int64(zoneOffset)) + + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + name, offset, _, _, _ := local.lookup(t.unixSec()) + if offset == zoneOffset && (zoneName == "" || name == zoneName) { + t.setLoc(local) + return t, nil + } + + // Otherwise create fake zone to record offset. + t.setLoc(FixedZone(zoneName, zoneOffset)) + return t, nil + } + + if zoneName != "" { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + // Look for local zone with the given offset. + // If that zone was in effect at the given time, use it. + offset, ok := local.lookupName(zoneName, t.unixSec()) + if ok { + t.addSec(-int64(offset)) + t.setLoc(local) + return t, nil + } + + // Otherwise, create fake zone with unknown offset. + if len(zoneName) > 3 && zoneName[:3] == "GMT" { + offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. + offset *= 3600 + } + t.setLoc(FixedZone(zoneName, offset)) + return t, nil + } + + // Otherwise, fall back to default. + return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil +} + +// parseTimeZone parses a time zone string and returns its length. Time zones +// are human-generated and unpredictable. We can't do precise error checking. +// On the other hand, for a correct parse there must be a time zone at the +// beginning of the string, so it's almost always true that there's one +// there. We look at the beginning of the string for a run of upper-case letters. +// If there are more than 5, it's an error. +// If there are 4 or 5 and the last is a T, it's a time zone. +// If there are 3, it's a time zone. +// Otherwise, other than special cases, it's not a time zone. +// GMT is special because it can have an hour offset. +func parseTimeZone(value string) (length int, ok bool) { + if len(value) < 3 { + return 0, false + } + // Special case 1: ChST and MeST are the only zones with a lower-case letter. + if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") { + return 4, true + } + // Special case 2: GMT may have an hour offset; treat it specially. + if value[:3] == "GMT" { + length = parseGMT(value) + return length, true + } + // Special Case 3: Some time zones are not named, but have +/-00 format + if value[0] == '+' || value[0] == '-' { + length = parseSignedOffset(value) + ok := length > 0 // parseSignedOffset returns 0 in case of bad input + return length, ok + } + // How many upper-case letters are there? Need at least three, at most five. + var nUpper int + for nUpper = 0; nUpper < 6; nUpper++ { + if nUpper >= len(value) { + break + } + if c := value[nUpper]; c < 'A' || 'Z' < c { + break + } + } + switch nUpper { + case 0, 1, 2, 6: + return 0, false + case 5: // Must end in T to match. + if value[4] == 'T' { + return 5, true + } + case 4: + // Must end in T, except one special case. + if value[3] == 'T' || value[:4] == "WITA" { + return 4, true + } + case 3: + return 3, true + } + return 0, false +} + +// parseGMT parses a GMT time zone. The input string is known to start "GMT". +// The function checks whether that is followed by a sign and a number in the +// range -23 through +23 excluding zero. +func parseGMT(value string) int { + value = value[3:] + if len(value) == 0 { + return 3 + } + + return 3 + parseSignedOffset(value) +} + +// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04"). +// The function checks for a signed number in the range -23 through +23 excluding zero. +// Returns length of the found offset string or 0 otherwise +func parseSignedOffset(value string) int { + sign := value[0] + if sign != '-' && sign != '+' { + return 0 + } + x, rem, err := leadingInt(value[1:]) + + // fail if nothing consumed by leadingInt + if err != nil || value[1:] == rem { + return 0 + } + if x > 23 { + return 0 + } + return len(value) - len(rem) +} + +func commaOrPeriod(b byte) bool { + return b == '.' || b == ',' +} + +func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { + if !commaOrPeriod(value[0]) { + err = errBad + return + } + if nbytes > 10 { + value = value[:10] + nbytes = 10 + } + if ns, err = atoi(value[1:nbytes]); err != nil { + return + } + if ns < 0 { + rangeErrString = "fractional second" + return + } + // We need nanoseconds, which means scaling by the number + // of missing digits in the format, maximum length 10. + scaleDigits := 10 - nbytes + for i := 0; i < scaleDigits; i++ { + ns *= 10 + } + return +} + +var errLeadingInt = errors.New("time: bad [0-9]*") // never printed + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x uint64, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x > 1<<63/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + uint64(c) - '0' + if x > 1<<63 { + // overflow + return 0, "", errLeadingInt + } + } + return x, s[i:], nil +} + +// leadingFraction consumes the leading [0-9]* from s. +// It is used only for fractions, so does not return an error on overflow, +// it just stops accumulating precision. +func leadingFraction(s string) (x uint64, scale float64, rem string) { + i := 0 + scale = 1 + overflow := false + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if overflow { + continue + } + if x > (1<<63-1)/10 { + // It's possible for overflow to give a positive number, so take care. + overflow = true + continue + } + y := x*10 + uint64(c) - '0' + if y > 1<<63 { + overflow = true + continue + } + x = y + scale *= 10 + } + return x, scale, s[i:] +} + +var unitMap = map[string]uint64{ + "ns": uint64(Nanosecond), + "us": uint64(Microsecond), + "µs": uint64(Microsecond), // U+00B5 = micro symbol + "μs": uint64(Microsecond), // U+03BC = Greek letter mu + "ms": uint64(Millisecond), + "s": uint64(Second), + "m": uint64(Minute), + "h": uint64(Hour), +} + +// ParseDuration parses a duration string. +// A duration string is a possibly signed sequence of +// decimal numbers, each with optional fraction and a unit suffix, +// such as "300ms", "-1.5h" or "2h45m". +// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". +func ParseDuration(s string) (Duration, error) { + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + orig := s + var d uint64 + neg := false + + // Consume [-+]? + if s != "" { + c := s[0] + if c == '-' || c == '+' { + neg = c == '-' + s = s[1:] + } + } + // Special case: if all that is left is "0", this is zero. + if s == "0" { + return 0, nil + } + if s == "" { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + for s != "" { + var ( + v, f uint64 // integers before, after decimal point + scale float64 = 1 // value = v + f/scale + ) + + var err error + + // The next character must be [0-9.] + if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + // Consume [0-9]* + pl := len(s) + v, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + pre := pl != len(s) // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + post := false + if s != "" && s[0] == '.' { + s = s[1:] + pl := len(s) + f, scale, s = leadingFraction(s) + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errors.New("time: invalid duration " + quote(orig)) + } + + // Consume unit. + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c == '.' || '0' <= c && c <= '9' { + break + } + } + if i == 0 { + return 0, errors.New("time: missing unit in duration " + quote(orig)) + } + u := s[:i] + s = s[i:] + unit, ok := unitMap[u] + if !ok { + return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) + } + if v > 1<<63/unit { + // overflow + return 0, errors.New("time: invalid duration " + quote(orig)) + } + v *= unit + if f > 0 { + // float64 is needed to be nanosecond accurate for fractions of hours. + // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) + v += uint64(float64(f) * (float64(unit) / scale)) + if v > 1<<63 { + // overflow + return 0, errors.New("time: invalid duration " + quote(orig)) + } + } + d += v + if d > 1<<63 { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + } + if neg { + return -Duration(d), nil + } + if d > 1<<63-1 { + return 0, errors.New("time: invalid duration " + quote(orig)) + } + return Duration(d), nil +} diff --git a/stdlibs/time/time.gno b/stdlibs/time/time.gno new file mode 100644 index 00000000000..bc529c1c976 --- /dev/null +++ b/stdlibs/time/time.gno @@ -0,0 +1,1625 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package time provides functionality for measuring and displaying time. +// +// The calendrical calculations always assume a Gregorian calendar, with +// no leap seconds. +// +// # Monotonic Clocks +// +// Operating systems provide both a “wall clock,” which is subject to +// changes for clock synchronization, and a “monotonic clock,” which is +// not. The general rule is that the wall clock is for telling time and +// the monotonic clock is for measuring time. Rather than split the API, +// in this package the Time returned by time.Now contains both a wall +// clock reading and a monotonic clock reading; later time-telling +// operations use the wall clock reading, but later time-measuring +// operations, specifically comparisons and subtractions, use the +// monotonic clock reading. +// +// For example, this code always computes a positive elapsed time of +// approximately 20 milliseconds, even if the wall clock is changed during +// the operation being timed: +// +// start := time.Now() +// ... operation that takes 20 milliseconds ... +// t := time.Now() +// elapsed := t.Sub(start) +// +// Other idioms, such as time.Since(start), time.Until(deadline), and +// time.Now().Before(deadline), are similarly robust against wall clock +// resets. +// +// The rest of this section gives the precise details of how operations +// use monotonic clocks, but understanding those details is not required +// to use this package. +// +// The Time returned by time.Now contains a monotonic clock reading. +// If Time t has a monotonic clock reading, t.Add adds the same duration to +// both the wall clock and monotonic clock readings to compute the result. +// Because t.AddDate(y, m, d), t.Round(d), and t.Truncate(d) are wall time +// computations, they always strip any monotonic clock reading from their results. +// Because t.In, t.Local, and t.UTC are used for their effect on the interpretation +// of the wall time, they also strip any monotonic clock reading from their results. +// The canonical way to strip a monotonic clock reading is to use t = t.Round(0). +// +// If Times t and u both contain monotonic clock readings, the operations +// t.After(u), t.Before(u), t.Equal(u), and t.Sub(u) are carried out +// using the monotonic clock readings alone, ignoring the wall clock +// readings. If either t or u contains no monotonic clock reading, these +// operations fall back to using the wall clock readings. +// +// On some systems the monotonic clock will stop if the computer goes to sleep. +// On such a system, t.Sub(u) may not accurately reflect the actual +// time that passed between t and u. +// +// Because the monotonic clock reading has no meaning outside +// the current process, the serialized forms generated by t.GobEncode, +// t.MarshalBinary, t.MarshalJSON, and t.MarshalText omit the monotonic +// clock reading, and t.Format provides no format for it. Similarly, the +// constructors time.Date, time.Parse, time.ParseInLocation, and time.Unix, +// as well as the unmarshalers t.GobDecode, t.UnmarshalBinary. +// t.UnmarshalJSON, and t.UnmarshalText always create times with +// no monotonic clock reading. +// +// The monotonic clock reading exists only in Time values. It is not +// a part of Duration values or the Unix times returned by t.Unix and +// friends. +// +// Note that the Go == operator compares not just the time instant but +// also the Location and the monotonic clock reading. See the +// documentation for the Time type for a discussion of equality +// testing for Time values. +// +// For debugging, the result of t.String does include the monotonic +// clock reading if present. If t != u because of different monotonic clock readings, +// that difference will be visible when printing t.String() and u.String(). +package time + +import ( + "errors" + ios "internal/os" // XXX to access time. + // XXX _ "unsafe" // for go:linkname +) + +// A Time represents an instant in time with nanosecond precision. +// +// Programs using times should typically store and pass them as values, +// not pointers. That is, time variables and struct fields should be of +// type time.Time, not *time.Time. +// +// A Time value can be used by multiple goroutines simultaneously except +// that the methods GobDecode, UnmarshalBinary, UnmarshalJSON and +// UnmarshalText are not concurrency-safe. +// +// Time instants can be compared using the Before, After, and Equal methods. +// The Sub method subtracts two instants, producing a Duration. +// The Add method adds a Time and a Duration, producing a Time. +// +// The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. +// As this time is unlikely to come up in practice, the IsZero method gives +// a simple way of detecting a time that has not been initialized explicitly. +// +// Each Time has associated with it a Location, consulted when computing the +// presentation form of the time, such as in the Format, Hour, and Year methods. +// The methods Local, UTC, and In return a Time with a specific location. +// Changing the location in this way changes only the presentation; it does not +// change the instant in time being denoted and therefore does not affect the +// computations described in earlier paragraphs. +// +// Representations of a Time value saved by the GobEncode, MarshalBinary, +// MarshalJSON, and MarshalText methods store the Time.Location's offset, but not +// the location name. They therefore lose information about Daylight Saving Time. +// +// In addition to the required “wall clock” reading, a Time may contain an optional +// reading of the current process's monotonic clock, to provide additional precision +// for comparison or subtraction. +// See the “Monotonic Clocks” section in the package documentation for details. +// +// Note that the Go == operator compares not just the time instant but also the +// Location and the monotonic clock reading. Therefore, Time values should not +// be used as map or database keys without first guaranteeing that the +// identical Location has been set for all values, which can be achieved +// through use of the UTC or Local method, and that the monotonic clock reading +// has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) +// to t == u, since t.Equal uses the most accurate comparison available and +// correctly handles the case when only one of its arguments has a monotonic +// clock reading. +type Time struct { + // wall and ext encode the wall time seconds, wall time nanoseconds, + // and optional monotonic clock reading in nanoseconds. + // + // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic), + // a 33-bit seconds field, and a 30-bit wall time nanoseconds field. + // The nanoseconds field is in the range [0, 999999999]. + // If the hasMonotonic bit is 0, then the 33-bit field must be zero + // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext. + // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit + // unsigned wall seconds since Jan 1 year 1885, and ext holds a + // signed 64-bit monotonic clock reading, nanoseconds since process start. + wall uint64 + ext int64 + + // loc specifies the Location that should be used to + // determine the minute, hour, month, day, and year + // that correspond to this Time. + // The nil location means UTC. + // All UTC times are represented with loc==nil, never loc==&utcLoc. + loc *Location +} + +const ( + hasMonotonic = 1 << 63 + maxWall = wallToInternal + (1<<33 - 1) // year 2157 + minWall = wallToInternal // year 1885 + nsecMask = 1<<30 - 1 + nsecShift = 30 +) + +// These helpers for manipulating the wall and monotonic clock readings +// take pointer receivers, even when they don't modify the time, +// to make them cheaper to call. + +// nsec returns the time's nanoseconds. +func (t *Time) nsec() int32 { + return int32(t.wall & nsecMask) +} + +// sec returns the time's seconds since Jan 1 year 1. +func (t *Time) sec() int64 { + if t.wall&hasMonotonic != 0 { + return wallToInternal + int64(t.wall<<1>>(nsecShift+1)) + } + return t.ext +} + +// unixSec returns the time's seconds since Jan 1 1970 (Unix time). +func (t *Time) unixSec() int64 { return t.sec() + internalToUnix } + +// addSec adds d seconds to the time. +func (t *Time) addSec(d int64) { + if t.wall&hasMonotonic != 0 { + sec := int64(t.wall << 1 >> (nsecShift + 1)) + dsec := sec + d + if 0 <= dsec && dsec <= 1<<33-1 { + t.wall = t.wall&nsecMask | uint64(dsec)< t.ext) == (d > 0) { + t.ext = sum + } else if d > 0 { + t.ext = 1<<63 - 1 + } else { + t.ext = -(1<<63 - 1) + } +} + +// setLoc sets the location associated with the time. +func (t *Time) setLoc(loc *Location) { + if loc == &utcLoc { + loc = nil + } + t.stripMono() + t.loc = loc +} + +// stripMono strips the monotonic clock reading in t. +func (t *Time) stripMono() { + if t.wall&hasMonotonic != 0 { + t.ext = t.sec() + t.wall &= nsecMask + } +} + +// setMono sets the monotonic clock reading in t. +// If t cannot hold a monotonic clock reading, +// because its wall time is too large, +// setMono is a no-op. +func (t *Time) setMono(m int64) { + if t.wall&hasMonotonic == 0 { + sec := t.ext + if sec < minWall || maxWall < sec { + return + } + t.wall |= hasMonotonic | uint64(sec-minWall)< u.ext + } + ts := t.sec() + us := u.sec() + return ts > us || ts == us && t.nsec() > u.nsec() +} + +// Before reports whether the time instant t is before u. +func (t Time) Before(u Time) bool { + if t.wall&u.wall&hasMonotonic != 0 { + return t.ext < u.ext + } + ts := t.sec() + us := u.sec() + return ts < us || ts == us && t.nsec() < u.nsec() +} + +// Equal reports whether t and u represent the same time instant. +// Two times can be equal even if they are in different locations. +// For example, 6:00 +0200 and 4:00 UTC are Equal. +// See the documentation on the Time type for the pitfalls of using == with +// Time values; most code should use Equal instead. +func (t Time) Equal(u Time) bool { + if t.wall&u.wall&hasMonotonic != 0 { + return t.ext == u.ext + } + return t.sec() == u.sec() && t.nsec() == u.nsec() +} + +// A Month specifies a month of the year (January = 1, ...). +type Month int + +const ( + January Month = 1 + iota + February + March + April + May + June + July + August + September + October + November + December +) + +// String returns the English name of the month ("January", "February", ...). +func (m Month) String() string { + if January <= m && m <= December { + return longMonthNames[m-1] + } + buf := make([]byte, 20) + n := fmtInt(buf, uint64(m)) + return "%!Month(" + string(buf[n:]) + ")" +} + +// A Weekday specifies a day of the week (Sunday = 0, ...). +type Weekday int + +const ( + Sunday Weekday = iota + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday +) + +// String returns the English name of the day ("Sunday", "Monday", ...). +func (d Weekday) String() string { + if Sunday <= d && d <= Saturday { + return longDayNames[d] + } + buf := make([]byte, 20) + n := fmtInt(buf, uint64(d)) + return "%!Weekday(" + string(buf[n:]) + ")" +} + +// Computations on time. +// +// The zero value for a Time is defined to be +// January 1, year 1, 00:00:00.000000000 UTC +// which (1) looks like a zero, or as close as you can get in a date +// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to +// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a +// non-negative year even in time zones west of UTC, unlike 1-1-0 +// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York. +// +// The zero Time value does not force a specific epoch for the time +// representation. For example, to use the Unix epoch internally, we +// could define that to distinguish a zero value from Jan 1 1970, that +// time would be represented by sec=-1, nsec=1e9. However, it does +// suggest a representation, namely using 1-1-1 00:00:00 UTC as the +// epoch, and that's what we do. +// +// The Add and Sub computations are oblivious to the choice of epoch. +// +// The presentation computations - year, month, minute, and so on - all +// rely heavily on division and modulus by positive constants. For +// calendrical calculations we want these divisions to round down, even +// for negative values, so that the remainder is always positive, but +// Go's division (like most hardware division instructions) rounds to +// zero. We can still do those computations and then adjust the result +// for a negative numerator, but it's annoying to write the adjustment +// over and over. Instead, we can change to a different epoch so long +// ago that all the times we care about will be positive, and then round +// to zero and round down coincide. These presentation routines already +// have to add the zone offset, so adding the translation to the +// alternate epoch is cheap. For example, having a non-negative time t +// means that we can write +// +// sec = t % 60 +// +// instead of +// +// sec = t % 60 +// if sec < 0 { +// sec += 60 +// } +// +// everywhere. +// +// The calendar runs on an exact 400 year cycle: a 400-year calendar +// printed for 1970-2369 will apply as well to 2370-2769. Even the days +// of the week match up. It simplifies the computations to choose the +// cycle boundaries so that the exceptional years are always delayed as +// long as possible. That means choosing a year equal to 1 mod 400, so +// that the first leap year is the 4th year, the first missed leap year +// is the 100th year, and the missed missed leap year is the 400th year. +// So we'd prefer instead to print a calendar for 2001-2400 and reuse it +// for 2401-2800. +// +// Finally, it's convenient if the delta between the Unix epoch and +// long-ago epoch is representable by an int64 constant. +// +// These three considerations—choose an epoch as early as possible, that +// uses a year equal to 1 mod 400, and that is no more than 2⁶³ seconds +// earlier than 1970—bring us to the year -292277022399. We refer to +// this year as the absolute zero year, and to times measured as a uint64 +// seconds since this year as absolute times. +// +// Times measured as an int64 seconds since the year 1—the representation +// used for Time's sec field—are called internal times. +// +// Times measured as an int64 seconds since the year 1970 are called Unix +// times. +// +// It is tempting to just use the year 1 as the absolute epoch, defining +// that the routines are only valid for years >= 1. However, the +// routines would then be invalid when displaying the epoch in time zones +// west of UTC, since it is year 0. It doesn't seem tenable to say that +// printing the zero time correctly isn't supported in half the time +// zones. By comparison, it's reasonable to mishandle some times in +// the year -292277022399. +// +// All this is opaque to clients of the API and can be changed if a +// better implementation presents itself. + +const ( + // The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = -292277022399 + + // The year of the zero Time. + // Assumed by the unixToInternal computation below. + internalYear = 1 + + // Offsets to convert between internal and absolute or Unix times. + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + internalToAbsolute = -absoluteToInternal + + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal + + wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay +) + +// IsZero reports whether t represents the zero time instant, +// January 1, year 1, 00:00:00 UTC. +func (t Time) IsZero() bool { + return t.sec() == 0 && t.nsec() == 0 +} + +// abs returns the time t as an absolute time, adjusted by the zone offset. +// It is called when computing a presentation property like Month or Hour. +func (t Time) abs() uint64 { + l := t.loc + // Avoid function calls when possible. + if l == nil || l == &localLoc { + l = l.get() + } + sec := t.unixSec() + if l != &utcLoc { + if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + sec += int64(l.cacheZone.offset) + } else { + _, offset, _, _, _ := l.lookup(sec) + sec += int64(offset) + } + } + return uint64(sec + (unixToInternal + internalToAbsolute)) +} + +// locabs is a combination of the Zone and abs methods, +// extracting both return values from a single zone lookup. +func (t Time) locabs() (name string, offset int, abs uint64) { + l := t.loc + if l == nil || l == &localLoc { + l = l.get() + } + // Avoid function call if we hit the local time cache. + sec := t.unixSec() + if l != &utcLoc { + if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + name = l.cacheZone.name + offset = l.cacheZone.offset + } else { + name, offset, _, _, _ = l.lookup(sec) + } + sec += int64(offset) + } else { + name = "UTC" + } + abs = uint64(sec + (unixToInternal + internalToAbsolute)) + return +} + +// Date returns the year, month, and day in which t occurs. +func (t Time) Date() (year int, month Month, day int) { + year, month, day, _ = t.date(true) + return +} + +// Year returns the year in which t occurs. +func (t Time) Year() int { + year, _, _, _ := t.date(false) + return year +} + +// Month returns the month of the year specified by t. +func (t Time) Month() Month { + _, month, _, _ := t.date(true) + return month +} + +// Day returns the day of the month specified by t. +func (t Time) Day() int { + _, _, day, _ := t.date(true) + return day +} + +// Weekday returns the day of the week specified by t. +func (t Time) Weekday() Weekday { + return absWeekday(t.abs()) +} + +// absWeekday is like Weekday but operates on an absolute time. +func absWeekday(abs uint64) Weekday { + // January 1 of the absolute year, like January 1 of 2001, was a Monday. + sec := (abs + uint64(Monday)*secondsPerDay) % secondsPerWeek + return Weekday(int(sec) / secondsPerDay) +} + +// ISOWeek returns the ISO 8601 year and week number in which t occurs. +// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to +// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 +// of year n+1. +func (t Time) ISOWeek() (year, week int) { + // According to the rule that the first calendar week of a calendar year is + // the week including the first Thursday of that year, and that the last one is + // the week immediately preceding the first calendar week of the next calendar year. + // See https://www.iso.org/obp/ui#iso:std:iso:8601:-1:ed-1:v1:en:term:3.1.1.23 for details. + + // weeks start with Monday + // Monday Tuesday Wednesday Thursday Friday Saturday Sunday + // 1 2 3 4 5 6 7 + // +3 +2 +1 0 -1 -2 -3 + // the offset to Thursday + abs := t.abs() + d := Thursday - absWeekday(abs) + // handle Sunday + if d == 4 { + d = -3 + } + // find the Thursday of the calendar week + abs += uint64(d) * secondsPerDay + year, _, _, yday := absDate(abs, false) + return year, yday/7 + 1 +} + +// Clock returns the hour, minute, and second within the day specified by t. +func (t Time) Clock() (hour, min, sec int) { + return absClock(t.abs()) +} + +// absClock is like clock but operates on an absolute time. +func absClock(abs uint64) (hour, min, sec int) { + sec = int(abs % secondsPerDay) + hour = sec / secondsPerHour + sec -= hour * secondsPerHour + min = sec / secondsPerMinute + sec -= min * secondsPerMinute + return +} + +// Hour returns the hour within the day specified by t, in the range [0, 23]. +func (t Time) Hour() int { + return int(t.abs()%secondsPerDay) / secondsPerHour +} + +// Minute returns the minute offset within the hour specified by t, in the range [0, 59]. +func (t Time) Minute() int { + return int(t.abs()%secondsPerHour) / secondsPerMinute +} + +// Second returns the second offset within the minute specified by t, in the range [0, 59]. +func (t Time) Second() int { + return int(t.abs() % secondsPerMinute) +} + +// Nanosecond returns the nanosecond offset within the second specified by t, +// in the range [0, 999999999]. +func (t Time) Nanosecond() int { + return int(t.nsec()) +} + +// YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, +// and [1,366] in leap years. +func (t Time) YearDay() int { + _, _, _, yday := t.date(false) + return yday + 1 +} + +// A Duration represents the elapsed time between two instants +// as an int64 nanosecond count. The representation limits the +// largest representable duration to approximately 290 years. +type Duration int64 + +const ( + minDuration Duration = -1 << 63 + maxDuration Duration = 1<<63 - 1 +) + +// Common durations. There is no definition for units of Day or larger +// to avoid confusion across daylight savings time zone transitions. +// +// To count the number of units in a Duration, divide: +// +// second := time.Second +// fmt.Print(int64(second/time.Millisecond)) // prints 1000 +// +// To convert an integer number of units to a Duration, multiply: +// +// seconds := 10 +// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s +const ( + Nanosecond Duration = 1 + Microsecond = 1000 * Nanosecond + Millisecond = 1000 * Microsecond + Second = 1000 * Millisecond + Minute = 60 * Second + Hour = 60 * Minute +) + +// String returns a string representing the duration in the form "72h3m0.5s". +// Leading zero units are omitted. As a special case, durations less than one +// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure +// that the leading digit is non-zero. The zero duration formats as 0s. +func (d Duration) String() string { + // Largest time is 2540400h10m10.000000000s + var buf [32]byte + w := len(buf) + + u := uint64(d) + neg := d < 0 + if neg { + u = -u + } + + if u < uint64(Second) { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + var prec int + w-- + buf[w] = 's' + w-- + switch { + case u == 0: + return "0s" + case u < uint64(Microsecond): + // print nanoseconds + prec = 0 + buf[w] = 'n' + case u < uint64(Millisecond): + // print microseconds + prec = 3 + // U+00B5 'µ' micro sign == 0xC2 0xB5 + w-- // Need room for two bytes. + copy(buf[w:], "µ") + default: + // print milliseconds + prec = 6 + buf[w] = 'm' + } + w, u = fmtFrac(buf[:w], u, prec) + w = fmtInt(buf[:w], u) + } else { + w-- + buf[w] = 's' + + w, u = fmtFrac(buf[:w], u, 9) + + // u is now integer seconds + w = fmtInt(buf[:w], u%60) + u /= 60 + + // u is now integer minutes + if u > 0 { + w-- + buf[w] = 'm' + w = fmtInt(buf[:w], u%60) + u /= 60 + + // u is now integer hours + // Stop at hours because days can be different lengths. + if u > 0 { + w-- + buf[w] = 'h' + w = fmtInt(buf[:w], u) + } + } + } + + if neg { + w-- + buf[w] = '-' + } + + return string(buf[w:]) +} + +// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the +// tail of buf, omitting trailing zeros. It omits the decimal +// point too when the fraction is 0. It returns the index where the +// output bytes begin and the value v/10**prec. +func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) { + // Omit trailing zeros up to and including decimal point. + w := len(buf) + print := false + for i := 0; i < prec; i++ { + digit := v % 10 + print = print || digit != 0 + if print { + w-- + buf[w] = byte(digit) + '0' + } + v /= 10 + } + if print { + w-- + buf[w] = '.' + } + return w, v +} + +// fmtInt formats v into the tail of buf. +// It returns the index where the output begins. +func fmtInt(buf []byte, v uint64) int { + w := len(buf) + if v == 0 { + w-- + buf[w] = '0' + } else { + for v > 0 { + w-- + buf[w] = byte(v%10) + '0' + v /= 10 + } + } + return w +} + +// Nanoseconds returns the duration as an integer nanosecond count. +func (d Duration) Nanoseconds() int64 { return int64(d) } + +// Microseconds returns the duration as an integer microsecond count. +func (d Duration) Microseconds() int64 { return int64(d) / 1e3 } + +// Milliseconds returns the duration as an integer millisecond count. +func (d Duration) Milliseconds() int64 { return int64(d) / 1e6 } + +// These methods return float64 because the dominant +// use case is for printing a floating point number like 1.5s, and +// a truncation to integer would make them not useful in those cases. +// Splitting the integer and fraction ourselves guarantees that +// converting the returned float64 to an integer rounds the same +// way that a pure integer conversion would have, even in cases +// where, say, float64(d.Nanoseconds())/1e9 would have rounded +// differently. + +// Seconds returns the duration as a floating point number of seconds. +func (d Duration) Seconds() float64 { + sec := d / Second + nsec := d % Second + return float64(sec) + float64(nsec)/1e9 +} + +// Minutes returns the duration as a floating point number of minutes. +func (d Duration) Minutes() float64 { + min := d / Minute + nsec := d % Minute + return float64(min) + float64(nsec)/(60*1e9) +} + +// Hours returns the duration as a floating point number of hours. +func (d Duration) Hours() float64 { + hour := d / Hour + nsec := d % Hour + return float64(hour) + float64(nsec)/(60*60*1e9) +} + +// Truncate returns the result of rounding d toward zero to a multiple of m. +// If m <= 0, Truncate returns d unchanged. +func (d Duration) Truncate(m Duration) Duration { + if m <= 0 { + return d + } + return d - d%m +} + +// lessThanHalf reports whether x+x < y but avoids overflow, +// assuming x and y are both positive (Duration is signed). +func lessThanHalf(x, y Duration) bool { + return uint64(x)+uint64(x) < uint64(y) +} + +// Round returns the result of rounding d to the nearest multiple of m. +// The rounding behavior for halfway values is to round away from zero. +// If the result exceeds the maximum (or minimum) +// value that can be stored in a Duration, +// Round returns the maximum (or minimum) duration. +// If m <= 0, Round returns d unchanged. +func (d Duration) Round(m Duration) Duration { + if m <= 0 { + return d + } + r := d % m + if d < 0 { + r = -r + if lessThanHalf(r, m) { + return d + r + } + if d1 := d - m + r; d1 < d { + return d1 + } + return minDuration // overflow + } + if lessThanHalf(r, m) { + return d - r + } + if d1 := d + m - r; d1 > d { + return d1 + } + return maxDuration // overflow +} + +// Abs returns the absolute value of d. +// As a special case, math.MinInt64 is converted to math.MaxInt64. +func (d Duration) Abs() Duration { + switch { + case d >= 0: + return d + case d == minDuration: + return maxDuration + default: + return -d + } +} + +// Add returns the time t+d. +func (t Time) Add(d Duration) Time { + dsec := int64(d / 1e9) + nsec := t.nsec() + int32(d%1e9) + if nsec >= 1e9 { + dsec++ + nsec -= 1e9 + } else if nsec < 0 { + dsec-- + nsec += 1e9 + } + t.wall = t.wall&^nsecMask | uint64(nsec) // update nsec + t.addSec(dsec) + if t.wall&hasMonotonic != 0 { + te := t.ext + int64(d) + if d < 0 && te > t.ext || d > 0 && te < t.ext { + // Monotonic clock reading now out of range; degrade to wall-only. + t.stripMono() + } else { + t.ext = te + } + } + return t +} + +// Sub returns the duration t-u. If the result exceeds the maximum (or minimum) +// value that can be stored in a Duration, the maximum (or minimum) duration +// will be returned. +// To compute t-d for a duration d, use t.Add(-d). +func (t Time) Sub(u Time) Duration { + if t.wall&u.wall&hasMonotonic != 0 { + te := t.ext + ue := u.ext + d := Duration(te - ue) + if d < 0 && te > ue { + return maxDuration // t - u is positive out of range + } + if d > 0 && te < ue { + return minDuration // t - u is negative out of range + } + return d + } + d := Duration(t.sec()-u.sec())*Second + Duration(t.nsec()-u.nsec()) + // Check for overflow or underflow. + switch { + case u.Add(d).Equal(t): + return d // d is correct + case t.Before(u): + return minDuration // t - u is negative out of range + default: + return maxDuration // t - u is positive out of range + } +} + +// Since returns the time elapsed since t. +// It is shorthand for time.Now().Sub(t). +func Since(t Time) Duration { + var now Time + if t.wall&hasMonotonic != 0 { + // Common case optimization: if t has monotonic time, then Sub will use only it. + now = Time{hasMonotonic, runtimeNano() - startNano, nil} + } else { + now = Now() + } + return now.Sub(t) +} + +// Until returns the duration until t. +// It is shorthand for t.Sub(time.Now()). +func Until(t Time) Duration { + var now Time + if t.wall&hasMonotonic != 0 { + // Common case optimization: if t has monotonic time, then Sub will use only it. + now = Time{hasMonotonic, runtimeNano() - startNano, nil} + } else { + now = Now() + } + return t.Sub(now) +} + +// AddDate returns the time corresponding to adding the +// given number of years, months, and days to t. +// For example, AddDate(-1, 2, 3) applied to January 1, 2011 +// returns March 4, 2010. +// +// AddDate normalizes its result in the same way that Date does, +// so, for example, adding one month to October 31 yields +// December 1, the normalized form for November 31. +func (t Time) AddDate(years int, months int, days int) Time { + year, month, day := t.Date() + hour, min, sec := t.Clock() + return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec()), t.Location()) +} + +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 +) + +// date computes the year, day of year, and when full=true, +// the month and day in which t occurs. +func (t Time) date(full bool) (year int, month Month, day int, yday int) { + return absDate(t.abs(), full) +} + +// absDate is like date but operates on an absolute time. +func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) { + // Split into time and day. + d := abs / secondsPerDay + + // Account for 400 year cycles. + n := d / daysPer400Years + y := 400 * n + d -= daysPer400Years * n + + // Cut off 100-year cycles. + // The last cycle has one extra leap year, so on the last day + // of that year, day / daysPer100Years will be 4 instead of 3. + // Cut it back down to 3 by subtracting n>>2. + n = d / daysPer100Years + n -= n >> 2 + y += 100 * n + d -= daysPer100Years * n + + // Cut off 4-year cycles. + // The last cycle has a missing leap year, which does not + // affect the computation. + n = d / daysPer4Years + y += 4 * n + d -= daysPer4Years * n + + // Cut off years within a 4-year cycle. + // The last year is a leap year, so on the last day of that year, + // day / 365 will be 4 instead of 3. Cut it back down to 3 + // by subtracting n>>2. + n = d / 365 + n -= n >> 2 + y += n + d -= 365 * n + + year = int(int64(y) + absoluteZeroYear) + yday = int(d) + + if !full { + return + } + + day = yday + if isLeap(year) { + // Leap year + switch { + case day > 31+29-1: + // After leap day; pretend it wasn't there. + day-- + case day == 31+29-1: + // Leap day. + month = February + day = 29 + return + } + } + + // Estimate month on assumption that every month has 31 days. + // The estimate may be too low by at most one month, so adjust. + month = Month(day / 31) + end := int(daysBefore[month+1]) + var begin int + if day >= end { + month++ + begin = end + } else { + begin = int(daysBefore[month]) + } + + month++ // because January is 1 + day = day - begin + 1 + return +} + +// daysBefore[m] counts the number of days in a non-leap year +// before month m begins. There is an entry for m=12, counting +// the number of days before January of next year (365). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +func daysIn(m Month, year int) int { + if m == February && isLeap(year) { + return 29 + } + return int(daysBefore[m] - daysBefore[m-1]) +} + +// daysSinceEpoch takes a year and returns the number of days from +// the absolute epoch to the start of that year. +// This is basically (year - zeroYear) * 365, but accounting for leap days. +func daysSinceEpoch(year int) uint64 { + y := uint64(int64(year) - absoluteZeroYear) + + // Add in days from 400-year cycles. + n := y / 400 + y -= 400 * n + d := daysPer400Years * n + + // Add in 100-year cycles. + n = y / 100 + y -= 100 * n + d += daysPer100Years * n + + // Add in 4-year cycles. + n = y / 4 + y -= 4 * n + d += daysPer4Years * n + + // Add in non-leap years. + n = y + d += 365 * n + + return d +} + +/* XXX replaced with ios.Now() +// Provided by package runtime. +func now() (sec int64, nsec int32, mono int64) +*/ + +// XXX SHIM +// runtimeNano returns the current value of the runtime clock in nanoseconds. +// +//go:linkname runtimeNano runtime.nanotime +func runtimeNano() int64 { + return 0 +} + +// Monotonic times are reported as offsets from startNano. +// We initialize startNano to runtimeNano() - 1 so that on systems where +// monotonic time resolution is fairly low (e.g. Windows 2008 +// which appears to have a default resolution of 15ms), +// we avoid ever reporting a monotonic time of 0. +// (Callers may want to use 0 as "time not set".) +var startNano int64 = runtimeNano() - 1 + +// Now returns the current local time. +func Now() Time { + sec, nsec, mono := ios.Now() // XXX now() + mono -= startNano + sec += unixToInternal - minWall + if uint64(sec)>>33 != 0 { + return Time{uint64(nsec), sec + minWall, Local} + } + return Time{hasMonotonic | uint64(sec)< 32767 { + return nil, errors.New("Time.MarshalBinary: unexpected zone offset") + } + offsetMin = int16(offset) + } + + sec := t.sec() + nsec := t.nsec() + enc := []byte{ + version, // byte 0 : version + byte(sec >> 56), // bytes 1-8: seconds + byte(sec >> 48), + byte(sec >> 40), + byte(sec >> 32), + byte(sec >> 24), + byte(sec >> 16), + byte(sec >> 8), + byte(sec), + byte(nsec >> 24), // bytes 9-12: nanoseconds + byte(nsec >> 16), + byte(nsec >> 8), + byte(nsec), + byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes + byte(offsetMin), + } + if version == timeBinaryVersionV2 { + enc = append(enc, byte(offsetSec)) + } + + return enc, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (t *Time) UnmarshalBinary(data []byte) error { + buf := data + if len(buf) == 0 { + return errors.New("Time.UnmarshalBinary: no data") + } + + version := buf[0] + if version != timeBinaryVersionV1 && version != timeBinaryVersionV2 { + return errors.New("Time.UnmarshalBinary: unsupported version") + } + + wantLen := /*version*/ 1 + /*sec*/ 8 + /*nsec*/ 4 + /*zone offset*/ 2 + if version == timeBinaryVersionV2 { + wantLen++ + } + if len(buf) != wantLen { + return errors.New("Time.UnmarshalBinary: invalid length") + } + + buf = buf[1:] + sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 | + int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56 + + buf = buf[8:] + nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 + + buf = buf[4:] + offset := int(int16(buf[1])|int16(buf[0])<<8) * 60 + if version == timeBinaryVersionV2 { + offset += int(buf[2]) + } + + *t = Time{} + t.wall = uint64(nsec) + t.ext = sec + + if offset == -1*60 { + t.setLoc(&utcLoc) + } else if _, localoff, _, _, _ := Local.lookup(t.unixSec()); offset == localoff { + t.setLoc(Local) + } else { + t.setLoc(FixedZone("", offset)) + } + + return nil +} + +// TODO(rsc): Remove GobEncoder, GobDecoder, MarshalJSON, UnmarshalJSON in Go 2. +// The same semantics will be provided by the generic MarshalBinary, MarshalText, +// UnmarshalBinary, UnmarshalText. + +// GobEncode implements the gob.GobEncoder interface. +func (t Time) GobEncode() ([]byte, error) { + return t.MarshalBinary() +} + +// GobDecode implements the gob.GobDecoder interface. +func (t *Time) GobDecode(data []byte) error { + return t.UnmarshalBinary(data) +} + +// MarshalJSON implements the json.Marshaler interface. +// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalJSON() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + // RFC 3339 is clear that years are 4 digits exactly. + // See golang.org/issue/4556#c15 for more discussion. + return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") + } + + b := make([]byte, 0, len(RFC3339Nano)+2) + b = append(b, '"') + b = t.AppendFormat(b, RFC3339Nano) + b = append(b, '"') + return b, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// The time is expected to be a quoted string in RFC 3339 format. +func (t *Time) UnmarshalJSON(data []byte) error { + // Ignore null, like in the main JSON package. + if string(data) == "null" { + return nil + } + // Fractional seconds are handled implicitly by Parse. + var err error + *t, err = Parse(`"`+RFC3339+`"`, string(data)) + return err +} + +// MarshalText implements the encoding.TextMarshaler interface. +// The time is formatted in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalText() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") + } + + b := make([]byte, 0, len(RFC3339Nano)) + return t.AppendFormat(b, RFC3339Nano), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The time is expected to be in RFC 3339 format. +func (t *Time) UnmarshalText(data []byte) error { + // Fractional seconds are handled implicitly by Parse. + var err error + *t, err = Parse(RFC3339, string(data)) + return err +} + +// Unix returns the local Time corresponding to the given Unix time, +// sec seconds and nsec nanoseconds since January 1, 1970 UTC. +// It is valid to pass nsec outside the range [0, 999999999]. +// Not all sec values have a corresponding time value. One such +// value is 1<<63-1 (the largest int64 value). +func Unix(sec int64, nsec int64) Time { + if nsec < 0 || nsec >= 1e9 { + n := nsec / 1e9 + sec += n + nsec -= n * 1e9 + if nsec < 0 { + nsec += 1e9 + sec-- + } + } + return unixTime(sec, int32(nsec)) +} + +// UnixMilli returns the local Time corresponding to the given Unix time, +// msec milliseconds since January 1, 1970 UTC. +func UnixMilli(msec int64) Time { + return Unix(msec/1e3, (msec%1e3)*1e6) +} + +// UnixMicro returns the local Time corresponding to the given Unix time, +// usec microseconds since January 1, 1970 UTC. +func UnixMicro(usec int64) Time { + return Unix(usec/1e6, (usec%1e6)*1e3) +} + +// IsDST reports whether the time in the configured location is in Daylight Savings Time. +func (t Time) IsDST() bool { + _, _, _, _, isDST := t.loc.lookup(t.Unix()) + return isDST +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +// norm returns nhi, nlo such that +// +// hi * base + lo == nhi * base + nlo +// 0 <= nlo < base +func norm(hi, lo, base int) (nhi, nlo int) { + if lo < 0 { + n := (-lo-1)/base + 1 + hi -= n + lo += n * base + } + if lo >= base { + n := lo / base + hi += n + lo -= n * base + } + return hi, lo +} + +// Date returns the Time corresponding to +// +// yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// +// in the appropriate zone for that time in the given location. +// +// The month, day, hour, min, sec, and nsec values may be outside +// their usual ranges and will be normalized during the conversion. +// For example, October 32 converts to November 1. +// +// A daylight savings time transition skips or repeats times. +// For example, in the United States, March 13, 2011 2:15am never occurred, +// while November 6, 2011 1:15am occurred twice. In such cases, the +// choice of time zone, and therefore the time, is not well-defined. +// Date returns a time that is correct in one of the two zones involved +// in the transition, but it does not guarantee which. +// +// Date panics if loc is nil. +func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { + if loc == nil { + panic("time: missing Location in call to Date") + } + + // Normalize month, overflowing into year. + m := int(month) - 1 + year, m = norm(year, m, 12) + month = Month(m) + 1 + + // Normalize nsec, sec, min, hour, overflowing into day. + sec, nsec = norm(sec, nsec, 1e9) + min, sec = norm(min, sec, 60) + hour, min = norm(hour, min, 60) + day, hour = norm(day, hour, 24) + + // Compute days since the absolute epoch. + d := daysSinceEpoch(year) + + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 + } + + // Add in days before today. + d += uint64(day - 1) + + // Add in time elapsed today. + abs := d * secondsPerDay + abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + + unix := int64(abs) + (absoluteToInternal + internalToUnix) + + // Look for zone offset for expected time, so we can adjust to UTC. + // The lookup function expects UTC, so first we pass unix in the + // hope that it will not be too close to a zone transition, + // and then adjust if it is. + _, offset, start, end, _ := loc.lookup(unix) + if offset != 0 { + utc := unix - int64(offset) + // If utc is valid for the time zone we found, then we have the right offset. + // If not, we get the correct offset by looking up utc in the location. + if utc < start || utc >= end { + _, offset, _, _, _ = loc.lookup(utc) + } + unix -= int64(offset) + } + + t := unixTime(unix, int32(nsec)) + t.setLoc(loc) + return t +} + +// Truncate returns the result of rounding t down to a multiple of d (since the zero time). +// If d <= 0, Truncate returns t stripped of any monotonic clock reading but otherwise unchanged. +// +// Truncate operates on the time as an absolute duration since the +// zero time; it does not operate on the presentation form of the +// time. Thus, Truncate(Hour) may return a time with a non-zero +// minute, depending on the time's Location. +func (t Time) Truncate(d Duration) Time { + t.stripMono() + if d <= 0 { + return t + } + _, r := div(t, d) + return t.Add(-r) +} + +// Round returns the result of rounding t to the nearest multiple of d (since the zero time). +// The rounding behavior for halfway values is to round up. +// If d <= 0, Round returns t stripped of any monotonic clock reading but otherwise unchanged. +// +// Round operates on the time as an absolute duration since the +// zero time; it does not operate on the presentation form of the +// time. Thus, Round(Hour) may return a time with a non-zero +// minute, depending on the time's Location. +func (t Time) Round(d Duration) Time { + t.stripMono() + if d <= 0 { + return t + } + _, r := div(t, d) + if lessThanHalf(r, d) { + return t.Add(-r) + } + return t.Add(d - r) +} + +// div divides t by d and returns the quotient parity and remainder. +// We don't use the quotient parity anymore (round half up instead of round to even) +// but it's still here in case we change our minds. +func div(t Time, d Duration) (qmod2 int, r Duration) { + neg := false + nsec := t.nsec() + sec := t.sec() + if sec < 0 { + // Operate on absolute value. + neg = true + sec = -sec + nsec = -nsec + if nsec < 0 { + nsec += 1e9 + sec-- // sec >= 1 before the -- so safe + } + } + + switch { + // Special case: 2d divides 1 second. + case d < Second && Second%(d+d) == 0: + qmod2 = int(nsec/int32(d)) & 1 + r = Duration(nsec % int32(d)) + + // Special case: d is a multiple of 1 second. + case d%Second == 0: + d1 := int64(d / Second) + qmod2 = int(sec/d1) & 1 + r = Duration(sec%d1)*Second + Duration(nsec) + + // General case. + // This could be faster if more cleverness were applied, + // but it's really only here to avoid special case restrictions in the API. + // No one will care about these cases. + default: + // Compute nanoseconds as 128-bit number. + sec := uint64(sec) + tmp := (sec >> 32) * 1e9 + u1 := tmp >> 32 + u0 := tmp << 32 + tmp = (sec & 0xFFFFFFFF) * 1e9 + u0x, u0 := u0, u0+tmp + if u0 < u0x { + u1++ + } + u0x, u0 = u0, u0+uint64(nsec) + if u0 < u0x { + u1++ + } + + // Compute remainder by subtracting r<>63 != 1 { + d1 <<= 1 + } + d0 := uint64(0) + for { + qmod2 = 0 + if u1 > d1 || u1 == d1 && u0 >= d0 { + // subtract + qmod2 = 1 + u0x, u0 = u0, u0-d0 + if u0 > u0x { + u1-- + } + u1 -= d1 + } + if d1 == 0 && d0 == uint64(d) { + break + } + d0 >>= 1 + d0 |= (d1 & 1) << 63 + d1 >>= 1 + } + r = Duration(u0) + } + + if neg && r != 0 { + // If input was negative and not an exact multiple of d, we computed q, r such that + // q*d + r = -t + // But the right answers are given by -(q-1), d-r: + // q*d + r = -t + // -q*d - r = t + // -(q-1)*d + (d - r) = t + qmod2 ^= 1 + r = d - r + } + return +} diff --git a/stdlibs/time/timezoneinfo.gno b/stdlibs/time/timezoneinfo.gno new file mode 100644 index 00000000000..d056296d1e3 --- /dev/null +++ b/stdlibs/time/timezoneinfo.gno @@ -0,0 +1,692 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +import ( + "errors" + // "sync" XXX + // "syscall" XXX +) + +//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go + +// A Location maps time instants to the zone in use at that time. +// Typically, the Location represents the collection of time offsets +// in use in a geographical area. For many Locations the time offset varies +// depending on whether daylight savings time is in use at the time instant. +type Location struct { + name string + zone []zone + tx []zoneTrans + + // The tzdata information can be followed by a string that describes + // how to handle DST transitions not recorded in zoneTrans. + // The format is the TZ environment variable without a colon; see + // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html. + // Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0 + extend string + + // Most lookups will be for the current time. + // To avoid the binary search through tx, keep a + // static one-element cache that gives the correct + // zone for the time when the Location was created. + // if cacheStart <= t < cacheEnd, + // lookup can return cacheZone. + // The units for cacheStart and cacheEnd are seconds + // since January 1, 1970 UTC, to match the argument + // to lookup. + cacheStart int64 + cacheEnd int64 + cacheZone *zone +} + +// A zone represents a single time zone such as CET. +type zone struct { + name string // abbreviated name, "CET" + offset int // seconds east of UTC + isDST bool // is this zone Daylight Savings Time? +} + +// A zoneTrans represents a single time zone transition. +type zoneTrans struct { + when int64 // transition time, in seconds since 1970 GMT + index uint8 // the index of the zone that goes into effect at that time + isstd, isutc bool // ignored - no idea what these mean +} + +// alpha and omega are the beginning and end of time for zone +// transitions. +const ( + alpha = -1 << 63 // math.MinInt64 + omega = 1<<63 - 1 // math.MaxInt64 +) + +// UTC represents Universal Coordinated Time (UTC). +var UTC *Location = &utcLoc + +// utcLoc is separate so that get can refer to &utcLoc +// and ensure that it never returns a nil *Location, +// even if a badly behaved client has changed UTC. +var utcLoc = Location{name: "UTC"} + +// Local represents the system's local time zone. +// On Unix systems, Local consults the TZ environment +// variable to find the time zone to use. No TZ means +// use the system default /etc/localtime. +// TZ="" means use UTC. +// TZ="foo" means use file foo in the system timezone directory. +var Local *Location = &localLoc + +// localLoc is separate so that initLocal can initialize +// it even if a client has changed Local. +var localLoc Location + +// XXX var localOnce sync.Once + +func (l *Location) get() *Location { + if l == nil { + return &utcLoc + } + if l == &localLoc { + // XXX localOnce.Do(initLocal) + } + return l +} + +// String returns a descriptive name for the time zone information, +// corresponding to the name argument to LoadLocation or FixedZone. +func (l *Location) String() string { + return l.get().name +} + +// FixedZone returns a Location that always uses +// the given zone name and offset (seconds east of UTC). +func FixedZone(name string, offset int) *Location { + l := &Location{ + name: name, + zone: []zone{{name, offset, false}}, + tx: []zoneTrans{{alpha, 0, false, false}}, + cacheStart: alpha, + cacheEnd: omega, + } + l.cacheZone = &l.zone[0] + return l +} + +// lookup returns information about the time zone in use at an +// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. +// +// The returned information gives the name of the zone (such as "CET"), +// the start and end times bracketing sec when that zone is in effect, +// the offset in seconds east of UTC (such as -5*60*60), and whether +// the daylight savings is being observed at that time. +func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) { + l = l.get() + + if len(l.zone) == 0 { + name = "UTC" + offset = 0 + start = alpha + end = omega + isDST = false + return + } + + if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + name = zone.name + offset = zone.offset + start = l.cacheStart + end = l.cacheEnd + isDST = zone.isDST + return + } + + if len(l.tx) == 0 || sec < l.tx[0].when { + zone := &l.zone[l.lookupFirstZone()] + name = zone.name + offset = zone.offset + start = alpha + if len(l.tx) > 0 { + end = l.tx[0].when + } else { + end = omega + } + isDST = zone.isDST + return + } + + // Binary search for entry with largest time <= sec. + // Not using sort.Search to avoid dependencies. + tx := l.tx + end = omega + lo := 0 + hi := len(tx) + for hi-lo > 1 { + m := lo + (hi-lo)/2 + lim := tx[m].when + if sec < lim { + end = lim + hi = m + } else { + lo = m + } + } + zone := &l.zone[tx[lo].index] + name = zone.name + offset = zone.offset + start = tx[lo].when + // end = maintained during the search + isDST = zone.isDST + + // If we're at the end of the known zone transitions, + // try the extend string. + if lo == len(tx)-1 && l.extend != "" { + if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, end, sec); ok { + return ename, eoffset, estart, eend, eisDST + } + } + + return +} + +// lookupFirstZone returns the index of the time zone to use for times +// before the first transition time, or when there are no transition +// times. +// +// The reference implementation in localtime.c from +// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz +// implements the following algorithm for these cases: +// 1. If the first zone is unused by the transitions, use it. +// 2. Otherwise, if there are transition times, and the first +// transition is to a zone in daylight time, find the first +// non-daylight-time zone before and closest to the first transition +// zone. +// 3. Otherwise, use the first zone that is not daylight time, if +// there is one. +// 4. Otherwise, use the first zone. +func (l *Location) lookupFirstZone() int { + // Case 1. + if !l.firstZoneUsed() { + return 0 + } + + // Case 2. + if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST { + for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- { + if !l.zone[zi].isDST { + return zi + } + } + } + + // Case 3. + for zi := range l.zone { + if !l.zone[zi].isDST { + return zi + } + } + + // Case 4. + return 0 +} + +// firstZoneUsed reports whether the first zone is used by some +// transition. +func (l *Location) firstZoneUsed() bool { + for _, tx := range l.tx { + if tx.index == 0 { + return true + } + } + return false +} + +// tzset takes a timezone string like the one found in the TZ environment +// variable, the end of the last time zone transition expressed as seconds +// since January 1, 1970 00:00:00 UTC, and a time expressed the same way. +// We call this a tzset string since in C the function tzset reads TZ. +// The return values are as for lookup, plus ok which reports whether the +// parse succeeded. +func tzset(s string, initEnd, sec int64) (name string, offset int, start, end int64, isDST, ok bool) { + var ( + stdName, dstName string + stdOffset, dstOffset int + ) + + stdName, s, ok = tzsetName(s) + if ok { + stdOffset, s, ok = tzsetOffset(s) + } + if !ok { + return "", 0, 0, 0, false, false + } + + // The numbers in the tzset string are added to local time to get UTC, + // but our offsets are added to UTC to get local time, + // so we negate the number we see here. + stdOffset = -stdOffset + + if len(s) == 0 || s[0] == ',' { + // No daylight savings time. + return stdName, stdOffset, initEnd, omega, false, true + } + + dstName, s, ok = tzsetName(s) + if ok { + if len(s) == 0 || s[0] == ',' { + dstOffset = stdOffset + secondsPerHour + } else { + dstOffset, s, ok = tzsetOffset(s) + dstOffset = -dstOffset // as with stdOffset, above + } + } + if !ok { + return "", 0, 0, 0, false, false + } + + if len(s) == 0 { + // Default DST rules per tzcode. + s = ",M3.2.0,M11.1.0" + } + // The TZ definition does not mention ';' here but tzcode accepts it. + if s[0] != ',' && s[0] != ';' { + return "", 0, 0, 0, false, false + } + s = s[1:] + + var startRule, endRule rule + startRule, s, ok = tzsetRule(s) + if !ok || len(s) == 0 || s[0] != ',' { + return "", 0, 0, 0, false, false + } + s = s[1:] + endRule, s, ok = tzsetRule(s) + if !ok || len(s) > 0 { + return "", 0, 0, 0, false, false + } + + year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false) + + ysec := int64(yday*secondsPerDay) + sec%secondsPerDay + + // Compute start of year in seconds since Unix epoch. + d := daysSinceEpoch(year) + abs := int64(d * secondsPerDay) + abs += absoluteToInternal + internalToUnix + + startSec := int64(tzruleTime(year, startRule, stdOffset)) + endSec := int64(tzruleTime(year, endRule, dstOffset)) + dstIsDST, stdIsDST := true, false + // Note: this is a flipping of "DST" and "STD" while retaining the labels + // This happens in southern hemispheres. The labelling here thus is a little + // inconsistent with the goal. + if endSec < startSec { + startSec, endSec = endSec, startSec + stdName, dstName = dstName, stdName + stdOffset, dstOffset = dstOffset, stdOffset + stdIsDST, dstIsDST = dstIsDST, stdIsDST + } + + // The start and end values that we return are accurate + // close to a daylight savings transition, but are otherwise + // just the start and end of the year. That suffices for + // the only caller that cares, which is Date. + if ysec < startSec { + return stdName, stdOffset, abs, startSec + abs, stdIsDST, true + } else if ysec >= endSec { + return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true + } else { + return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true + } +} + +// tzsetName returns the timezone name at the start of the tzset string s, +// and the remainder of s, and reports whether the parsing is OK. +func tzsetName(s string) (string, string, bool) { + if len(s) == 0 { + return "", "", false + } + if s[0] != '<' { + for i, r := range s { + switch r { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+': + if i < 3 { + return "", "", false + } + return s[:i], s[i:], true + } + } + if len(s) < 3 { + return "", "", false + } + return s, "", true + } else { + for i, r := range s { + if r == '>' { + return s[1:i], s[i+1:], true + } + } + return "", "", false + } +} + +// tzsetOffset returns the timezone offset at the start of the tzset string s, +// and the remainder of s, and reports whether the parsing is OK. +// The timezone offset is returned as a number of seconds. +func tzsetOffset(s string) (offset int, rest string, ok bool) { + if len(s) == 0 { + return 0, "", false + } + neg := false + if s[0] == '+' { + s = s[1:] + } else if s[0] == '-' { + s = s[1:] + neg = true + } + + // The tzdata code permits values up to 24 * 7 here, + // although POSIX does not. + var hours int + hours, s, ok = tzsetNum(s, 0, 24*7) + if !ok { + return 0, "", false + } + off := hours * secondsPerHour + if len(s) == 0 || s[0] != ':' { + if neg { + off = -off + } + return off, s, true + } + + var mins int + mins, s, ok = tzsetNum(s[1:], 0, 59) + if !ok { + return 0, "", false + } + off += mins * secondsPerMinute + if len(s) == 0 || s[0] != ':' { + if neg { + off = -off + } + return off, s, true + } + + var secs int + secs, s, ok = tzsetNum(s[1:], 0, 59) + if !ok { + return 0, "", false + } + off += secs + + if neg { + off = -off + } + return off, s, true +} + +// ruleKind is the kinds of rules that can be seen in a tzset string. +type ruleKind int + +const ( + ruleJulian ruleKind = iota + ruleDOY + ruleMonthWeekDay +) + +// rule is a rule read from a tzset string. +type rule struct { + kind ruleKind + day int + week int + mon int + time int // transition time +} + +// tzsetRule parses a rule from a tzset string. +// It returns the rule, and the remainder of the string, and reports success. +func tzsetRule(s string) (rule, string, bool) { + var r rule + if len(s) == 0 { + return rule{}, "", false + } + ok := false + if s[0] == 'J' { + var jday int + jday, s, ok = tzsetNum(s[1:], 1, 365) + if !ok { + return rule{}, "", false + } + r.kind = ruleJulian + r.day = jday + } else if s[0] == 'M' { + var mon int + mon, s, ok = tzsetNum(s[1:], 1, 12) + if !ok || len(s) == 0 || s[0] != '.' { + return rule{}, "", false + + } + var week int + week, s, ok = tzsetNum(s[1:], 1, 5) + if !ok || len(s) == 0 || s[0] != '.' { + return rule{}, "", false + } + var day int + day, s, ok = tzsetNum(s[1:], 0, 6) + if !ok { + return rule{}, "", false + } + r.kind = ruleMonthWeekDay + r.day = day + r.week = week + r.mon = mon + } else { + var day int + day, s, ok = tzsetNum(s, 0, 365) + if !ok { + return rule{}, "", false + } + r.kind = ruleDOY + r.day = day + } + + if len(s) == 0 || s[0] != '/' { + r.time = 2 * secondsPerHour // 2am is the default + return r, s, true + } + + offset, s, ok := tzsetOffset(s[1:]) + if !ok { + return rule{}, "", false + } + r.time = offset + + return r, s, true +} + +// tzsetNum parses a number from a tzset string. +// It returns the number, and the remainder of the string, and reports success. +// The number must be between min and max. +func tzsetNum(s string, min, max int) (num int, rest string, ok bool) { + if len(s) == 0 { + return 0, "", false + } + num = 0 + for i, r := range s { + if r < '0' || r > '9' { + if i == 0 || num < min { + return 0, "", false + } + return num, s[i:], true + } + num *= 10 + num += int(r) - '0' + if num > max { + return 0, "", false + } + } + if num < min { + return 0, "", false + } + return num, "", true +} + +// tzruleTime takes a year, a rule, and a timezone offset, +// and returns the number of seconds since the start of the year +// that the rule takes effect. +func tzruleTime(year int, r rule, off int) int { + var s int + switch r.kind { + case ruleJulian: + s = (r.day - 1) * secondsPerDay + if isLeap(year) && r.day >= 60 { + s += secondsPerDay + } + case ruleDOY: + s = r.day * secondsPerDay + case ruleMonthWeekDay: + // Zeller's Congruence. + m1 := (r.mon+9)%12 + 1 + yy0 := year + if r.mon <= 2 { + yy0-- + } + yy1 := yy0 / 100 + yy2 := yy0 % 100 + dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7 + if dow < 0 { + dow += 7 + } + // Now dow is the day-of-week of the first day of r.mon. + // Get the day-of-month of the first "dow" day. + d := r.day - dow + if d < 0 { + d += 7 + } + for i := 1; i < r.week; i++ { + if d+7 >= daysIn(Month(r.mon), year) { + break + } + d += 7 + } + d += int(daysBefore[r.mon-1]) + if isLeap(year) && r.mon > 2 { + d++ + } + s = d * secondsPerDay + } + + return s + r.time - off +} + +// lookupName returns information about the time zone with +// the given name (such as "EST") at the given pseudo-Unix time +// (what the given time of day would be in UTC). +func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) { + l = l.get() + + // First try for a zone with the right name that was actually + // in effect at the given time. (In Sydney, Australia, both standard + // and daylight-savings time are abbreviated "EST". Using the + // offset helps us pick the right one for the given time. + // It's not perfect: during the backward transition we might pick + // either one.) + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset)) + if nam == zone.name { + return offset, true + } + } + } + + // Otherwise fall back to an ordinary name match. + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + return zone.offset, true + } + } + + // Otherwise, give up. + return +} + +// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment +// syntax too, but I don't feel like implementing it today. + +var errLocation = errors.New("time: invalid location name") + +var zoneinfo *string + +// XXX var zoneinfoOnce sync.Once + +// LoadLocation returns the Location with the given name. +// +// If the name is "" or "UTC", LoadLocation returns UTC. +// If the name is "Local", LoadLocation returns Local. +// +// Otherwise, the name is taken to be a location name corresponding to a file +// in the IANA Time Zone database, such as "America/New_York". +// +// LoadLocation looks for the IANA Time Zone database in the following +// locations in order: +// +// - the directory or uncompressed zip file named by the ZONEINFO environment variable +// - on a Unix system, the system standard installation location +// - $GOROOT/lib/time/zoneinfo.zip +// - the time/tzdata package, if it was imported +func LoadLocation(name string) (*Location, error) { + panic("XXX LoadLocation not yet implemented") + /* + if name == "" || name == "UTC" { + return UTC, nil + } + if name == "Local" { + return Local, nil + } + if containsDotDot(name) || name[0] == '/' || name[0] == '\\' { + // No valid IANA Time Zone name contains a single dot, + // much less dot dot. Likewise, none begin with a slash. + return nil, errLocation + } + zoneinfoOnce.Do(func() { + env, _ := syscall.Getenv("ZONEINFO") + zoneinfo = &env + }) + var firstErr error + if *zoneinfo != "" { + if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil { + if z, err := LoadLocationFromTZData(name, zoneData); err == nil { + return z, nil + } + firstErr = err + } else if err != syscall.ENOENT { + firstErr = err + } + } + if z, err := loadLocation(name, platformZoneSources); err == nil { + return z, nil + } else if firstErr == nil { + firstErr = err + } + return nil, firstErr + */ +} + +// containsDotDot reports whether s contains "..". +func containsDotDot(s string) bool { + if len(s) < 2 { + return false + } + for i := 0; i < len(s)-1; i++ { + if s[i] == '.' && s[i+1] == '.' { + return true + } + } + return false +} diff --git a/tests/file.go b/tests/file.go index 8ee9b957b94..c722c21fd57 100644 --- a/tests/file.go +++ b/tests/file.go @@ -41,6 +41,7 @@ func testMachineCustom(store gno.Store, pkgPath string, stdout io.Writer, maxAll ctx := stdlibs.ExecContext{ ChainID: "dev", Height: 123, + Timestamp: 1234567890, Msg: nil, OrigCaller: caller.Bech32(), OrigPkgAddr: pkgAddr.Bech32(), @@ -67,11 +68,13 @@ func RunFileTest(rootDir string, path string, nativeLibs bool, logger loggerFunc stdin := new(bytes.Buffer) stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - filesPath := "./files" + filesPath := "./files2" + mode := ImportModeStdlibsPreferred if nativeLibs { - filesPath = "./files2" + filesPath = "./files" + mode = ImportModeNativePreferred } - store := TestStore(rootDir, filesPath, stdin, stdout, stderr, nativeLibs) + store := TestStore(rootDir, filesPath, stdin, stdout, stderr, mode) store.SetLogStoreOps(true) m := testMachineCustom(store, pkgPath, stdout, maxAlloc, send) diff --git a/tests/files/float0.gno b/tests/files/float0.gno new file mode 100644 index 00000000000..07a6db74550 --- /dev/null +++ b/tests/files/float0.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := int(1.2) + println(x) +} + +// Error: +// main/files/float0.gno:4: cannot convert (const (1.2 bigdec)) to integer type diff --git a/tests/files/float1.gno b/tests/files/float1.gno new file mode 100644 index 00000000000..9eaed64e063 --- /dev/null +++ b/tests/files/float1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := int(float64(1.2)) + println(x) +} + +// Output: +// 1 diff --git a/tests/files/float2.gno b/tests/files/float2.gno new file mode 100644 index 00000000000..7c415d256dc --- /dev/null +++ b/tests/files/float2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := float64(0x1p-52) + println(x) +} + +// Output: +// 2.220446049250313e-16 diff --git a/tests/files/float3.gno b/tests/files/float3.gno new file mode 100644 index 00000000000..0508dcb418a --- /dev/null +++ b/tests/files/float3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := float64(0x1p0 - 0x1p-52) + println(x) +} + +// Output: +// 0.9999999999999998 diff --git a/tests/files/float4.gno b/tests/files/float4.gno new file mode 100644 index 00000000000..e69fc4072bf --- /dev/null +++ b/tests/files/float4.gno @@ -0,0 +1,12 @@ +package main + +import "math" + +func main() { + println(math.MaxFloat32) + println(math.MaxFloat64) +} + +// Output: +// 3.4028234663852886e+38 +// 1.7976931348623157e+308 diff --git a/tests/files/std1.gno b/tests/files/std1.gno index e882c22c699..0326dce5e81 100644 --- a/tests/files/std1.gno +++ b/tests/files/std1.gno @@ -9,4 +9,4 @@ func main() { } // Output: -// Jan 1, 1970 at 12:00am (UTC) +// Feb 13, 2009 at 11:31pm (UTC) diff --git a/tests/files/stdlibs.gno b/tests/files/stdlibs.gno new file mode 100644 index 00000000000..3afd9f81519 --- /dev/null +++ b/tests/files/stdlibs.gno @@ -0,0 +1,15 @@ +package main + +import "time" + +func main() { + println(time.UTC == nil) + time.UTC = nil + println(time.UTC == nil) + println("done") +} + +// Output: +// false +// true +// done diff --git a/tests/files/switch40.gno b/tests/files/switch40.gno new file mode 100644 index 00000000000..ff405ebf019 --- /dev/null +++ b/tests/files/switch40.gno @@ -0,0 +1,16 @@ +package main + +func main() { + a := 1 + switch c := int16(a); c { + case 0: + panic("should not happen") + case 1: + println("done") + default: + panic("should not happen") + } +} + +// Output: +// done diff --git a/tests/files2/assign0b.gno b/tests/files2/assign0b.gno index 3374dc76ff4..0467faff838 100644 --- a/tests/files2/assign0b.gno +++ b/tests/files2/assign0b.gno @@ -14,5 +14,5 @@ func main() { } // Output: -// &{ 10s} -// &{ 0s} +// &{ 10000000000} +// &{ 0} diff --git a/tests/files2/float0.gno b/tests/files2/float0.gno new file mode 100644 index 00000000000..221e0c2f1c9 --- /dev/null +++ b/tests/files2/float0.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := int(1.2) + println(x) +} + +// Error: +// main/files2/float0.gno:4: cannot convert (const (1.2 bigdec)) to integer type diff --git a/tests/files2/float1.gno b/tests/files2/float1.gno new file mode 100644 index 00000000000..9eaed64e063 --- /dev/null +++ b/tests/files2/float1.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := int(float64(1.2)) + println(x) +} + +// Output: +// 1 diff --git a/tests/files2/float2.gno b/tests/files2/float2.gno new file mode 100644 index 00000000000..7c415d256dc --- /dev/null +++ b/tests/files2/float2.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := float64(0x1p-52) + println(x) +} + +// Output: +// 2.220446049250313e-16 diff --git a/tests/files2/float3.gno b/tests/files2/float3.gno new file mode 100644 index 00000000000..0508dcb418a --- /dev/null +++ b/tests/files2/float3.gno @@ -0,0 +1,9 @@ +package main + +func main() { + x := float64(0x1p0 - 0x1p-52) + println(x) +} + +// Output: +// 0.9999999999999998 diff --git a/tests/files2/float4.gno b/tests/files2/float4.gno new file mode 100644 index 00000000000..e69fc4072bf --- /dev/null +++ b/tests/files2/float4.gno @@ -0,0 +1,12 @@ +package main + +import "math" + +func main() { + println(math.MaxFloat32) + println(math.MaxFloat64) +} + +// Output: +// 3.4028234663852886e+38 +// 1.7976931348623157e+308 diff --git a/tests/files2/float5.gno b/tests/files2/float5.gno new file mode 100644 index 00000000000..df31cb4063b --- /dev/null +++ b/tests/files2/float5.gno @@ -0,0 +1,18 @@ +package main + +import ( + imath "internal/math" + "math" +) + +func main() { + println(math.MaxFloat32, imath.Float32bits(math.MaxFloat32)) + println(math.MaxFloat64, imath.Float64bits(math.MaxFloat64)) +} + +// Output: +// 3.4028234663852886e+38 2139095039 +// 1.7976931348623157e+308 9218868437227405311 + +// NOTE: 0x7f7fffff is 2139095039 +// NOTE: 0x7fefffffffffffff is 9218868437227405311 diff --git a/tests/files2/map29.gno b/tests/files2/map29.gno index b4a4129cd39..d33540715da 100644 --- a/tests/files2/map29.gno +++ b/tests/files2/map29.gno @@ -23,4 +23,4 @@ func main() { } // Output: -// {test 1s} +// {test 1000000000} diff --git a/tests/files2/std1.gno b/tests/files2/std1.gno index e882c22c699..0326dce5e81 100644 --- a/tests/files2/std1.gno +++ b/tests/files2/std1.gno @@ -9,4 +9,4 @@ func main() { } // Output: -// Jan 1, 1970 at 12:00am (UTC) +// Feb 13, 2009 at 11:31pm (UTC) diff --git a/tests/files2/stdlibs.gno b/tests/files2/stdlibs.gno new file mode 100644 index 00000000000..261e7fbe78c --- /dev/null +++ b/tests/files2/stdlibs.gno @@ -0,0 +1,14 @@ +// PKGPATH: gno.land/r/test +package test + +import "time" + +func main() { + println(time.UTC == nil) + time.UTC = nil + println(time.UTC == nil) + println("done") +} + +// Error: +// cannot modify external-realm or non-realm object diff --git a/tests/files2/struct13.gno b/tests/files2/struct13.gno index 3e197b04645..4cfb5bd1152 100644 --- a/tests/files2/struct13.gno +++ b/tests/files2/struct13.gno @@ -15,4 +15,4 @@ func main() { } // Output: -// 0s +// 0 diff --git a/tests/files2/time0.gno b/tests/files2/time0.gno index 52c5b4d6727..d9d0aed4b54 100644 --- a/tests/files2/time0.gno +++ b/tests/files2/time0.gno @@ -1,13 +1,12 @@ package main import ( - "fmt" "time" ) func main() { - fmt.Println(time.Now()) + println(time.Now()) } // Output: -// 1970-01-01 00:00:00 +0000 UTC +// 2009-02-13 23:31:30 +0000 UTC m=+0.000000001 diff --git a/tests/files2/time1.gno b/tests/files2/time1.gno index 9749d472e08..2250bb5bdaf 100644 --- a/tests/files2/time1.gno +++ b/tests/files2/time1.gno @@ -1,14 +1,13 @@ package main import ( - "fmt" "time" ) func main() { t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) m := t.Minute() - fmt.Println(t, m) + println(t, m) } // Output: diff --git a/tests/files2/time11.gno b/tests/files2/time11.gno index 641ab4e6e4d..9f6126fe87b 100644 --- a/tests/files2/time11.gno +++ b/tests/files2/time11.gno @@ -8,8 +8,10 @@ import ( const df = time.Minute * 30 func main() { + println(df) fmt.Printf("df: %v %T\n", df, df) } // Output: -// df: 30m0s time.Duration +// 30m0s +// df: 1800000000000 int64 diff --git a/tests/files2/time12.gno b/tests/files2/time12.gno index 890e49cd1f0..21e0a755118 100644 --- a/tests/files2/time12.gno +++ b/tests/files2/time12.gno @@ -1,14 +1,13 @@ package main import ( - "fmt" "time" ) var twentyFourHours = time.Duration(24 * time.Hour) func main() { - fmt.Println(twentyFourHours.Hours()) + println(twentyFourHours.Hours()) } // Output: diff --git a/tests/files2/time13.gno b/tests/files2/time13.gno index a2eedafe880..1c2d1d868d1 100644 --- a/tests/files2/time13.gno +++ b/tests/files2/time13.gno @@ -1,7 +1,6 @@ package main import ( - "fmt" "time" ) @@ -11,7 +10,7 @@ var t time.Time = time.Date(2007, time.November, 10, 23, 4, 5, 0, time.UTC) func main() { t = time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) - fmt.Println(t.Clock()) + println(t.Clock()) } // Output: diff --git a/tests/files2/time14.gno b/tests/files2/time14.gno index 9f28c57d006..c1fdf91c0aa 100644 --- a/tests/files2/time14.gno +++ b/tests/files2/time14.gno @@ -1,7 +1,6 @@ package main import ( - "fmt" "time" ) @@ -13,7 +12,7 @@ func f() time.Time { } func main() { - fmt.Println(f()) + println(f()) } // Output: diff --git a/tests/files2/time2.gno b/tests/files2/time2.gno index 03ea3a2be96..054a67c18d8 100644 --- a/tests/files2/time2.gno +++ b/tests/files2/time2.gno @@ -1,14 +1,13 @@ package main import ( - "fmt" "time" ) func main() { t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) h, m, s := t.Clock() - fmt.Println(h, m, s) + println(h, m, s) } // Output: diff --git a/tests/files2/time3.gno b/tests/files2/time3.gno index 0848abd9a13..b2d0dc5b123 100644 --- a/tests/files2/time3.gno +++ b/tests/files2/time3.gno @@ -1,14 +1,13 @@ package main import ( - "fmt" "time" ) // FIXME related to named returns func main() { t := time.Date(2009, time.November, 10, 23, 4, 5, 0, time.UTC) - fmt.Println(t.Clock()) + println(t.Clock()) } // Output: diff --git a/tests/files2/time4.gno b/tests/files2/time4.gno index 3662e35cb01..4ab9214a5bd 100644 --- a/tests/files2/time4.gno +++ b/tests/files2/time4.gno @@ -1,14 +1,13 @@ package main import ( - "fmt" "time" ) func main() { var m time.Month m = 9 - fmt.Println(m) + println(m) } // Output: diff --git a/tests/files2/time6.gno b/tests/files2/time6.gno index c88d3ab8115..a6fce53a815 100644 --- a/tests/files2/time6.gno +++ b/tests/files2/time6.gno @@ -1,7 +1,6 @@ package main import ( - "fmt" "time" ) @@ -9,7 +8,7 @@ func main() { t := &time.Time{} t.UnmarshalText([]byte("1985-04-12T23:20:50.52Z")) - fmt.Println(t) + println(t) } // Output: diff --git a/tests/files2/time7.gno b/tests/files2/time7.gno index 1e02defc80d..1d8e5cd5ada 100644 --- a/tests/files2/time7.gno +++ b/tests/files2/time7.gno @@ -1,13 +1,12 @@ package main import ( - "fmt" "time" ) var d = 2 * time.Second -func main() { fmt.Println(d) } +func main() { println(d) } // Output: // 2s diff --git a/tests/files2/time9.gno b/tests/files2/time9.gno index a87b4560d1a..fe2bde85dac 100644 --- a/tests/files2/time9.gno +++ b/tests/files2/time9.gno @@ -1,12 +1,11 @@ package main import ( - "fmt" "time" ) func main() { - fmt.Println((5 * time.Minute).Seconds()) + println((5 * time.Minute).Seconds()) } // Output: diff --git a/tests/files2/type2.gno b/tests/files2/type2.gno index 453fd4b64e7..ea40d6cf359 100644 --- a/tests/files2/type2.gno +++ b/tests/files2/type2.gno @@ -1,7 +1,6 @@ package main import ( - "fmt" "time" ) @@ -17,7 +16,7 @@ type T1 struct { func main() { t := T1{} t.time = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) - fmt.Println(t.time) + println(t.time) } // Output: diff --git a/tests/imports.go b/tests/imports.go index 44c81c04175..cbc95a27e0c 100644 --- a/tests/imports.go +++ b/tests/imports.go @@ -50,12 +50,28 @@ import ( "github.com/gnolang/gno/stdlibs" ) -// NOTE: this isn't safe. -func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, nativeLibs bool) (store gno.Store) { +type importMode uint64 + +const ( + ImportModeStdlibsOnly importMode = iota + ImportModeStdlibsPreferred + ImportModeNativePreferred +) + +// ImportModeStdlibsOnly: use stdlibs/* only (except fmt). for stdlibs/* and examples/* testing. +// ImportModeStdlibsPreferred: use stdlibs/* if present, otherwise use native. for files/tests2/*. +// ImportModeNativePreferred: do not use stdlibs/* if native registered. for files/tests/*. +// NOTE: this isn't safe, should only be used for testing. +func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { if pkgPath == "" { panic(fmt.Sprintf("invalid zero package path in testStore().pkgGetter")) } + if mode != ImportModeStdlibsOnly && + mode != ImportModeStdlibsPreferred && + mode != ImportModeNativePreferred { + panic(fmt.Sprintf("unrecognized import mode")) + } if filesPath != "" { // if _test package... @@ -75,93 +91,119 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri } } - // TODO: if isRealm, can we panic here? - // otherwise, built-in package value. - switch pkgPath { - case "os": - pkg := gno.NewPackageNode("os", pkgPath, nil) - pkg.DefineGoNativeValue("Stdin", stdin) - pkg.DefineGoNativeValue("Stdout", stdout) - pkg.DefineGoNativeValue("Stderr", stderr) - return pkg, pkg.NewPackage() - case "fmt": - pkg := gno.NewPackageNode("fmt", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) - pkg.DefineGoNativeType(reflect.TypeOf((*fmt.Formatter)(nil)).Elem()) - pkg.DefineGoNativeValue("Println", func(a ...interface{}) (n int, err error) { - // NOTE: uncomment to debug long running tests - fmt.Println(a...) - res := fmt.Sprintln(a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Printf", func(format string, a ...interface{}) (n int, err error) { - res := fmt.Sprintf(format, a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Print", func(a ...interface{}) (n int, err error) { - res := fmt.Sprint(a...) - return stdout.Write([]byte(res)) - }) - pkg.DefineGoNativeValue("Sprint", fmt.Sprint) - pkg.DefineGoNativeValue("Sprintf", fmt.Sprintf) - pkg.DefineGoNativeValue("Sprintln", fmt.Sprintln) - pkg.DefineGoNativeValue("Sscanf", fmt.Sscanf) - pkg.DefineGoNativeValue("Errorf", fmt.Errorf) - pkg.DefineGoNativeValue("Fprintln", fmt.Fprintln) - pkg.DefineGoNativeValue("Fprintf", fmt.Fprintf) - pkg.DefineGoNativeValue("Fprint", fmt.Fprint) - return pkg, pkg.NewPackage() - case "encoding/base64": - if nativeLibs { + // if stdlibs package is preferred , try to load it first. + if mode == ImportModeStdlibsOnly || + mode == ImportModeStdlibsPreferred { + stdlibPath := filepath.Join(rootDir, "stdlibs", pkgPath) + if osm.DirExists(stdlibPath) { + memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + // NOTE: see also pkgs/sdk/vm/builtins.go + // XXX: why does this fail when just pkgPath? + PkgPath: "gno.land/r/stdlibs/" + pkgPath, + Output: stdout, + Store: store, + }) + return m2.RunMemPackage(memPkg, true) + } + } + + // if native package is allowed, return it. + if pkgPath == "os" || // special cases even when StdlibsOnly (for tests). + pkgPath == "fmt" || // TODO: try to minimize these exceptions over time. + pkgPath == "log" || + pkgPath == "crypto/rand" || + pkgPath == "crypto/md5" || + pkgPath == "crypto/sha1" || + pkgPath == "encoding/base64" || + pkgPath == "encoding/binary" || + pkgPath == "encoding/json" || + pkgPath == "encoding/xml" || + pkgPath == "math" || + pkgPath == "math/big" || + pkgPath == "math/rand" || + mode == ImportModeStdlibsPreferred || + mode == ImportModeNativePreferred { + switch pkgPath { + case "os": + pkg := gno.NewPackageNode("os", pkgPath, nil) + pkg.DefineGoNativeValue("Stdin", stdin) + pkg.DefineGoNativeValue("Stdout", stdout) + pkg.DefineGoNativeValue("Stderr", stderr) + return pkg, pkg.NewPackage() + case "fmt": + pkg := gno.NewPackageNode("fmt", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) + pkg.DefineGoNativeType(reflect.TypeOf((*fmt.Formatter)(nil)).Elem()) + pkg.DefineGoNativeValue("Println", func(a ...interface{}) (n int, err error) { + // NOTE: uncomment to debug long running tests + fmt.Println(a...) + res := fmt.Sprintln(a...) + return stdout.Write([]byte(res)) + }) + pkg.DefineGoNativeValue("Printf", func(format string, a ...interface{}) (n int, err error) { + res := fmt.Sprintf(format, a...) + return stdout.Write([]byte(res)) + }) + pkg.DefineGoNativeValue("Print", func(a ...interface{}) (n int, err error) { + res := fmt.Sprint(a...) + return stdout.Write([]byte(res)) + }) + pkg.DefineGoNativeValue("Sprint", fmt.Sprint) + pkg.DefineGoNativeValue("Sprintf", fmt.Sprintf) + pkg.DefineGoNativeValue("Sprintln", fmt.Sprintln) + pkg.DefineGoNativeValue("Sscanf", fmt.Sscanf) + pkg.DefineGoNativeValue("Errorf", fmt.Errorf) + pkg.DefineGoNativeValue("Fprintln", fmt.Fprintln) + pkg.DefineGoNativeValue("Fprintf", fmt.Fprintf) + pkg.DefineGoNativeValue("Fprint", fmt.Fprint) + return pkg, pkg.NewPackage() + case "encoding/base64": pkg := gno.NewPackageNode("base64", pkgPath, nil) pkg.DefineGoNativeValue("RawStdEncoding", base64.RawStdEncoding) pkg.DefineGoNativeValue("StdEncoding", base64.StdEncoding) pkg.DefineGoNativeValue("NewDecoder", base64.NewDecoder) return pkg, pkg.NewPackage() - } - case "encoding/binary": - pkg := gno.NewPackageNode("binary", pkgPath, nil) - pkg.DefineGoNativeValue("LittleEndian", binary.LittleEndian) - pkg.DefineGoNativeValue("BigEndian", binary.BigEndian) - return pkg, pkg.NewPackage() - case "encoding/json": - pkg := gno.NewPackageNode("json", pkgPath, nil) - pkg.DefineGoNativeValue("Unmarshal", json.Unmarshal) - pkg.DefineGoNativeValue("Marshal", json.Marshal) - return pkg, pkg.NewPackage() - case "encoding/xml": - pkg := gno.NewPackageNode("xml", pkgPath, nil) - pkg.DefineGoNativeValue("Unmarshal", xml.Unmarshal) - return pkg, pkg.NewPackage() - case "net": - pkg := gno.NewPackageNode("net", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(net.TCPAddr{})) - pkg.DefineGoNativeValue("IPv4", net.IPv4) - return pkg, pkg.NewPackage() - case "net/http": - // XXX UNSAFE - // There's no reason why we can't replace these with safer alternatives. - panic("just say gno") - /* - pkg := gno.NewPackageNode("http", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(http.Request{})) - pkg.DefineGoNativeValue("DefaultClient", http.DefaultClient) - pkg.DefineGoNativeType(reflect.TypeOf(http.Client{})) + case "encoding/binary": + pkg := gno.NewPackageNode("binary", pkgPath, nil) + pkg.DefineGoNativeValue("LittleEndian", binary.LittleEndian) + pkg.DefineGoNativeValue("BigEndian", binary.BigEndian) return pkg, pkg.NewPackage() - */ - case "net/url": - pkg := gno.NewPackageNode("url", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(url.Values{})) - return pkg, pkg.NewPackage() - case "bufio": - if nativeLibs { + case "encoding/json": + pkg := gno.NewPackageNode("json", pkgPath, nil) + pkg.DefineGoNativeValue("Unmarshal", json.Unmarshal) + pkg.DefineGoNativeValue("Marshal", json.Marshal) + return pkg, pkg.NewPackage() + case "encoding/xml": + pkg := gno.NewPackageNode("xml", pkgPath, nil) + pkg.DefineGoNativeValue("Unmarshal", xml.Unmarshal) + return pkg, pkg.NewPackage() + case "net": + pkg := gno.NewPackageNode("net", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(net.TCPAddr{})) + pkg.DefineGoNativeValue("IPv4", net.IPv4) + return pkg, pkg.NewPackage() + case "net/http": + // XXX UNSAFE + // There's no reason why we can't replace these with safer alternatives. + panic("just say gno") + /* + pkg := gno.NewPackageNode("http", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(http.Request{})) + pkg.DefineGoNativeValue("DefaultClient", http.DefaultClient) + pkg.DefineGoNativeType(reflect.TypeOf(http.Client{})) + return pkg, pkg.NewPackage() + */ + case "net/url": + pkg := gno.NewPackageNode("url", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(url.Values{})) + return pkg, pkg.NewPackage() + case "bufio": pkg := gno.NewPackageNode("bufio", pkgPath, nil) pkg.DefineGoNativeValue("NewScanner", bufio.NewScanner) pkg.DefineGoNativeType(reflect.TypeOf(bufio.SplitFunc(nil))) return pkg, pkg.NewPackage() - } - case "bytes": - if nativeLibs { + case "bytes": pkg := gno.NewPackageNode("bytes", pkgPath, nil) pkg.DefineGoNativeValue("Equal", bytes.Equal) pkg.DefineGoNativeValue("Compare", bytes.Compare) @@ -170,23 +212,22 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeValue("Repeat", bytes.Repeat) pkg.DefineGoNativeType(reflect.TypeOf(bytes.Buffer{})) return pkg, pkg.NewPackage() - } - case "time": - pkg := gno.NewPackageNode("time", pkgPath, nil) - pkg.DefineGoNativeValue("Date", time.Date) - pkg.DefineGoNativeValue("Second", time.Second) - pkg.DefineGoNativeValue("Minute", time.Minute) - pkg.DefineGoNativeValue("Hour", time.Hour) - pkg.DefineGoNativeValue("Now", func() time.Time { return time.Unix(0, 0).UTC() }) // deterministic - pkg.DefineGoNativeValue("November", time.November) - pkg.DefineGoNativeValue("UTC", time.UTC) - pkg.DefineGoNativeValue("Unix", time.Unix) - pkg.DefineGoNativeType(reflect.TypeOf(time.Time{})) - pkg.DefineGoNativeType(reflect.TypeOf(time.Month(0))) - pkg.DefineGoNativeType(reflect.TypeOf(time.Duration(0))) - return pkg, pkg.NewPackage() - case "strings": - if nativeLibs { + case "time": + pkg := gno.NewPackageNode("time", pkgPath, nil) + pkg.DefineGoNativeValue("Millisecond", time.Millisecond) + pkg.DefineGoNativeValue("Second", time.Second) + pkg.DefineGoNativeValue("Minute", time.Minute) + pkg.DefineGoNativeValue("Hour", time.Hour) + pkg.DefineGoNativeValue("Date", time.Date) + pkg.DefineGoNativeValue("Now", func() time.Time { return time.Unix(0, 0).UTC() }) // deterministic + pkg.DefineGoNativeValue("November", time.November) + pkg.DefineGoNativeValue("UTC", time.UTC) + pkg.DefineGoNativeValue("Unix", time.Unix) + pkg.DefineGoNativeType(reflect.TypeOf(time.Time{})) + pkg.DefineGoNativeType(reflect.TypeOf(time.Duration(0))) + pkg.DefineGoNativeType(reflect.TypeOf(time.Month(0))) + return pkg, pkg.NewPackage() + case "strings": pkg := gno.NewPackageNode("strings", pkgPath, nil) pkg.DefineGoNativeValue("Split", strings.Split) pkg.DefineGoNativeValue("SplitN", strings.SplitN) @@ -199,92 +240,87 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeValue("Join", strings.Join) pkg.DefineGoNativeType(reflect.TypeOf(strings.Builder{})) return pkg, pkg.NewPackage() - } - case "math": - if nativeLibs { + case "math": pkg := gno.NewPackageNode("math", pkgPath, nil) pkg.DefineGoNativeValue("Abs", math.Abs) pkg.DefineGoNativeValue("Cos", math.Cos) pkg.DefineGoNativeValue("Pi", math.Pi) pkg.DefineGoNativeValue("MaxFloat32", math.MaxFloat32) + pkg.DefineGoNativeValue("MaxFloat64", math.MaxFloat64) return pkg, pkg.NewPackage() - } - case "math/rand": - // XXX only expose for tests. - pkg := gno.NewPackageNode("rand", pkgPath, nil) - pkg.DefineGoNativeValue("Intn", rand.Intn) - pkg.DefineGoNativeValue("Uint32", rand.Uint32) - pkg.DefineGoNativeValue("Seed", rand.Seed) - pkg.DefineGoNativeValue("New", rand.New) - pkg.DefineGoNativeValue("NewSource", rand.NewSource) - pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) - return pkg, pkg.NewPackage() - case "crypto/rand": - pkg := gno.NewPackageNode("rand", pkgPath, nil) - pkg.DefineGoNativeValue("Prime", crand.Prime) - // for determinism: - // pkg.DefineGoNativeValue("Reader", crand.Reader) - pkg.DefineGoNativeValue("Reader", &dummyReader{}) - return pkg, pkg.NewPackage() - case "crypto/md5": - pkg := gno.NewPackageNode("md5", pkgPath, nil) - pkg.DefineGoNativeValue("New", md5.New) - return pkg, pkg.NewPackage() - case "crypto/sha1": - pkg := gno.NewPackageNode("sha1", pkgPath, nil) - pkg.DefineGoNativeValue("New", sha1.New) - return pkg, pkg.NewPackage() - case "image": - pkg := gno.NewPackageNode("image", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(image.Point{})) - return pkg, pkg.NewPackage() - case "image/color": - pkg := gno.NewPackageNode("color", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(color.NRGBA64{})) - return pkg, pkg.NewPackage() - case "compress/flate": - pkg := gno.NewPackageNode("flate", pkgPath, nil) - pkg.DefineGoNativeValue("BestSpeed", flate.BestSpeed) - return pkg, pkg.NewPackage() - case "compress/gzip": - pkg := gno.NewPackageNode("gzip", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(gzip.Writer{})) - pkg.DefineGoNativeValue("BestCompression", gzip.BestCompression) - pkg.DefineGoNativeValue("BestSpeed", gzip.BestSpeed) - return pkg, pkg.NewPackage() - case "context": - pkg := gno.NewPackageNode("context", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf((*context.Context)(nil)).Elem()) - pkg.DefineGoNativeValue("WithValue", context.WithValue) - pkg.DefineGoNativeValue("Background", context.Background) - return pkg, pkg.NewPackage() - case "sync": - pkg := gno.NewPackageNode("sync", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(sync.Mutex{})) - pkg.DefineGoNativeType(reflect.TypeOf(sync.RWMutex{})) - pkg.DefineGoNativeType(reflect.TypeOf(sync.Pool{})) - return pkg, pkg.NewPackage() - case "sync/atomic": - pkg := gno.NewPackageNode("atomic", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(atomic.Value{})) - return pkg, pkg.NewPackage() - case "math/big": - pkg := gno.NewPackageNode("big", pkgPath, nil) - pkg.DefineGoNativeValue("NewInt", big.NewInt) - return pkg, pkg.NewPackage() - case "sort": - if nativeLibs { + case "math/rand": + // XXX only expose for tests. + pkg := gno.NewPackageNode("rand", pkgPath, nil) + pkg.DefineGoNativeValue("Intn", rand.Intn) + pkg.DefineGoNativeValue("Uint32", rand.Uint32) + pkg.DefineGoNativeValue("Seed", rand.Seed) + pkg.DefineGoNativeValue("New", rand.New) + pkg.DefineGoNativeValue("NewSource", rand.NewSource) + pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) + return pkg, pkg.NewPackage() + case "crypto/rand": + pkg := gno.NewPackageNode("rand", pkgPath, nil) + pkg.DefineGoNativeValue("Prime", crand.Prime) + // for determinism: + // pkg.DefineGoNativeValue("Reader", crand.Reader) + pkg.DefineGoNativeValue("Reader", &dummyReader{}) + return pkg, pkg.NewPackage() + case "crypto/md5": + pkg := gno.NewPackageNode("md5", pkgPath, nil) + pkg.DefineGoNativeValue("New", md5.New) + return pkg, pkg.NewPackage() + case "crypto/sha1": + pkg := gno.NewPackageNode("sha1", pkgPath, nil) + pkg.DefineGoNativeValue("New", sha1.New) + return pkg, pkg.NewPackage() + case "image": + pkg := gno.NewPackageNode("image", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(image.Point{})) + return pkg, pkg.NewPackage() + case "image/color": + pkg := gno.NewPackageNode("color", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(color.NRGBA64{})) + return pkg, pkg.NewPackage() + case "compress/flate": + pkg := gno.NewPackageNode("flate", pkgPath, nil) + pkg.DefineGoNativeValue("BestSpeed", flate.BestSpeed) + return pkg, pkg.NewPackage() + case "compress/gzip": + pkg := gno.NewPackageNode("gzip", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(gzip.Writer{})) + pkg.DefineGoNativeValue("BestCompression", gzip.BestCompression) + pkg.DefineGoNativeValue("BestSpeed", gzip.BestSpeed) + return pkg, pkg.NewPackage() + case "context": + pkg := gno.NewPackageNode("context", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf((*context.Context)(nil)).Elem()) + pkg.DefineGoNativeValue("WithValue", context.WithValue) + pkg.DefineGoNativeValue("Background", context.Background) + return pkg, pkg.NewPackage() + case "sync": + pkg := gno.NewPackageNode("sync", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(sync.Mutex{})) + pkg.DefineGoNativeType(reflect.TypeOf(sync.RWMutex{})) + pkg.DefineGoNativeType(reflect.TypeOf(sync.Pool{})) + return pkg, pkg.NewPackage() + case "sync/atomic": + pkg := gno.NewPackageNode("atomic", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(atomic.Value{})) + return pkg, pkg.NewPackage() + case "math/big": + pkg := gno.NewPackageNode("big", pkgPath, nil) + pkg.DefineGoNativeValue("NewInt", big.NewInt) + return pkg, pkg.NewPackage() + case "sort": pkg := gno.NewPackageNode("sort", pkgPath, nil) pkg.DefineGoNativeValue("Strings", sort.Strings) // pkg.DefineGoNativeValue("Sort", sort.Sort) return pkg, pkg.NewPackage() - } - case "flag": - pkg := gno.NewPackageNode("flag", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(flag.Flag{})) - return pkg, pkg.NewPackage() - case "io": - if nativeLibs { + case "flag": + pkg := gno.NewPackageNode("flag", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(flag.Flag{})) + return pkg, pkg.NewPackage() + case "io": pkg := gno.NewPackageNode("io", pkgPath, nil) pkg.DefineGoNativeValue("EOF", io.EOF) pkg.DefineGoNativeValue("ReadFull", io.ReadFull) @@ -292,74 +328,73 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeType(reflect.TypeOf((*io.Closer)(nil)).Elem()) pkg.DefineGoNativeType(reflect.TypeOf((*io.Reader)(nil)).Elem()) return pkg, pkg.NewPackage() - } - case "io/ioutil": - if nativeLibs { + case "io/ioutil": pkg := gno.NewPackageNode("ioutil", pkgPath, nil) pkg.DefineGoNativeValue("NopCloser", ioutil.NopCloser) pkg.DefineGoNativeValue("ReadAll", ioutil.ReadAll) return pkg, pkg.NewPackage() - } - case "log": - pkg := gno.NewPackageNode("log", pkgPath, nil) - pkg.DefineGoNativeValue("Fatal", log.Fatal) - return pkg, pkg.NewPackage() - case "text/template": - pkg := gno.NewPackageNode("template", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(template.FuncMap{})) - return pkg, pkg.NewPackage() - case "unicode/utf8": - if nativeLibs { + case "log": + pkg := gno.NewPackageNode("log", pkgPath, nil) + pkg.DefineGoNativeValue("Fatal", log.Fatal) + return pkg, pkg.NewPackage() + case "text/template": + pkg := gno.NewPackageNode("template", pkgPath, nil) + pkg.DefineGoNativeType(reflect.TypeOf(template.FuncMap{})) + return pkg, pkg.NewPackage() + case "unicode/utf8": pkg := gno.NewPackageNode("utf8", pkgPath, nil) pkg.DefineGoNativeValue("DecodeRuneInString", utf8.DecodeRuneInString) tv := gno.TypedValue{T: gno.UntypedRuneType} // TODO dry tv.SetInt32(utf8.RuneSelf) // .. pkg.Define("RuneSelf", tv) // .. return pkg, pkg.NewPackage() - } - case "errors": - if nativeLibs { + case "errors": pkg := gno.NewPackageNode("errors", pkgPath, nil) pkg.DefineGoNativeValue("New", errors.New) return pkg, pkg.NewPackage() + case "hash/fnv": + pkg := gno.NewPackageNode("fnv", pkgPath, nil) + pkg.DefineGoNativeValue("New32a", fnv.New32a) + return pkg, pkg.NewPackage() + /* XXX support somehow for speed. for now, generic implemented in stdlibs. + case "internal/bytealg": + pkg := gno.NewPackageNode("bytealg", pkgPath, nil) + pkg.DefineGoNativeValue("Compare", bytealg.Compare) + pkg.DefineGoNativeValue("CountString", bytealg.CountString) + pkg.DefineGoNativeValue("Cutover", bytealg.Cutover) + pkg.DefineGoNativeValue("Equal", bytealg.Equal) + pkg.DefineGoNativeValue("HashStr", bytealg.HashStr) + pkg.DefineGoNativeValue("HashStrBytes", bytealg.HashStrBytes) + pkg.DefineGoNativeValue("HashStrRev", bytealg.HashStrRev) + pkg.DefineGoNativeValue("HashStrRevBytes", bytealg.HashStrRevBytes) + pkg.DefineGoNativeValue("Index", bytealg.Index) + pkg.DefineGoNativeValue("IndexByte", bytealg.IndexByte) + pkg.DefineGoNativeValue("IndexByteString", bytealg.IndexByteString) + pkg.DefineGoNativeValue("IndexRabinKarp", bytealg.IndexRabinKarp) + pkg.DefineGoNativeValue("IndexRabinKarpBytes", bytealg.IndexRabinKarpBytes) + pkg.DefineGoNativeValue("IndexString", bytealg.IndexString) + return pkg, pkg.NewPackage() + */ + default: + // continue on... } - case "hash/fnv": - pkg := gno.NewPackageNode("fnv", pkgPath, nil) - pkg.DefineGoNativeValue("New32a", fnv.New32a) - return pkg, pkg.NewPackage() - /* XXX support somehow for speed. for now, generic implemented in stdlibs. - case "internal/bytealg": - pkg := gno.NewPackageNode("bytealg", pkgPath, nil) - pkg.DefineGoNativeValue("Compare", bytealg.Compare) - pkg.DefineGoNativeValue("CountString", bytealg.CountString) - pkg.DefineGoNativeValue("Cutover", bytealg.Cutover) - pkg.DefineGoNativeValue("Equal", bytealg.Equal) - pkg.DefineGoNativeValue("HashStr", bytealg.HashStr) - pkg.DefineGoNativeValue("HashStrBytes", bytealg.HashStrBytes) - pkg.DefineGoNativeValue("HashStrRev", bytealg.HashStrRev) - pkg.DefineGoNativeValue("HashStrRevBytes", bytealg.HashStrRevBytes) - pkg.DefineGoNativeValue("Index", bytealg.Index) - pkg.DefineGoNativeValue("IndexByte", bytealg.IndexByte) - pkg.DefineGoNativeValue("IndexByteString", bytealg.IndexByteString) - pkg.DefineGoNativeValue("IndexRabinKarp", bytealg.IndexRabinKarp) - pkg.DefineGoNativeValue("IndexRabinKarpBytes", bytealg.IndexRabinKarpBytes) - pkg.DefineGoNativeValue("IndexString", bytealg.IndexString) - return pkg, pkg.NewPackage() - */ - default: - // continue on... } - // if stdlibs package... - stdlibPath := filepath.Join(rootDir, "stdlibs", pkgPath) - if osm.DirExists(stdlibPath) { - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - }) - return m2.RunMemPackage(memPkg, true) + + // if native package is preferred, try to load stdlibs/* as backup. + if mode == ImportModeNativePreferred { + stdlibPath := filepath.Join(rootDir, "stdlibs", pkgPath) + if osm.DirExists(stdlibPath) { + memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "test", + Output: stdout, + Store: store, + }) + pn, pv = m2.RunMemPackage(memPkg, true) + return + } } + // if examples package... examplePath := filepath.Join(rootDir, "examples", pkgPath) if osm.DirExists(examplePath) { @@ -369,7 +404,8 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri Output: stdout, Store: store, }) - return m2.RunMemPackage(memPkg, true) + pn, pv = m2.RunMemPackage(memPkg, true) + return } return nil, nil } diff --git a/tests/package_test.go b/tests/package_test.go index ab7819a4a00..7d3a7a61380 100644 --- a/tests/package_test.go +++ b/tests/package_test.go @@ -63,7 +63,7 @@ func runPackageTest(t *testing.T, dir string, path string) { // stdout := new(bytes.Buffer) stdout := os.Stdout stderr := new(bytes.Buffer) - store := TestStore("..", path, stdin, stdout, stderr, false) + store := TestStore("..", path, stdin, stdout, stderr, ImportModeStdlibsOnly) store.SetLogStoreOps(true) m := gno.NewMachineWithOptions(gno.MachineOptions{ PkgPath: "test", diff --git a/transcribe.go b/transcribe.go index 8c155c9a2b4..a920ab56082 100644 --- a/transcribe.go +++ b/transcribe.go @@ -21,6 +21,7 @@ const ( const ( TRANS_ENTER TransStage = iota TRANS_BLOCK + TRANS_BLOCK2 TRANS_LEAVE ) @@ -603,6 +604,14 @@ func transcribe(t Transform, ns []Node, ftype TransField, index int, n Node, nc if isStopOrSkip(nc, c) { return } + // NOTE: special block case for after .Init and .X. + cnn2, c2 = t(ns, ftype, index, cnn, TRANS_BLOCK2) + if isStopOrSkip(nc, c2) { + nn = cnn2 + return + } else { + cnn = cnn2.(*SwitchStmt) + } for idx := range cnn.Clauses { cnn.Clauses[idx] = *transcribe(t, nns, TRANS_SWITCH_CASE, idx, &cnn.Clauses[idx], &c).(*SwitchClauseStmt) if isBreak(c) { diff --git a/types.go b/types.go index 6c30381e085..b361c5dfcb5 100644 --- a/types.go +++ b/types.go @@ -95,9 +95,13 @@ const ( Uint16Type Uint32Type Uint64Type - // UintptrType + Float32Type + Float64Type UntypedBigintType BigintType + UntypedBigdecType + BigdecType + // UintptrType ) // Used for converting constant binary expressions. @@ -135,16 +139,24 @@ func (pt PrimitiveType) Specificity() int { return 0 case Uint64Type: return 0 + case Float32Type: + return 0 + case Float64Type: + return 0 case BigintType: return 1 - case UntypedBoolType: + case BigdecType: return 2 - case UntypedRuneType: + case UntypedBigdecType: return 3 case UntypedStringType: return 4 case UntypedBigintType: return 4 + case UntypedRuneType: + return 5 + case UntypedBoolType: + return 6 default: panic(fmt.Sprintf("unexpected primitive type %v", pt)) } @@ -178,8 +190,14 @@ func (pt PrimitiveType) Kind() Kind { return Uint32Kind case Uint64Type: return Uint64Kind + case Float32Type: + return Float32Kind + case Float64Type: + return Float64Kind case BigintType, UntypedBigintType: return BigintKind + case BigdecType, UntypedBigdecType: + return BigdecKind default: panic(fmt.Sprintf("unexpected primitive type %v", pt)) } @@ -222,10 +240,18 @@ func (pt PrimitiveType) TypeID() TypeID { return typeid("uint32") case Uint64Type: return typeid("uint64") + case Float32Type: + return typeid("float32") + case Float64Type: + return typeid("float64") case UntypedBigintType: return typeid(" bigint") case BigintType: return typeid("bigint") + case UntypedBigdecType: + return typeid(" bigdec") + case BigdecType: + return typeid("bigdec") default: panic(fmt.Sprintf("unexpected primitive type %v", pt)) } @@ -267,10 +293,18 @@ func (pt PrimitiveType) String() string { return string("uint32") case Uint64Type: return string("uint64") + case Float32Type: + return string("float32") + case Float64Type: + return string("float64") case UntypedBigintType: return string(" bigint") case BigintType: return string("bigint") + case UntypedBigdecType: + return string(" bigdec") + case BigdecType: + return string("bigdec") default: panic(fmt.Sprintf("unexpected primitive type %d", pt)) } @@ -1911,45 +1945,6 @@ func (mn MaybeNativeType) GetPkgPath() string { return mn.Type.GetPkgPath() } -//---------------------------------------- -// Float32 and Float64 - -var ( - Float32Type *StructType - Float64Type *StructType -) - -// NOTE: this path is used to identify the struct type as a Float64. -const ( - float32PkgPath = uversePkgPath + "#float32" - float64PkgPath = uversePkgPath + "#float64" -) - -func init() { - Float32Type = &StructType{ - PkgPath: float32PkgPath, - Fields: []FieldType{ - { - Name: "Value", - Type: Uint32Type, - Embedded: false, - Tag: "", - }, - }, - } - Float64Type = &StructType{ - PkgPath: float64PkgPath, - Fields: []FieldType{ - { - Name: "Value", - Type: Uint64Type, - Embedded: false, - Tag: "", - }, - }, - } -} - //---------------------------------------- // Kind @@ -1969,7 +1964,10 @@ const ( Uint16Kind Uint32Kind Uint64Kind + Float32Kind + Float64Kind BigintKind // not in go. + BigdecKind // not in go. // UintptrKind ArrayKind SliceKind @@ -2018,8 +2016,14 @@ func KindOf(t Type) Kind { return Uint32Kind case Uint64Type: return Uint64Kind + case Float32Type: + return Float32Kind + case Float64Type: + return Float64Kind case BigintType, UntypedBigintType: return BigintKind + case BigdecType, UntypedBigdecType: + return BigdecKind default: panic(fmt.Sprintf("unexpected primitive type %s", t.String())) } @@ -2125,7 +2129,7 @@ func assertEqualityTypes(lt, rt Type) { func isUntyped(t Type) bool { switch t { - case UntypedBoolType, UntypedRuneType, UntypedBigintType, UntypedStringType: + case UntypedBoolType, UntypedRuneType, UntypedBigintType, UntypedBigdecType, UntypedStringType: return true default: return false @@ -2151,6 +2155,8 @@ func defaultTypeOf(t Type) Type { return Int32Type case UntypedBigintType: return IntType + case UntypedBigdecType: + return Float64Type case UntypedStringType: return StringType default: @@ -2201,8 +2207,14 @@ func fillEmbeddedName(ft *FieldType) { ft.Name = Name("uint32") case Uint64Type: ft.Name = Name("uint64") + case Float32Type: + ft.Name = Name("float32") + case Float64Type: + ft.Name = Name("float64") case BigintType: ft.Name = Name("bigint") + case BigdecType: + ft.Name = Name("bigdec") default: panic("should not happen") } diff --git a/values.go b/values.go index 9007ae752a5..93343d668b1 100644 --- a/values.go +++ b/values.go @@ -3,12 +3,14 @@ package gno import ( "encoding/binary" "fmt" + "math" "math/big" "reflect" "strconv" "strings" "unsafe" + "github.com/cockroachdb/apd" "github.com/gnolang/gno/pkgs/crypto" ) @@ -24,6 +26,7 @@ type Value interface { // for performance. func (StringValue) assertValue() {} func (BigintValue) assertValue() {} +func (BigdecValue) assertValue() {} func (DataByteValue) assertValue() {} func (PointerValue) assertValue() {} func (*ArrayValue) assertValue() {} @@ -41,6 +44,7 @@ func (RefValue) assertValue() {} var ( _ Value = StringValue("") _ Value = BigintValue{} + _ Value = BigdecValue{} _ Value = DataByteValue{} _ Value = PointerValue{} _ Value = &ArrayValue{} // TODO doesn't have to be pointer? @@ -56,8 +60,14 @@ var ( _ Value = RefValue{} ) +//---------------------------------------- +// StringValue + type StringValue string +//---------------------------------------- +// BigintValue + type BigintValue struct { V *big.Int } @@ -84,6 +94,43 @@ func (bv BigintValue) Copy(alloc *Allocator) BigintValue { return BigintValue{V: big.NewInt(0).Set(bv.V)} } +//---------------------------------------- +// BigdecValue + +type BigdecValue struct { + V *apd.Decimal +} + +func (bv BigdecValue) MarshalAmino() (string, error) { + bz, err := bv.V.MarshalText() + if err != nil { + return "", err + } + return string(bz), nil +} + +func (bv *BigdecValue) UnmarshalAmino(s string) error { + vv := apd.New(0, 0) + err := vv.UnmarshalText([]byte(s)) + if err != nil { + return err + } + bv.V = vv + return nil +} + +func (bv BigdecValue) Copy(alloc *Allocator) BigdecValue { + cp := apd.New(0, 0) + _, err := apd.BaseContext.Add(cp, cp, bv.V) + if err != nil { + panic("should not happen") + } + return BigdecValue{V: cp} +} + +//---------------------------------------- +// DataByteValue + type DataByteValue struct { Base *ArrayValue // base array. Index int // base.Data index. @@ -98,6 +145,9 @@ func (dbv DataByteValue) SetByte(b byte) { dbv.Base.Data[dbv.Index] = b } +//---------------------------------------- +// PointerValue + // Base is set if the pointer refers to an array index or // struct field or block var. // A pointer constructed via a &x{} composite lit @@ -248,6 +298,9 @@ func (pv PointerValue) Deref() (tv TypedValue) { } } +//---------------------------------------- +// ArrayValue + type ArrayValue struct { ObjectInfo List []TypedValue @@ -338,6 +391,9 @@ func (av *ArrayValue) Copy(alloc *Allocator) *ArrayValue { } } +//---------------------------------------- +// SliceValue + type SliceValue struct { Base Value Offset int @@ -382,6 +438,9 @@ func (sv *SliceValue) GetPointerAtIndexInt2(store Store, ii int, et Type) Pointe return sv.GetBase(store).GetPointerAtIndexInt2(store, sv.Offset+ii, et) } +//---------------------------------------- +// StructValue + type StructValue struct { ObjectInfo Fields []TypedValue @@ -445,6 +504,9 @@ func (sv *StructValue) Copy(alloc *Allocator) *StructValue { return alloc.NewStruct(fields) } +//---------------------------------------- +// FuncValue + // FuncValue.Type stores the method signature from the // declaration, and has exact parameter/result names declared, // whereas the TypedValue.T that contains at .V may not. (i.e. @@ -550,6 +612,9 @@ func (fv *FuncValue) GetClosure(store Store) *Block { } } +//---------------------------------------- +// BoundMethodValue + type BoundMethodValue struct { ObjectInfo @@ -563,6 +628,9 @@ type BoundMethodValue struct { Receiver TypedValue } +//---------------------------------------- +// MapValue + type MapValue struct { ObjectInfo List *MapList @@ -705,11 +773,17 @@ func (mv *MapValue) DeleteForKey(store Store, key *TypedValue) { } } +//---------------------------------------- +// TypeValue + // The type itself as a value. type TypeValue struct { Type Type } +//---------------------------------------- +// PackageValue + type PackageValue struct { ObjectInfo // is a separate object from .Block. Block Value @@ -826,6 +900,9 @@ func (pv *PackageValue) GetPkgAddr() crypto.Address { return DerivePkgAddr(pv.PkgPath) } +//---------------------------------------- +// NativeValue + type NativeValue struct { Value reflect.Value `json:"-"` Bytes []byte // XXX is this used? @@ -839,7 +916,7 @@ func (nv *NativeValue) Copy(alloc *Allocator) *NativeValue { } //---------------------------------------- -// TypedValue +// TypedValue (is not a value, but a tuple) type TypedValue struct { T Type `json:",omitempty"` // never nil @@ -981,6 +1058,18 @@ func (tv *TypedValue) PrimitiveBytes() (data []byte) { binary.LittleEndian.PutUint64( data, uint64(tv.GetUint())) return data + case Float32Type: + data = make([]byte, 4) + u32 := math.Float32bits(tv.GetFloat32()) + binary.LittleEndian.PutUint32( + data, uint32(u32)) + return data + case Float64Type: + data = make([]byte, 8) + u64 := math.Float64bits(tv.GetFloat64()) + binary.LittleEndian.PutUint64( + data, uint64(u64)) + return data case BigintType: return tv.V.(BigintValue).V.Bytes() default: @@ -1301,17 +1390,72 @@ func (tv *TypedValue) GetUint64() uint64 { return *(*uint64)(unsafe.Pointer(&tv.N)) } -func (tv *TypedValue) GetBig() *big.Int { +func (tv *TypedValue) SetFloat32(n float32) { + if debug { + if tv.T.Kind() != Float32Kind || isNative(tv.T) { + panic(fmt.Sprintf( + "TypedValue.SetFloat32() on type %s", + tv.T.String())) + } + } + *(*float32)(unsafe.Pointer(&tv.N)) = n +} + +func (tv *TypedValue) GetFloat32() float32 { + if debug { + if tv.T != nil && tv.T.Kind() != Float32Kind { + panic(fmt.Sprintf( + "TypedValue.GetFloat32() on type %s", + tv.T.String())) + } + } + return *(*float32)(unsafe.Pointer(&tv.N)) +} + +func (tv *TypedValue) SetFloat64(n float64) { + if debug { + if tv.T.Kind() != Float64Kind || isNative(tv.T) { + panic(fmt.Sprintf( + "TypedValue.SetFloat64() on type %s", + tv.T.String())) + } + } + *(*float64)(unsafe.Pointer(&tv.N)) = n +} + +func (tv *TypedValue) GetFloat64() float64 { + if debug { + if tv.T != nil && tv.T.Kind() != Float64Kind { + panic(fmt.Sprintf( + "TypedValue.GetFloat64() on type %s", + tv.T.String())) + } + } + return *(*float64)(unsafe.Pointer(&tv.N)) +} + +func (tv *TypedValue) GetBigInt() *big.Int { if debug { if tv.T != nil && tv.T.Kind() != BigintKind { panic(fmt.Sprintf( - "TypedValue.GetBig() on type %s", + "TypedValue.GetBigInt() on type %s", tv.T.String())) } } return tv.V.(BigintValue).V } +func (tv *TypedValue) GetBigDec() *apd.Decimal { + if debug { + if tv.T != nil && tv.T.Kind() != BigdecKind { + panic(fmt.Sprintf( + "TypedValue.GetBigDec() on type %s", + tv.T.String())) + } + } + return tv.V.(BigdecValue).V +} + func (tv *TypedValue) ComputeMapKey(store Store, omitType bool) MapKey { // Special case when nil: has no separator. if tv.T == nil { @@ -1489,7 +1633,12 @@ func (tv *TypedValue) GetPointerTo(alloc *Allocator, store Store, path ValuePath panic("should not happen") } case VPDerefValMethod: - dtv = tv.V.(PointerValue).TV + dtv2 := tv.V.(PointerValue).TV + dtv = &TypedValue{ // In case method is called on converted type, like ((*othertype)x).Method(). + T: tv.T.Elem(), + V: dtv2.V, + N: dtv2.N, + } isPtr = true path.Type = VPValMethod case VPDerefPtrMethod: diff --git a/values_conversions.go b/values_conversions.go index 3f5e858a3fe..aa04e83ceda 100644 --- a/values_conversions.go +++ b/values_conversions.go @@ -3,8 +3,11 @@ package gno import ( "fmt" "math" + "math/big" "reflect" "strconv" + + "github.com/cockroachdb/apd" ) // t cannot be nil or untyped or DataByteType. @@ -127,6 +130,14 @@ GNO_CASE: x := uint64(tv.GetInt()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetInt()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetInt()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetInt()))) tv.T = t @@ -178,6 +189,14 @@ GNO_CASE: x := uint64(tv.GetInt8()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetInt8()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetInt8()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetInt8()))) tv.T = t @@ -229,6 +248,14 @@ GNO_CASE: x := uint64(tv.GetInt16()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetInt16()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetInt16()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetInt16()))) tv.T = t @@ -280,6 +307,14 @@ GNO_CASE: x := uint64(tv.GetInt32()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetInt32()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetInt32()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetInt32()))) tv.T = t @@ -331,6 +366,14 @@ GNO_CASE: x := uint64(tv.GetInt64()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetInt64()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetInt64()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetInt64()))) tv.T = t @@ -382,6 +425,14 @@ GNO_CASE: x := uint64(tv.GetUint()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetUint()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetUint()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetUint()))) tv.T = t @@ -433,6 +484,14 @@ GNO_CASE: x := uint64(tv.GetUint8()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetUint8()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetUint8()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetUint8()))) tv.T = t @@ -484,6 +543,14 @@ GNO_CASE: x := uint64(tv.GetUint16()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetUint16()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetUint16()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetUint16()))) tv.T = t @@ -535,6 +602,14 @@ GNO_CASE: x := uint64(tv.GetUint32()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetUint32()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetUint32()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetUint32()))) tv.T = t @@ -586,6 +661,14 @@ GNO_CASE: x := uint64(tv.GetUint64()) tv.T = t tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetUint64()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetUint64()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) case StringKind: tv.V = alloc.NewString(string(rune(tv.GetUint64()))) tv.T = t @@ -595,6 +678,116 @@ GNO_CASE: "cannot convert %s to %s", tvk.String(), k.String())) } + case Float32Kind: + switch k { + case IntKind: + x := int(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetInt(x) + case Int8Kind: + x := int8(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetInt8(x) + case Int16Kind: + x := int16(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetInt16(x) + case Int32Kind: + x := int32(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetInt32(x) + case Int64Kind: + x := int64(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetInt64(x) + case UintKind: + x := uint(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetUint(x) + case Uint8Kind: + x := uint8(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetUint8(x) + case Uint16Kind: + x := uint16(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetUint16(x) + case Uint32Kind: + x := uint32(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetUint32(x) + case Uint64Kind: + x := uint64(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetFloat32()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) + default: + panic(fmt.Sprintf( + "cannot convert %s to %s", + tvk.String(), k.String())) + } + case Float64Kind: + switch k { + case IntKind: + x := int(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetInt(x) + case Int8Kind: + x := int8(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetInt8(x) + case Int16Kind: + x := int16(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetInt16(x) + case Int32Kind: + x := int32(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetInt32(x) + case Int64Kind: + x := int64(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetInt64(x) + case UintKind: + x := uint(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetUint(x) + case Uint8Kind: + x := uint8(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetUint8(x) + case Uint16Kind: + x := uint16(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetUint16(x) + case Uint32Kind: + x := uint32(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetUint32(x) + case Uint64Kind: + x := uint64(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetUint64(x) + case Float32Kind: + x := float32(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetFloat32(x) + case Float64Kind: + x := float64(tv.GetFloat64()) // XXX determinism? + tv.T = t + tv.SetFloat64(x) + default: + panic(fmt.Sprintf( + "cannot convert %s to %s", + tvk.String(), k.String())) + } case StringKind: bt := baseOf(t) switch cbt := bt.(type) { @@ -722,11 +915,11 @@ func ConvertUntypedTo(tv *TypedValue, t Type) { return } // general case + if t == nil { + t = defaultTypeOf(tv.T) + } switch tv.T { case UntypedBoolType: - if t == nil { - t = BoolType - } if debug { if t.Kind() != BoolKind { panic("untyped bool can only be converted to bool kind") @@ -734,25 +927,21 @@ func ConvertUntypedTo(tv *TypedValue, t Type) { } tv.T = t case UntypedRuneType: - if t == nil { - t = Int32Type - } ConvertUntypedRuneTo(tv, t) case UntypedBigintType: if preprocessing == 0 { panic("untyped Bigint conversion should not happen during interpretation") } - if t == nil { - t = IntType - } ConvertUntypedBigintTo(tv, tv.V.(BigintValue), t) + case UntypedBigdecType: + if preprocessing == 0 { + panic("untyped Bigdec conversion should not happen during interpretation") + } + ConvertUntypedBigdecTo(tv, tv.V.(BigdecValue), t) case UntypedStringType: if preprocessing == 0 { panic("untyped String conversion should not happen during interpretation") } - if t == nil { - t = StringType - } if t.Kind() == StringKind { tv.T = t return @@ -773,7 +962,7 @@ func ConvertUntypedRuneTo(dst *TypedValue, t Type) { switch k { case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind: case UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind: - case StringKind: + case StringKind, BigintKind, BigdecKind: default: panic(fmt.Sprintf( "cannot convert untyped rune type to %s", @@ -842,13 +1031,18 @@ func ConvertUntypedRuneTo(dst *TypedValue, t Type) { dst.SetUint64(uint64(sv)) case StringKind: panic("not yet implemented") + case BigintKind: + dst.ClearNum() + dst.V = BigintValue{V: big.NewInt(int64(sv))} + case BigdecKind: + dst.ClearNum() + dst.V = BigdecValue{V: apd.New(int64(sv), 0)} default: panic(fmt.Sprintf("unexpected target %v", k)) } } -// All fields may be modified to complete the conversion. func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { k := t.Kind() bi := bv.V @@ -884,6 +1078,38 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { } } uv = bi.Uint64() + case Float32Kind: + dst.T = t + dst.V = nil + // 24 for float32 + bf := big.NewFloat(0.0).SetInt(bi).SetPrec(24) + if bf.IsInf() { + panic("bigint overflows float32") + } + f32, acc := bf.Float32() + if f32 == 0 && (acc == big.Below || acc == big.Above) { + panic("bigint underflows float32 (too close to zero)") + } + dst.SetFloat32(f32) + return // done + case Float64Kind: + dst.T = t + dst.V = nil + // 53 for float64 + bf := big.NewFloat(0.0).SetInt(bi).SetPrec(53) + if bf.IsInf() { + panic("bigint overflows float64") + } + f64, acc := bf.Float64() + if f64 == 0 && (acc == big.Below || acc == big.Above) { + panic("bigint underflows float64 (too close to zero)") + } + dst.SetFloat64(f64) + return // done + case BigdecKind: + dst.T = t + dst.V = BigdecValue{V: apd.NewWithBigInt(bi, 0)} + return // done default: panic(fmt.Sprintf( "cannot convert untyped bigint type to %s", @@ -973,3 +1199,97 @@ func ConvertUntypedBigintTo(dst *TypedValue, bv BigintValue, t Type) { } } + +func ConvertUntypedBigdecTo(dst *TypedValue, bv BigdecValue, t Type) { + k := t.Kind() + bd := bv.V + switch k { + case BigintKind: + if !isInteger(bd) { + panic(fmt.Sprintf( + "cannot convert untyped bigdec to integer -- %s not an exact integer", + bd.String(), + )) + } + dst.T = t + dst.V = BigintValue{V: toBigInt(bd)} + return // done + case BoolKind: + panic("cannot convert untyped bigdec to bool") + case InterfaceKind: + dst.T = Float64Type + dst.V = nil + f, _ := bd.Float64() + dst.SetFloat64(f) + return + case IntKind, Int8Kind, Int16Kind, Int32Kind, Int64Kind: + fallthrough + case UintKind, Uint8Kind, Uint16Kind, Uint32Kind, Uint64Kind: + if !isInteger(bd) { + panic(fmt.Sprintf( + "cannot convert untyped bigdec to integer -- %s not an exact integer", + bd.String(), + )) + } + ConvertUntypedBigintTo(dst, BigintValue{V: toBigInt(bd)}, t) + return + case Float32Kind: + dst.T = t + dst.V = nil + f64, _ := bd.Float64() + bf := big.NewFloat(f64) + f32, acc := bf.Float32() + if f32 == 0 && (acc == big.Below || acc == big.Above) { + panic("cannot convert untyped bigdec to float32 -- too close to zero") + } else if math.IsInf(float64(f32), 0) { + panic("cannot convert untyped bigdec to float32 -- too close to +-Inf") + } + dst.SetFloat32(f32) + return + case Float64Kind: + dst.T = t + dst.V = nil + f64, _ := bd.Float64() + if f64 == 0 && !bd.IsZero() { + panic("cannot convert untyped bigdec to float64 -- too close to zero") + } else if math.IsInf(float64(f64), 0) { + panic("cannot convert untyped bigdec to float64 -- too close to +-Inf") + } + dst.SetFloat64(f64) + return + default: + panic(fmt.Sprintf( + "cannot convert untyped bigdec type to %s", + k.String())) + } +} + +//---------------------------------------- +// apd.Decimal utility + +func isInteger(d *apd.Decimal) bool { + d2 := apd.New(0, 0) + res, err := apd.BaseContext.RoundToIntegralExact(d2, d) + if err != nil { + panic("should not happen") + } + integer := !res.Inexact() + return integer +} + +func toBigInt(d *apd.Decimal) *big.Int { + d2 := apd.New(0, 0) + _, err := apd.BaseContext.RoundToIntegralExact(d2, d) + if err != nil { + panic("should not happen") + } + d2s := d2.String() + bi := big.NewInt(0) + _, ok := bi.SetString(d2s, 10) + if !ok { + panic(fmt.Sprintf( + "invalid integer constant: %s", + d2s)) + } + return bi +} diff --git a/values_string.go b/values_string.go index e67d156b71d..759aa207840 100644 --- a/values_string.go +++ b/values_string.go @@ -15,6 +15,10 @@ func (v BigintValue) String() string { return v.V.String() } +func (v BigdecValue) String() string { + return v.V.String() +} + func (v DataByteValue) String() string { return fmt.Sprintf("(%0X)", (v.GetByte())) } @@ -210,8 +214,14 @@ func (tv *TypedValue) Sprint(m *Machine) string { return fmt.Sprintf("%d", tv.GetUint32()) case Uint64Type: return fmt.Sprintf("%d", tv.GetUint64()) + case Float32Type: + return fmt.Sprintf("%v", tv.GetFloat32()) + case Float64Type: + return fmt.Sprintf("%v", tv.GetFloat64()) case UntypedBigintType, BigintType: return tv.V.(BigintValue).V.String() + case UntypedBigdecType, BigdecType: + return tv.V.(BigdecValue).V.String() default: panic("should not happen") } @@ -310,6 +320,10 @@ func (tv TypedValue) String() string { vs = fmt.Sprintf("%d", tv.GetUint32()) case Uint64Type: vs = fmt.Sprintf("%d", tv.GetUint64()) + case Float32Type: + vs = fmt.Sprintf("%v", tv.GetFloat32()) + case Float64Type: + vs = fmt.Sprintf("%v", tv.GetFloat64()) default: vs = "nil" } diff --git a/vptype_string.go b/vptype_string.go index 6d8f9368a36..f998e9c235a 100644 --- a/vptype_string.go +++ b/vptype_string.go @@ -35,7 +35,7 @@ var ( func (i VPType) String() string { switch { - case 0 <= i && i <= 6: + case i <= 6: return _VPType_name_0[_VPType_index_0[i]:_VPType_index_0[i+1]] case 18 <= i && i <= 21: i -= 18