lhae-0.0.3: src/Model/Grid.hs
module Model.Grid (Grid,new,getCell,setCellInput,setCellExpr
,rewriteCell,updateCell,updateCellIO,resizeTo
,addRows,addColumns,deleteColumns,deleteRows
,insertRowBefore,insertColumnBefore
,updateRowLabel,updateColumnLabel
,columnIndex,rowIndex
,dependencyCoordsFromInput,getFilePath,save,isSaved
,getColumnValues,getRowValues,evaluateString)
where
import Control.Monad (forM,forM_,when)
import Control.Applicative ((<$>))
import Data.IORef
import System.FilePath (takeFileName)
import Util.DynArray (DynArray)
import qualified Util.DynArray as Dyn
import Model.Cell (Cell)
import qualified Model.Cell as Cell
import Model.CellContent (CellExpr(..),CellValue(..),CellReference(..)
,Reference(..),NamedReference(..),CompileReason(..))
import qualified Model.CellContent as CellContent
import Model.CellExpression.Parser (parse)
import Model.GridHeader (Header)
import qualified Model.GridHeader as Header
import CellCoordinate (CellCoord,coordRange)
import ModelToView (ModelToView)
import qualified ModelToView
data Grid = Grid { cells :: DynArray CellCoord Cell
, rowHeader :: Header
, columnHeader :: Header
, filePath :: IORef (Maybe FilePath)
, gridIsSaved :: IORef Bool
, numViewGridRows :: IO Int
, numViewGridCols :: IO Int
, markAsSavedAs :: String -> IO ()
, markAsUnsaved :: IO ()
}
new :: ModelToView -> IO Grid
new toView = do
cells <- Dyn.newMatrix (1,1) Cell.empty
rowHeader <- Header.new
columnHeader <- Header.new
filePath <- newIORef Nothing
gridIsSaved <- newIORef False
return $ Grid cells rowHeader columnHeader filePath gridIsSaved
(ModelToView.numDataRows toView)
(ModelToView.numDataColumns toView)
(ModelToView.markAsSavedAs toView)
(ModelToView.markAsUnsaved toView)
getCell :: CellCoord -> Grid -> IO Cell
getCell coord = Dyn.read coord . cells
getCellValue :: CellCoord -> Grid -> IO CellValue
getCellValue coord grid = Cell.value <$> getCell coord grid
setCellInput :: CellCoord -> String -> Grid -> IO [CellCoord]
setCellInput coord input grid = do
expr <- compile (parse input,coord) grid
setCell coord expr input grid
setCellExpr :: CellCoord -> CellExpr -> Grid -> IO [CellCoord]
setCellExpr coord expr grid = do
expr' <- compile (expr,coord) grid
setCell coord expr' ('=':show expr) grid
setCell :: CellCoord -> CellExpr -> String -> Grid -> IO [CellCoord]
setCell coord expr input grid = do
unsave grid
cell <- getCell coord grid
deleteDependenciesOf cell
let cell' = Cell.setExpression input expr cell
Dyn.write coord cell' $ cells grid
addDependenciesOf cell'
evaluate coord grid
where
onCoords coords f = forM_ coords $ \c -> updateCell c f grid
deleteDependenciesOf cell =
let onRef ref =
case ref of
Column a -> Header.deleteDependent coord a $ columnHeader grid
Row a -> Header.deleteDependent coord a $ rowHeader grid
_ -> do cs <- referencedCoords ref grid
onCoords cs $ Cell.deleteDependent coord
in forM_ (Cell.dependencies cell) onRef
addDependenciesOf cell =
let onRef ref =
case ref of
Column a -> Header.addDependent coord a $ columnHeader grid
Row a -> Header.addDependent coord a $ rowHeader grid
_ -> do cs <- referencedCoords ref grid
onCoords cs $ Cell.addDependent coord
in forM_ (Cell.dependencies cell) onRef
rewriteCell :: CellCoord -> Grid -> IO [CellCoord]
rewriteCell coord grid = do
cell <- getCell coord grid
if (not . null $ Cell.dependencies cell)
then setCellInput coord (Cell.input cell) grid else return []
rewriteAllCells :: Grid -> IO ()
rewriteAllCells grid = do
rows <- numViewGridRows grid
cols <- numViewGridCols grid
forM_ [0..rows-1] $ \r ->
forM_ [0..cols-1] $ \c -> rewriteCell (r,c) grid >> return ()
evaluate :: CellCoord -> Grid -> IO [CellCoord]
evaluate evalCoord grid =
let evaluateCell = Cell.evaluate $ referencedCellValue `flip` grid
evaluateHeaderDeps i getHeader = do
dependents <- Header.dependents i $ getHeader grid
concat <$> (forM dependents $ \d -> evaluate d grid)
evalRec (coord@(row,col)) = do
cell <- getCell coord grid
updateCellIO coord evaluateCell grid
changed1 <- forM (Cell.dependent cell) evalRec
changed2 <- evaluateHeaderDeps row rowHeader
changed3 <- evaluateHeaderDeps col columnHeader
return $ coord : ((concat changed1) ++ changed2 ++ changed3)
in
evalRec evalCoord
evaluateString :: String -> CellCoord -> Grid -> IO CellValue
evaluateString input coord grid = do
expr <- compile (parse input,coord) grid
Cell.evaluate (referencedCellValue `flip` grid)
(Cell.setExpression input expr Cell.empty)
>>= return . Cell.value
referencedCoords :: Reference -> Grid -> IO [CellCoord]
referencedCoords ref grid =
case ref of
Cell coord -> return [coord]
Range (from,to) -> return $ coordRange (from,to)
Column c -> do rows <- numViewGridRows grid
return $ coordRange ((0,c),(rows-1,c))
Row r -> do cols <- numViewGridCols grid
return $ coordRange ((r,0),(r,cols-1))
dependencyCoordsFromInput :: String -> Grid -> IO [CellCoord]
dependencyCoordsFromInput input grid =
let parsed = parse input
isValidRef ref =
case ref of NamedReference (NamedCell (SameRow _)) -> False
NamedReference (NamedCell (SameColumn _)) -> False
NamedReference _ -> True
ListExpr l -> and $ map isValidRef l
_ -> False
getDepCoords expr =
case expr of
Reference r -> referencedCoords r grid
ListExpr l -> concat <$> (forM l getDepCoords)
in
if not $ isValidRef parsed then return []
else referenceConversion (parsed,(-1,-1)) grid >>= getDepCoords
referencedCellValue :: Reference -> Grid -> IO CellValue
referencedCellValue ref grid = do
coord <- referencedCoords ref grid
case ref of
(Cell _) -> getCellValue (head coord) grid
_ -> ListValue <$> (forM coord $ getCellValue `flip` grid)
updateCellIO :: CellCoord -> (Cell -> IO Cell) -> Grid -> IO ()
updateCellIO coord f grid = unsave grid
>> (Dyn.updateIO coord f $ cells grid)
updateCell :: CellCoord -> (Cell -> Cell) -> Grid -> IO ()
updateCell coord f grid = unsave grid
>> (Dyn.update coord f $ cells grid)
resizeTo :: CellCoord -> Grid -> IO ()
resizeTo (r,c) = Dyn.resizeTo ((0,0),(r-1,c-1)) . cells
addRows,addColumns :: [String] -> Grid -> IO ()
addRows names grid = do
n <- numViewGridRows grid
forM_ (zip [n..] names) $ \(i,name) ->
Header.setLabel name i $ rowHeader grid
unsave grid >> rewriteAllCells grid
addColumns names grid = do
n <- numViewGridCols grid
forM_ (zip [n..] names) $ \(i,name) ->
Header.setLabel name i $ columnHeader grid
unsave grid >> rewriteAllCells grid
deleteColumns :: [Int] -> Grid -> IO ()
deleteColumns cols grid = do
Header.delete cols $ columnHeader grid
Dyn.deleteColumnsInMatrix cols $ cells grid
unsave grid >> rewriteAllCells grid
deleteRows :: [Int] -> Grid -> IO ()
deleteRows rows grid = do
Header.delete rows $ rowHeader grid
Dyn.deleteRowsInMatrix rows $ cells grid
unsave grid >> rewriteAllCells grid
insertRowBefore,insertColumnBefore :: String -> Int -> Grid -> IO ()
insertRowBefore name i grid = do
rows <- numViewGridRows grid
Header.insertLabelBefore name i (rows-1) $ rowHeader grid
Dyn.insertEmptyRowIntoMatrixBefore i (rows-1) $ cells grid
unsave grid >> rewriteAllCells grid
insertColumnBefore name i grid = do
cols <- numViewGridCols grid
Header.insertLabelBefore name i (cols-1) $ columnHeader grid
Dyn.insertEmptyColumnIntoMatrixBefore i (cols-1) $ cells grid
unsave grid >> rewriteAllCells grid
updateRowLabel,updateColumnLabel :: Int -> String -> Grid -> IO [CellCoord]
updateRowLabel i to grid = do
Header.setLabel to i $ rowHeader grid
changed1 <- do
cols <- numViewGridCols grid
rowDeps <- forM [0..cols] $ \c -> Cell.dependent <$> (getCell (i,c) grid)
forM (concat rowDeps) $ rewriteCell `flip` grid
changed2 <- do
headerDeps <- Header.dependents i $ rowHeader grid
forM headerDeps $ rewriteCell `flip` grid
unsave grid
return $ (concat changed1) ++ (concat changed2)
updateColumnLabel i to grid = do
Header.setLabel to i $ columnHeader grid
changed1 <- do
rows <- numViewGridRows grid
colDeps <- forM [0..rows] $ \r -> Cell.dependent <$> (getCell (r,i) grid)
forM (concat colDeps) $ rewriteCell `flip` grid
changed2 <- do
headerDeps <- Header.dependents i $ columnHeader grid
forM headerDeps $ rewriteCell `flip` grid
unsave grid
return $ (concat changed1) ++ (concat changed2)
rowIndex,columnIndex :: String -> Grid -> IO (Maybe Int)
rowIndex name grid = Header.index name $ rowHeader grid
columnIndex name grid = Header.index name $ columnHeader grid
labelsToCoord :: (String,String) -> Grid -> IO (Maybe CellCoord)
labelsToCoord (rowName,columnName) grid = do
row <- rowIndex rowName grid
col <- columnIndex columnName grid
return $ case (row,col) of
(Just r,Just c) -> Just (r,c)
_ -> Nothing
getFilePath :: Grid -> IO (Maybe FilePath)
getFilePath = readIORef . filePath
save :: FilePath -> Grid -> IO ()
save path grid = do
writeIORef (gridIsSaved grid) True
writeIORef (filePath grid) $ Just path
markAsSavedAs grid $ takeFileName path
unsave :: Grid -> IO ()
unsave grid = do
currentlySaved <- isSaved grid
writeIORef (gridIsSaved grid) False
when currentlySaved $ markAsUnsaved grid
isSaved :: Grid -> IO Bool
isSaved = readIORef . gridIsSaved
getColumnCells,getRowCells :: Int -> Grid -> IO [Cell]
getColumnCells i grid = do
rows <- numViewGridRows grid
forM [0..rows-1] $ \row -> getCell (row,i) grid
getRowCells i grid = do
cols <- numViewGridCols grid
forM [0..cols-1] $ \col -> getCell (i,col) grid
getColumnValues,getRowValues :: Int -> Grid -> IO [CellValue]
getColumnValues i grid = map Cell.value <$> getColumnCells i grid
getRowValues i grid = map Cell.value <$> getRowCells i grid
compile :: (CellExpr,CellCoord) -> Grid -> IO CellExpr
compile (expr,coord) grid = do
expr' <- referenceConversion (expr,coord) grid
referenceLoop <- checkReferenceLoop (expr',coord) grid
if referenceLoop then return $ CompileErrorExpr ReferenceLoop
else return expr'
checkReferenceLoop :: (CellExpr,CellCoord) -> Grid -> IO Bool
checkReferenceLoop (expr,coord) grid =
let searchOnDependencies deps = do
refCoords <- do cs <- forM deps $ referencedCoords `flip` grid
return $ concat cs
or <$> forM refCoords depthFirstSearch
depthFirstSearch coord' | coord' == coord = return True
depthFirstSearch coord' = do
deps <- Cell.dependencies <$> getCell coord' grid
searchOnDependencies deps
in
searchOnDependencies $ CellContent.dependencies expr
referenceConversion :: (CellExpr,CellCoord) -> Grid -> IO CellExpr
referenceConversion (expr,(row,col)) grid =
let convert (NamedCell (Named a)) =
fmap Cell <$> labelsToCoord a grid
convert (NamedCell (SameRow colName)) =
fmap (\c -> Cell (row,c)) <$> columnIndex colName grid
convert (NamedCell (SameColumn rowName)) =
fmap (\r -> Cell (r,col)) <$> rowIndex rowName grid
convert (NamedRange (a,b)) =
do a' <- convert $ NamedCell a
b' <- convert $ NamedCell b
return $ case (a',b') of
(Just (Cell x),Just (Cell y)) -> Just $ Range (x,y)
_ -> Nothing
convert (NamedColumn a) = fmap Column <$> columnIndex a grid
convert (NamedRow a) = fmap Row <$> rowIndex a grid
refConversion e = referenceConversion (e,(row,col)) grid
in case expr of
NamedReference ref ->
do ref' <- convert ref
case ref' of
Just r -> return $ Reference r
Nothing -> return $ CompileErrorExpr $ RefConversionError ref
ListExpr a -> ListExpr <$> forM a refConversion
UnaryOp s a -> UnaryOp s <$> refConversion a
BinaryOp s a b -> do a' <- refConversion a
b' <- refConversion b
return $ BinaryOp s a' b'
Call s a -> Call s <$> refConversion a
Sub a -> Sub <$> refConversion a
IfThenElse a b c -> do a' <- refConversion a
b' <- refConversion b
c' <- refConversion c
return $ IfThenElse a' b' c'
_ -> return expr