diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f0960b9..c89bbf5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,9 @@ +2.1.0 +----- + ++ Added a gleam/dynamic Decoder. ++ Added a string decoder. + 2.0.0 ----- diff --git a/gleam.toml b/gleam.toml index c9dc3d6..ac7b608 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "bigi" -version = "2.0.0" +version = "2.1.0" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. diff --git a/src/bigi.gleam b/src/bigi.gleam index b7f12b5..e15b30e 100644 --- a/src/bigi.gleam +++ b/src/bigi.gleam @@ -1,4 +1,5 @@ import gleam/order +import gleam/dynamic /// A big integer. pub type BigInt @@ -9,11 +10,27 @@ pub type BigInt pub fn zero() -> BigInt /// Create a big integer from a regular integer. +/// +/// Note that in the JavaScript target, if your integer is bigger than the +/// [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) +/// or smaller than the +/// [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER), +/// you may lose precision when operating on it, including when converting it +/// into a big integer (as the JavaScript Number type has already reduced the +/// precision of the value). @external(erlang, "bigi_ffi", "from") @external(javascript, "./bigi_ffi.mjs", "from") pub fn from_int(int: Int) -> BigInt -/// Convert the big integer to a regular integer. +/// Convert a string into a big integer. +/// +/// If the string does not represent a big integer in base 10, an error is +/// returned. Trailing non-digit content is not allowed. +@external(erlang, "bigi_ffi", "from_string") +@external(javascript, "./bigi_ffi.mjs", "from_string") +pub fn from_string(str: String) -> Result(BigInt, Nil) + +/// Convert a big integer to a regular integer. /// /// In Erlang, this cannot fail, as all Erlang integers are big integers. In the /// JavaScript target, this will fail if the integer is bigger than the @@ -123,6 +140,11 @@ pub fn digits(bigint: BigInt) { get_digit(bigint, [], divisor) } +/// Decode a `gleam/dynamic` value into a big integer, if possible. +@external(erlang, "bigi_ffi", "decode") +@external(javascript, "./bigi_ffi.mjs", "decode") +pub fn decode(dyn: dynamic.Dynamic) -> Result(BigInt, dynamic.DecodeErrors) + fn get_digit(bigint: BigInt, digits: List(Int), divisor: BigInt) { case compare(bigint, divisor) { order.Gt -> { diff --git a/src/bigi_ffi.erl b/src/bigi_ffi.erl index d790944..fc25066 100644 --- a/src/bigi_ffi.erl +++ b/src/bigi_ffi.erl @@ -2,6 +2,7 @@ -export([ from/1, + from_string/1, to/1, zero/0, compare/2, @@ -14,11 +15,18 @@ remainder_no_zero/2, modulo/2, modulo_no_zero/2, - power/2 + power/2, + decode/1 ]). from(Int) -> Int. +from_string(Str) -> + case string:to_integer(Str) of + {_, Rest} when Rest /= <<"">> -> {error, nil}; + {Int, _} -> {ok, Int} + end. + to(BigInt) -> {ok, BigInt}. zero() -> 0. @@ -65,3 +73,18 @@ do_power(A, N) -> 0 -> 1; 1 -> A end). + +decode(Dyn) when is_integer(Dyn) -> {ok, Dyn}; +decode(Dyn) -> {error, {decode_error, <<"bigint">>, get_type(Dyn), []}}. + +get_type(Val) when is_atom(Val) -> <<"atom">>; +get_type(Val) when is_function(Val) -> <<"function">>; +get_type(Val) when is_pid(Val) -> <<"pid">>; +get_type(Val) when is_binary(Val) -> <<"binary">>; +get_type(Val) when is_list(Val) -> <<"list">>; +get_type(Val) when is_map(Val) -> <<"map">>; +get_type(Val) when is_reference(Val) -> <<"reference">>; +get_type(Val) when is_float(Val) -> <<"float">>; +get_type(Val) when is_tuple(Val) -> <<"tuple">>; +get_type(Val) when is_port(Val) -> <<"port">>; +get_type(Val) when is_bitstring(Val) -> <<"bitstring">>. diff --git a/src/bigi_ffi.mjs b/src/bigi_ffi.mjs index 895bba4..80c6134 100644 --- a/src/bigi_ffi.mjs +++ b/src/bigi_ffi.mjs @@ -1,10 +1,19 @@ -import { Ok, Error } from "./gleam.mjs"; +import { Ok, Error, toList } from "./gleam.mjs"; import { Lt, Eq, Gt } from "../gleam_stdlib/gleam/order.mjs"; +import { DecodeError } from "../gleam_stdlib/gleam/dynamic.mjs"; export function from(int) { return BigInt(int); } +export function from_string(string) { + try { + return new Ok(BigInt(string)); + } catch { + return new Error(undefined); + } +} + export function to(bigint) { if (bigint > Number.MAX_SAFE_INTEGER || bigint < Number.MIN_SAFE_INTEGER) { return new Error(undefined); @@ -106,3 +115,12 @@ export function power(a, b) { return new Ok(a ** b); } + +export function decode(dyn) { + const dynType = typeof dyn; + if (dynType === "bigint") { + return new Ok(dyn); + } else { + return new Error(toList([new DecodeError("bigint", dynType, toList([]))])); + } +} diff --git a/test/bigi_test.gleam b/test/bigi_test.gleam index 640a602..1809a04 100644 --- a/test/bigi_test.gleam +++ b/test/bigi_test.gleam @@ -1,4 +1,5 @@ import gleam/order +import gleam/dynamic import gleeunit import gleeunit/should import bigi @@ -145,6 +146,34 @@ pub fn negative_exponent_test() { should.be_error(bigint) } +pub fn dynamic_decoder_test() { + let bigint = bigi.from_int(1234) + let dyn = dynamic.from(bigint) + should.equal(bigi.decode(dyn), Ok(bigint)) +} + +pub fn dynamic_decoder_fail_test() { + let not_bigint = "foo" + let dyn = dynamic.from(not_bigint) + should.be_error(bigi.decode(dyn)) +} + +pub fn from_string_test() { + let str = + "214589024895249582498594209582039480239609385130598310670139580139855" + let parsed = bigi.from_string(str) + should.be_ok(parsed) + let assert Ok(parsed_data) = parsed + should.equal(bigi.to_string(parsed_data), str) +} + +pub fn from_bad_string_test() { + let str = + "214589024895249582498594209582039480239-//-609385130598310670139580139855" + let parsed = bigi.from_string(str) + should.be_error(parsed) +} + pub fn power_test() { let assert Ok(bigint) = bigi.power(bigi.from_int(2), bigi.from_int(65_535))