From f616a9f6a8ae8797f8804fb88283c74eb4d301db Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Thu, 8 Feb 2024 22:07:36 +0200 Subject: [PATCH] WIP --- .gitignore | 4 + README.md | 25 ++++++ gleam.toml | 20 +++++ manifest.toml | 13 +++ src/ranged_int.gleam | 13 +++ src/ranged_int/builtin/uint.gleam | 31 ++++++++ src/ranged_int/builtin/uint8.gleam | 40 ++++++++++ src/ranged_int/interface.gleam | 123 +++++++++++++++++++++++++++++ src/ranged_int/limit.gleam | 45 +++++++++++ src/ranged_int/utils.gleam | 24 ++++++ test/ranged_int_test.gleam | 12 +++ 11 files changed, 350 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 gleam.toml create mode 100644 manifest.toml create mode 100644 src/ranged_int.gleam create mode 100644 src/ranged_int/builtin/uint.gleam create mode 100644 src/ranged_int/builtin/uint8.gleam create mode 100644 src/ranged_int/interface.gleam create mode 100644 src/ranged_int/limit.gleam create mode 100644 src/ranged_int/utils.gleam create mode 100644 test/ranged_int_test.gleam diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/README.md b/README.md new file mode 100644 index 0000000..c403b5f --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# ranged_int + +[![Package Version](https://img.shields.io/hexpm/v/ranged_int)](https://hex.pm/packages/ranged_int) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/ranged_int/) + +```sh +gleam add ranged_int +``` +```gleam +import ranged_int + +pub fn main() { + // TODO: An example of the project in use +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +gleam shell # Run an Erlang shell +``` diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..51225a7 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,20 @@ +name = "ranged_int" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "username", repo = "project" } +# links = [{ title = "Website", href = "https://gleam.run" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +gleam_stdlib = "~> 0.34 or ~> 1.0" +bigi = "~> 1.0" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..8c2b769 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,13 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "bigi", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "bigi", source = "hex", outer_checksum = "CB2766365C3DA3C651AD55F6A6389A586FA6B2D66B80BE0DA168EEBD0EE1A43D" }, + { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, + { name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, +] + +[requirements] +bigi = { version = "~> 1.0"} +gleam_stdlib = { version = "~> 0.34 or ~> 1.0" } +gleeunit = { version = "~> 1.0" } diff --git a/src/ranged_int.gleam b/src/ranged_int.gleam new file mode 100644 index 0000000..14d641e --- /dev/null +++ b/src/ranged_int.gleam @@ -0,0 +1,13 @@ +import gleam/io +import bigi +import ranged_int/builtin/uint8 +import ranged_int/builtin/uint +import ranged_int/utils + +pub fn main() { + io.debug(utils.overflow( + utils.DidUnderflow(bigi.from_int(12)), + bigi.from_int(-15), + bigi.from_int(-126_463), + )) +} diff --git a/src/ranged_int/builtin/uint.gleam b/src/ranged_int/builtin/uint.gleam new file mode 100644 index 0000000..b98d8e2 --- /dev/null +++ b/src/ranged_int/builtin/uint.gleam @@ -0,0 +1,31 @@ +import bigi.{type BigInt} +import ranged_int/interface.{type Interface, Interface, Limits} +import ranged_int/limit.{Bounded, Unbounded} + +pub const min_limit = 0 + +pub opaque type Uint { + Uint(data: BigInt) +} + +const uint_interface: Interface(Uint, interface.NonOverflowable) = Interface( + from_bigint_unsafe: from_bigint_unsafe, + to_bigint: to_bigint, + limits: limits, +) + +pub fn from_bigint(value: BigInt) { + interface.from_bigint(value, uint_interface) +} + +fn limits() { + Limits(max: Unbounded, min: Bounded(bigi.from_int(min_limit))) +} + +fn from_bigint_unsafe(value: BigInt) { + Uint(data: value) +} + +fn to_bigint(uint: Uint) { + uint.data +} diff --git a/src/ranged_int/builtin/uint8.gleam b/src/ranged_int/builtin/uint8.gleam new file mode 100644 index 0000000..118ac8d --- /dev/null +++ b/src/ranged_int/builtin/uint8.gleam @@ -0,0 +1,40 @@ +import bigi.{type BigInt} +import ranged_int/interface.{type Interface, Interface, Limits} +import ranged_int/limit.{Bounded} + +pub const max_limit = 255 + +pub const min_limit = 0 + +pub opaque type Uint8 { + Uint8(data: BigInt) +} + +const uint8_interface: Interface(Uint8, interface.Overflowable) = Interface( + from_bigint_unsafe: from_bigint_unsafe, + to_bigint: to_bigint, + limits: limits, +) + +pub fn from_bigint(value: BigInt) { + interface.from_bigint(value, uint8_interface) +} + +pub fn from_bigint_overflow(value: BigInt) { + interface.from_bigint_overflow(value, uint8_interface) +} + +fn limits() { + Limits( + max: Bounded(bigi.from_int(max_limit)), + min: Bounded(bigi.from_int(min_limit)), + ) +} + +fn from_bigint_unsafe(value: BigInt) { + Uint8(data: value) +} + +fn to_bigint(uint: Uint8) { + uint.data +} diff --git a/src/ranged_int/interface.gleam b/src/ranged_int/interface.gleam new file mode 100644 index 0000000..ea1eaec --- /dev/null +++ b/src/ranged_int/interface.gleam @@ -0,0 +1,123 @@ +import gleam/order +import bigi.{type BigInt} +import ranged_int/limit.{type Limit, NoOverflow, Overflow, Underflow} +import ranged_int/utils + +type Constructor(a) = + fn(BigInt) -> a + +pub type Overflowable { + Overflowable +} + +pub type NonOverflowable { + NonOverflowable +} + +pub opaque type IntType(overflow_mode) { + IntType +} + +pub type Limits { + Limits(max: Limit, min: Limit) +} + +pub type MathOp = + fn(BigInt, BigInt) -> BigInt + +pub type Interface(a, overflow_mode) { + Interface( + to_bigint: fn(a) -> BigInt, + from_bigint_unsafe: Constructor(a), + limits: fn() -> Limits, + ) +} + +pub const mode_overflowable: IntType(Overflowable) = IntType + +pub const mode_non_overflowable: IntType(NonOverflowable) = IntType + +pub fn from_bigint( + value: BigInt, + interface: Interface(a, overflow_mode), +) -> Result(a, utils.Overflow) { + let limits = interface.limits() + case limit.check_limits(value, limits.max, limits.min) { + NoOverflow -> Ok(interface.from_bigint_unsafe(value)) + Overflow(amount) -> Error(utils.DidOverflow(amount)) + Underflow(amount) -> Error(utils.DidUnderflow(amount)) + } +} + +pub fn from_bigint_overflow( + value: BigInt, + interface: Interface(a, Overflowable), +) -> a { + handle_overflow(value, interface) +} + +pub fn compare( + int1: a, + int2: a, + interface: Interface(a, overflow_mode), +) -> order.Order { + bigi.compare(interface.to_bigint(int1), interface.to_bigint(int2)) +} + +pub fn math_op( + int1: a, + int2: a, + interface: Interface(a, overflow_mode), + op: MathOp, +) -> Result(a, utils.Overflow) { + let limits = interface.limits() + let bigint1 = interface.to_bigint(int1) + let bigint2 = interface.to_bigint(int2) + let result = op(bigint1, bigint2) + case limit.check_limits(result, limits.max, limits.min) { + NoOverflow -> Ok(interface.from_bigint_unsafe(result)) + Overflow(amount) -> Error(utils.DidOverflow(amount)) + Underflow(amount) -> Error(utils.DidUnderflow(amount)) + } +} + +pub fn math_op_overflow( + int1: a, + int2: a, + interface: Interface(a, Overflowable), + op: MathOp, +) -> a { + let bigint1 = interface.to_bigint(int1) + let bigint2 = interface.to_bigint(int2) + let result = op(bigint1, bigint2) + handle_overflow(result, interface) +} + +pub fn math_op_eject( + int1: a, + int2: a, + interface: Interface(a, overflow_mode), + op: MathOp, +) -> BigInt { + let bigint1 = interface.to_bigint(int1) + let bigint2 = interface.to_bigint(int2) + op(bigint1, bigint2) +} + +pub fn handle_overflow(value: BigInt, interface: Interface(a, Overflowable)) { + let limits = interface.limits() + let result = case limit.check_limits(value, limits.max, limits.min) { + NoOverflow -> value + Overflow(amount) -> { + let assert limit.Bounded(max) = limits.max + let assert limit.Bounded(min) = limits.min + utils.overflow(utils.DidOverflow(amount), max, min) + } + Underflow(amount) -> { + let assert limit.Bounded(max) = limits.max + let assert limit.Bounded(min) = limits.min + utils.overflow(utils.DidUnderflow(amount), max, min) + } + } + interface.from_bigint_unsafe(result) +} diff --git a/src/ranged_int/limit.gleam b/src/ranged_int/limit.gleam new file mode 100644 index 0000000..c71c589 --- /dev/null +++ b/src/ranged_int/limit.gleam @@ -0,0 +1,45 @@ +import gleam/order +import bigi.{type BigInt} + +/// A limit for an integer. Each direction (max, min) may have its own limit. +pub type Limit { + /// The integer is limited to this value inclusive. + Bounded(BigInt) + + /// The integer is unbounded. + Unbounded +} + +/// Result of a limit check. +pub type LimitCheck { + /// No overflow was detected. + NoOverflow + + /// The amount would overflow the allowed range by this amount. + Overflow(BigInt) + + /// The amount would underflow (be lower than the minimum) by this amount. + Underflow(BigInt) +} + +/// Check the value against the limits. +pub fn check_limits(value: BigInt, max max: Limit, min min: Limit) -> LimitCheck { + let max_diff = diff(max, value) + case bigi.compare(bigi.zero(), max_diff) { + order.Gt -> Overflow(max_diff) + order.Eq | order.Lt -> { + let min_diff = diff(min, value) + case bigi.compare(bigi.zero(), min_diff) { + order.Lt -> Underflow(bigi.multiply(min_diff, bigi.from_int(-1))) + order.Eq | order.Gt -> NoOverflow + } + } + } +} + +fn diff(limit: Limit, value: BigInt) { + case limit { + Unbounded -> bigi.zero() + Bounded(lim) -> bigi.subtract(value, lim) + } +} diff --git a/src/ranged_int/utils.gleam b/src/ranged_int/utils.gleam new file mode 100644 index 0000000..54b66b6 --- /dev/null +++ b/src/ranged_int/utils.gleam @@ -0,0 +1,24 @@ +import bigi.{type BigInt} + +pub type Overflow { + DidOverflow(BigInt) + DidUnderflow(BigInt) +} + +pub fn overflow(overflow: Overflow, max: BigInt, min: BigInt) -> BigInt { + let total_values = + bigi.subtract(max, min) + |> bigi.add(bigi.from_int(1)) + + let min_adjustment_factor = bigi.multiply(min, bigi.from_int(-1)) + let adjusted_max = bigi.add(max, min_adjustment_factor) + let adjusted_min = bigi.zero() + + let overflowed_value = case overflow { + DidOverflow(amount) -> bigi.add(adjusted_max, amount) + DidUnderflow(amount) -> bigi.subtract(adjusted_min, amount) + } + + let overflow_result = bigi.modulo(overflowed_value, total_values) + bigi.subtract(overflow_result, min_adjustment_factor) +} diff --git a/test/ranged_int_test.gleam b/test/ranged_int_test.gleam new file mode 100644 index 0000000..3831e7a --- /dev/null +++ b/test/ranged_int_test.gleam @@ -0,0 +1,12 @@ +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +// gleeunit test functions end in `_test` +pub fn hello_world_test() { + 1 + |> should.equal(1) +}