summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJuan Carlos <juancarlospaco@gmail.com>2021-01-14 16:19:41 -0300
committerGitHub <noreply@github.com>2021-01-14 20:19:41 +0100
commit41965880ce095da09a1f7e781a0c79e436432401 (patch)
tree8607f0b064b7e81b3b2fcdab1d255f6128cb8bae
parenta90f7a66edd393f04f12fb2f53ef2a6de553cf6b (diff)
downloadNim-41965880ce095da09a1f7e781a0c79e436432401.tar.gz
Add js BigInts (#16409)
* Add BigInts
* Renames tos plurals
* Improve Stringifications
* Update changelog.md

Co-authored-by: flywind <43030857+xflywind@users.noreply.github.com>

* RunnableExamplerize
* discard the discardable pragma
* Several improvements from peer reviews, more docs
* More doc, more test
* More doc, more test
* Better error message 'Error: usage of low is an {.error.} defined at jsbigints.nim' instead of just 'type mismatch JsBigInt'
* is an overload, rename
* proc to scare kids away
* Update lib/js/jsbigints.nim

Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>

* https://github.com/nim-lang/Nim/pull/16409#discussion_r554365041

Co-authored-by: flywind <43030857+xflywind@users.noreply.github.com>
Co-authored-by: Timothee Cour <timothee.cour2@gmail.com>
-rw-r--r--changelog.md2
-rw-r--r--lib/std/jsbigints.nim193
-rw-r--r--tests/stdlib/tjsbigints.nim38
-rw-r--r--tools/kochdocs.nim5
4 files changed, 237 insertions, 1 deletions
diff --git a/changelog.md b/changelog.md
index 9078895c9..2604b09b3 100644
--- a/changelog.md
+++ b/changelog.md
@@ -72,6 +72,8 @@
 - `echo` and `debugEcho` will now raise `IOError` if writing to stdout fails.  Previous behavior
   silently ignored errors.  See #16366.  Use `-d:nimLegacyEchoNoRaise` for previous behavior.
 
+- Added `jsbigints` module, arbitrary precision integers for JavaScript target.
+
 - Added `math.copySign`.
 - Added new operations for singly- and doubly linked lists: `lists.toSinglyLinkedList`
   and `lists.toDoublyLinkedList` convert from `openArray`s; `lists.copy` implements
diff --git a/lib/std/jsbigints.nim b/lib/std/jsbigints.nim
new file mode 100644
index 000000000..733f3369b
--- /dev/null
+++ b/lib/std/jsbigints.nim
@@ -0,0 +1,193 @@
+## Arbitrary precision integers.
+## * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
+when not defined(js):
+  {.fatal: "Module jsbigints is designed to be used with the JavaScript backend.".}
+
+type JsBigIntImpl {.importc: "bigint".} = int # https://github.com/nim-lang/Nim/pull/16606
+type JsBigInt* = distinct JsBigIntImpl        ## Arbitrary precision integer for JavaScript target.
+
+func big*(integer: SomeInteger): JsBigInt {.importjs: "BigInt(#)".} =
+  ## Constructor for `JsBigInt`.
+  runnableExamples:
+    doAssert big(1234567890) == big"1234567890"
+
+func big*(integer: cstring): JsBigInt {.importjs: "BigInt(#)".} =
+  ## Constructor for `JsBigInt`.
+  runnableExamples:
+    doAssert big"-1" == big"1" - big"2"
+
+func toCstring*(this: JsBigInt; radix: 2..36): cstring {.importjs: "#.toString(#)".} =
+  ## Converts from `JsBigInt` to `cstring` representation.
+  ## * `radix` Base to use for representing numeric values.
+  ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
+  runnableExamples:
+    doAssert big"2147483647".toCstring(2) == "1111111111111111111111111111111".cstring
+
+func toCstring*(this: JsBigInt): cstring {.importjs: "#.toString()".}
+  ## Converts from `JsBigInt` to `cstring` representation.
+  ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString
+
+func `$`*(this: JsBigInt): string =
+  ## Returns a `string` representation of `JsBigInt`.
+  runnableExamples: doAssert $big"1024" == "1024n"
+  $toCstring(this) & 'n'
+
+func wrapToInt*(this: JsBigInt; bits: Natural): JsBigInt {.importjs:
+  "(() => { const i = #, b = #; return BigInt.asIntN(b, i) })()".} =
+  ## Wraps `this` to a signed `JsBigInt` of `bits` bits in `-2 ^ (bits - 1)` .. `2 ^ (bits - 1) - 1`.
+  ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN
+  runnableExamples:
+    doAssert (big("3") + big("2") ** big("66")).wrapToInt(13) == big("3")
+
+func wrapToUint*(this: JsBigInt; bits: Natural): JsBigInt {.importjs:
+  "(() => { const i = #, b = #; return BigInt.asUintN(b, i) })()".} =
+  ## Wraps `this` to an unsigned `JsBigInt` of `bits` bits in 0 ..  `2 ^ bits - 1`.
+  ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN
+  runnableExamples:
+    doAssert (big("3") + big("2") ** big("66")).wrapToUint(66) == big("3")
+
+func toNumber*(this: JsBigInt): BiggestInt {.importjs: "Number(#)".} =
+  ## Does not do any bounds check and may or may not return an inexact representation.
+  runnableExamples:
+    doAssert toNumber(big"2147483647") == 2147483647.BiggestInt
+
+func `+`*(x, y: JsBigInt): JsBigInt {.importjs: "(# $1 #)".} =
+  runnableExamples:
+    doAssert (big"9" + big"1") == big"10"
+
+func `-`*(x, y: JsBigInt): JsBigInt {.importjs: "(# $1 #)".} =
+  runnableExamples:
+    doAssert (big"9" - big"1") == big"8"
+
+func `*`*(x, y: JsBigInt): JsBigInt {.importjs: "(# $1 #)".} =
+  runnableExamples:
+    doAssert (big"42" * big"9") == big"378"
+
+func `div`*(x, y: JsBigInt): JsBigInt {.importjs: "(# / #)".} =
+  ## Same as `div` but for `JsBigInt`(uses JavaScript `BigInt() / BigInt()`).
+  runnableExamples:
+    doAssert big"13" div big"3" == big"4"
+    doAssert big"-13" div big"3" == big"-4"
+    doAssert big"13" div big"-3" == big"-4"
+    doAssert big"-13" div big"-3" == big"4"
+
+func `mod`*(x, y: JsBigInt): JsBigInt {.importjs: "(# % #)".} =
+  ## Same as `mod` but for `JsBigInt` (uses JavaScript `BigInt() % BigInt()`).
+  runnableExamples:
+    doAssert big"13" mod big"3" == big"1"
+    doAssert big"-13" mod big"3" == big"-1"
+    doAssert big"13" mod big"-3" == big"1"
+    doAssert big"-13" mod big"-3" == big"-1"
+
+func `<`*(x, y: JsBigInt): bool {.importjs: "(# $1 #)".} =
+  runnableExamples:
+    doAssert big"2" < big"9"
+
+func `<=`*(x, y: JsBigInt): bool {.importjs: "(# $1 #)".} =
+  runnableExamples:
+    doAssert big"1" <= big"5"
+
+func `==`*(x, y: JsBigInt): bool {.importjs: "(# == #)".} =
+  runnableExamples:
+    doAssert big"42" == big"42"
+
+func `**`*(x, y: JsBigInt): JsBigInt {.importjs: "((#) $1 #)".} =
+  runnableExamples:
+    doAssert (big"9" ** big"5") == big"59049"
+
+func `xor`*(x, y: JsBigInt): JsBigInt {.importjs: "(# ^ #)".} =
+  runnableExamples:
+    doAssert (big"555" xor big"2") == big"553"
+
+func `shl`*(a, b: JsBigInt): JsBigInt {.importjs: "(# << #)".} =
+  runnableExamples:
+    doAssert (big"999" shl big"2") == big"3996"
+
+func `shr`*(a, b: JsBigInt): JsBigInt {.importjs: "(# >> #)".} =
+  runnableExamples:
+    doAssert (big"999" shr big"2") == big"249"
+
+func `-`*(this: JsBigInt): JsBigInt {.importjs: "($1#)".} =
+  runnableExamples:
+    doAssert -(big"10101010101") == big"-10101010101"
+
+func inc*(this: var JsBigInt) {.importjs: "(++[#][0][0])".} =
+  runnableExamples:
+    var big1: JsBigInt = big"1"
+    inc big1
+    doAssert big1 == big"2"
+
+func dec*(this: var JsBigInt) {.importjs: "(--[#][0][0])".} =
+  runnableExamples:
+    var big1: JsBigInt = big"2"
+    dec big1
+    doAssert big1 == big"1"
+
+func inc*(this: var JsBigInt; amount: JsBigInt) {.importjs: "([#][0][0] += #)".} =
+  runnableExamples:
+    var big1: JsBigInt = big"1"
+    inc big1, big"2"
+    doAssert big1 == big"3"
+
+func dec*(this: var JsBigInt; amount: JsBigInt) {.importjs: "([#][0][0] -= #)".} =
+  runnableExamples:
+    var big1: JsBigInt = big"1"
+    dec big1, big"2"
+    doAssert big1 == big"-1"
+
+func `+=`*(x: var JsBigInt; y: JsBigInt) {.importjs: "([#][0][0] $1 #)".} =
+  runnableExamples:
+    var big1: JsBigInt = big"1"
+    big1 += big"2"
+    doAssert big1 == big"3"
+
+func `-=`*(x: var JsBigInt; y: JsBigInt) {.importjs: "([#][0][0] $1 #)".} =
+  runnableExamples:
+    var big1: JsBigInt = big"1"
+    big1 -= big"2"
+    doAssert big1 == big"-1"
+
+func `*=`*(x: var JsBigInt; y: JsBigInt) {.importjs: "([#][0][0] $1 #)".} =
+  runnableExamples:
+    var big1: JsBigInt = big"2"
+    big1 *= big"4"
+    doAssert big1 == big"8"
+
+func `/=`*(x: var JsBigInt; y: JsBigInt) {.importjs: "([#][0][0] $1 #)".} =
+  ## Same as `x = x div y`.
+  runnableExamples:
+    var big1: JsBigInt = big"11"
+    big1 /= big"2"
+    doAssert big1 == big"5"
+
+proc `+`*(_: JsBigInt): JsBigInt {.error:
+  "See https://github.com/tc39/proposal-bigint/blob/master/ADVANCED.md#dont-break-asmjs".} # Can not be used by design
+  ## **Do NOT use.** https://github.com/tc39/proposal-bigint/blob/master/ADVANCED.md#dont-break-asmjs
+
+proc low*(_: typedesc[JsBigInt]): JsBigInt {.error:
+  "Arbitrary precision integers do not have a known low.".} ## **Do NOT use.**
+
+proc high*(_: typedesc[JsBigInt]): JsBigInt {.error:
+  "Arbitrary precision integers do not have a known high.".} ## **Do NOT use.**
+
+
+runnableExamples:
+  let big1: JsBigInt = big"2147483647"
+  let big2: JsBigInt = big"666"
+  doAssert JsBigInt isnot int
+  doAssert big1 != big2
+  doAssert big1 > big2
+  doAssert big1 >= big2
+  doAssert big2 < big1
+  doAssert big2 <= big1
+  doAssert not(big1 == big2)
+  let z = JsBigInt.default
+  doAssert $z == "0n"
+  block:
+    var a: seq[JsBigInt]
+    a.setLen 2
+    doAssert a == @[big"0", big"0"]
+    doAssert a[^1] == big"0"
+    var b: JsBigInt
+    doAssert b == big"0"
+    doAssert b == JsBigInt.default
diff --git a/tests/stdlib/tjsbigints.nim b/tests/stdlib/tjsbigints.nim
new file mode 100644
index 000000000..1988f637b
--- /dev/null
+++ b/tests/stdlib/tjsbigints.nim
@@ -0,0 +1,38 @@
+discard """
+  targets: "js"
+"""
+
+import std/jsbigints
+
+
+let big1: JsBigInt = big"2147483647"
+let big2: JsBigInt = big"666"
+var big3: JsBigInt = big"2"
+
+doAssert big3 == big"2"
+doAssert (big3 xor big2) == big"664"
+doAssert (big1 mod big2) == big"613"
+doAssert -big1 == big"-2147483647"
+doAssert big1 div big2 == big"3224449"
+doAssert big1 + big2 == big"2147484313"
+doAssert big1 - big2 == big"2147482981"
+doAssert big1 shl big3 == big"8589934588"
+doAssert big1 shr big3 == big"536870911"
+doAssert big1 * big2 == big"1430224108902"
+doAssert $big1 == "2147483647n"
+doAssert big1.toCstring(10) == "2147483647".cstring
+doAssert big2 ** big3 == big(443556)
+var huge = big"999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
+huge.inc
+huge = huge + big"-999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
+doAssert huge == big"1"
+var list: seq[JsBigInt]
+for i in big"0" .. big"5":
+  doAssert i is JsBigInt
+  list.add i
+doAssert list == @[big"0", big"1", big"2", big"3", big"4", big"5"]
+list = @[]
+for i in big"0" ..< big"5":
+  doAssert i is JsBigInt
+  list.add i
+doAssert list == @[big"0", big"1", big"2", big"3", big"4"]
diff --git a/tools/kochdocs.nim b/tools/kochdocs.nim
index f258087e7..6fa64c73b 100644
--- a/tools/kochdocs.nim
+++ b/tools/kochdocs.nim
@@ -14,8 +14,11 @@ const
   webUploadOutput = "web/upload"
 
 var nimExe*: string
+const allowList = ["jsbigints.nim"]
 
-template isJsOnly(file: string): bool = file.isRelativeTo("lib/js")
+template isJsOnly(file: string): bool =
+  file.isRelativeTo("lib/js") or
+  file.extractFilename in allowList
 
 proc exe*(f: string): string =
   result = addFileExt(f, ExeExt)