packages feed

build-1.0: test/Spreadsheet.hs

module Spreadsheet where

import Data.Bool
import Data.Char
import Data.Maybe
import Data.String
import Build.Store
import Build.Task
import Text.Read

-- | A 'Cell' is described by a pair integers: 'row' and 'column'. We provide
-- @IsString@ instance for convenience, so @"A8"@ corresponds to @Cell 8 0@.
data Cell = Cell { row :: Int, column :: Int } deriving (Eq, Ord)

-- | Get the name of a 'Cell', e.g. @name (Cell 8 0) == "A8"@.
name :: Cell -> String
name (Cell r c) | c >= 0 && c < 26 = chr (c + ord 'A') : show r
                | otherwise        = show (Cell r c)

instance IsString Cell where
    fromString string = case string of
        columnChar : rowIndex -> Cell r c
            where
              r = fromMaybe fail (readMaybe rowIndex)
              c | isAsciiUpper columnChar = ord columnChar - ord 'A'
                | otherwise               = fail
        _ -> fail
      where
        fail = error $ "Cannot parse cell name " ++ string

instance Show Cell where
    show = name

instance Hashable Cell where
    hash (Cell row column) = Cell <$> hash row <*> hash column

-- | Some cells contain formulas for computing values from other cells. Formulas
-- include:
-- * 'Constant' integer values.
-- * References to cells.
-- * Simple arithmetic functions, such as 'Unary' negation and 'Binary' addition.
-- * Conditional expressions 'IfZero' @x y z@ that evaluate to @y@ if @x@ is zero
--   and to @z@ otherwise. Conditionals require dynamic dependencies to be handled
--   correctly, because their static dependencies may form cycles. Example:
--
--   A1 = IfZero B1 A2 C1
--   A2 = IfZero B1 C2 A1
--
--   Statically there is a mutual dependency between A1 and A2, but dynamically
--   there is either A1 -> A2 or A2 -> A1.
-- * Finally, there is a 'Random' formula that returns a random value in a
--   specified range @[low..high]@. This introduces non-determinism, including
--   failures when the range is empty.
data Formula = Constant Int
             | Reference Cell
             | RelativeReference Int Int
             | Unary (Int -> Int) Formula
             | Binary (Int -> Int -> Int) Formula Formula
             | IfZero Formula Formula Formula
             | Random Int Int

instance Num Formula where
    fromInteger = Constant . fromInteger
    (+)    = Binary (+)
    (-)    = Binary (-)
    (*)    = Binary (*)
    abs    = Unary abs
    signum = Unary signum

instance IsString Formula where
    fromString = Reference . fromString

-- | A short alias for 'RelativeReference'.
rel :: Int -> Int -> Formula
rel = RelativeReference

-- | A spreadsheet is a partial mapping of cells to formulas. Cells for which
-- the mapping returns @Nothing@ are inputs.
type Spreadsheet = Cell -> Maybe Formula

-- | Monadic spreadsheet computation.
spreadsheetTask :: Spreadsheet -> Tasks Monad Cell Int
spreadsheetTask spreadsheet cell@(Cell r c) = case spreadsheet cell of
    Nothing      -> Nothing -- This is an input
    Just formula -> Just $ Task $ evaluate formula
  where
    evaluate formula fetch = go formula
      where go formula = case formula of
                Constant x              -> pure x
                Reference cell          -> fetch cell
                RelativeReference dr dc -> fetch (Cell (r + dr) (c + dc))
                Unary  op fx            -> op <$> go fx
                Binary op fx fy         -> op <$> go fx <*> go fy
                IfZero fx fy fz         -> do
                    x <- go fx
                    if x == 0 then go fy else go fz
                Random _ _      -> error "Not supported by monadic tasks"

-- | Applicative spreadsheet computation.
spreadsheetTaskA :: Spreadsheet -> Tasks Applicative Cell Int
spreadsheetTaskA spreadsheet cell@(Cell r c) = case spreadsheet cell of
    Nothing      -> Nothing -- This is an input
    Just formula -> Just $ Task $ evaluate formula
  where
    evaluate formula fetch = go formula
      where go formula = case formula of
                Constant x              -> pure x
                Reference cell          -> fetch cell
                RelativeReference dr dc -> fetch (Cell (r + dr) (c + dc))
                Unary  op fx            -> op <$> go fx
                Binary op fx fy         -> op <$> go fx <*> go fy
                IfZero fx fy fz         -> bool <$> go fz
                                                <*> go fy
                                                <*> ((==0) <$> go fx)
                Random _ _      -> error "Random not implemented"