diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2024-10-11 18:45:34 -0600 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2024-10-11 18:45:34 -0600 |
commit | da0d596946cf21e2f275dd03b40c0a6c0824f66b (patch) | |
tree | 517763d8d0613dc0f1b138eb2434a2a709383227 /src/Language/Fiddle/Internal/UnitNumbers.hs | |
parent | 6ce692d61e8486c103a8492b0ec372858b29de50 (diff) | |
download | fiddle-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.hs | 74 |
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) + |