summaryrefslogtreecommitdiff
path: root/src/Language/Fiddle/Internal/UnitNumbers.hs
blob: 7bdc5394ee542d29566f8f0d611c2507c7bc6422 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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)