summaryrefslogtreecommitdiff
path: root/src/Language/Fiddle/Internal/UnitNumbers.hs
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-10-11 18:45:34 -0600
committerJosh Rahm <joshuarahm@gmail.com>2024-10-11 18:45:34 -0600
commitda0d596946cf21e2f275dd03b40c0a6c0824f66b (patch)
tree517763d8d0613dc0f1b138eb2434a2a709383227 /src/Language/Fiddle/Internal/UnitNumbers.hs
parent6ce692d61e8486c103a8492b0ec372858b29de50 (diff)
downloadfiddle-da0d596946cf21e2f275dd03b40c0a6c0824f66b.tar.gz
fiddle-da0d596946cf21e2f275dd03b40c0a6c0824f66b.tar.bz2
fiddle-da0d596946cf21e2f275dd03b40c0a6c0824f66b.zip
Change 'Expression' to use numbers with units.
This helps to catch bugs in the compiler, specifically ones related to mixing up bits and bytes.
Diffstat (limited to 'src/Language/Fiddle/Internal/UnitNumbers.hs')
-rw-r--r--src/Language/Fiddle/Internal/UnitNumbers.hs74
1 files changed, 74 insertions, 0 deletions
diff --git a/src/Language/Fiddle/Internal/UnitNumbers.hs b/src/Language/Fiddle/Internal/UnitNumbers.hs
new file mode 100644
index 0000000..7bdc539
--- /dev/null
+++ b/src/Language/Fiddle/Internal/UnitNumbers.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Language.Fiddle.Internal.UnitNumbers where
+
+import Data.Aeson (FromJSON (..), ToJSON (..))
+import Text.Printf
+
+-- | 'NumberWithUnit' represents a numeric value associated with a unit type.
+-- The unit type 'u' is a phantom type, meaning it carries no runtime data
+-- but provides compile-time type safety for the units.
+-- The actual value is of type 'i', such as 'Int' or 'Integer'.
+newtype NumberWithUnit u i = NumberWithUnit i
+ deriving newtype (Real, Enum, Num, Eq, Ord, Integral, PrintfArg)
+
+-- | Custom 'Show' instance for 'NumberWithUnit' to display the numeric value.
+instance (Show i) => Show (NumberWithUnit u i) where
+ show (NumberWithUnit b) = show b
+
+-- | JSON serialization for 'NumberWithUnit', converting to and from JSON using
+-- the underlying numeric type.
+instance (ToJSON i) => ToJSON (NumberWithUnit u i) where
+ toJSON (NumberWithUnit b) = toJSON b
+
+instance (FromJSON i) => FromJSON (NumberWithUnit u i) where
+ parseJSON v = NumberWithUnit <$> parseJSON v
+
+-- | Type alias for 'NumberWithUnit' where the underlying numeric type is 'Int'.
+-- This is a shorthand for convenience when working with integers.
+type N u = NumberWithUnit u Int
+
+-- | Phantom types representing different units that can be used with
+-- 'NumberWithUnit'. These types carry no data but serve as compile-time
+-- markers for the units.
+data Bits
+data Bytes
+data Address
+data Unitless
+
+-- | 'NamedUnit' is a typeclass for units that can be represented as strings.
+-- It provides a 'unitName' method for displaying a formatted representation
+-- of the unit.
+class NamedUnit u where
+ unitName :: N u -> String
+
+-- | 'NamedUnit' instance for 'Bits'. It displays the value followed by "bit"
+-- or "bits", depending on whether the value is 1 or not.
+instance NamedUnit Bits where
+ unitName 1 = "1 bit"
+ unitName n = show n ++ " bits"
+
+-- | 'NamedUnit' instance for 'Bytes'. It displays the value followed by "byte"
+-- or "bytes", depending on whether the value is 1 or not.
+instance NamedUnit Bytes where
+ unitName 1 = "1 byte"
+ unitName n = show n ++ " bytes"
+
+-- | Convert a value from 'Bits' to 'Bytes'. This returns a tuple containing the
+-- number of complete bytes and the remaining bits.
+bitsToBytes :: N Bits -> (N Bytes, N Bits)
+bitsToBytes (NumberWithUnit a) =
+ let (y, i) = divMod a 8 in (NumberWithUnit y, NumberWithUnit i)
+
+-- | Convert a value from 'Bytes' to 'Bits'. This multiplies the byte value by
+-- 8 to get the corresponding number of bits.
+bytesToBits :: N Bytes -> N Bits
+bytesToBits (NumberWithUnit a) = NumberWithUnit (a * 8)
+
+-- | Multiplication operator for 'NumberWithUnit' values. The result retains
+-- the original unit 'u', while the right operand is of the 'Unitless' type.
+-- This operator is useful for scaling values by a unitless factor.
+(.*.) :: N u -> N Unitless -> N u
+(.*.) (NumberWithUnit a) (NumberWithUnit b) = NumberWithUnit (a * b)
+