WIP
This commit is contained in:
commit
f616a9f6a8
11 changed files with 350 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.beam
|
||||
*.ez
|
||||
/build
|
||||
erl_crash.dump
|
25
README.md
Normal file
25
README.md
Normal file
|
@ -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 <https://hexdocs.pm/ranged_int>.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
gleam shell # Run an Erlang shell
|
||||
```
|
20
gleam.toml
Normal file
20
gleam.toml
Normal file
|
@ -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"
|
13
manifest.toml
Normal file
13
manifest.toml
Normal file
|
@ -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" }
|
13
src/ranged_int.gleam
Normal file
13
src/ranged_int.gleam
Normal file
|
@ -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),
|
||||
))
|
||||
}
|
31
src/ranged_int/builtin/uint.gleam
Normal file
31
src/ranged_int/builtin/uint.gleam
Normal file
|
@ -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
|
||||
}
|
40
src/ranged_int/builtin/uint8.gleam
Normal file
40
src/ranged_int/builtin/uint8.gleam
Normal file
|
@ -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
|
||||
}
|
123
src/ranged_int/interface.gleam
Normal file
123
src/ranged_int/interface.gleam
Normal file
|
@ -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)
|
||||
}
|
45
src/ranged_int/limit.gleam
Normal file
45
src/ranged_int/limit.gleam
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
24
src/ranged_int/utils.gleam
Normal file
24
src/ranged_int/utils.gleam
Normal file
|
@ -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)
|
||||
}
|
12
test/ranged_int_test.gleam
Normal file
12
test/ranged_int_test.gleam
Normal file
|
@ -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)
|
||||
}
|
Loading…
Reference in a new issue