------------------------------------------------------------------------------- -- -- An RPN calculator -- ------------------------------------------------------------------------------- package RPN () where {- This system lends itself well to the creation of new abstract types. It is difficult to implement in our current system. We support the following buttons on our calculator: 0-9 digits negate + addition - subtraction * multiplication / integer division ^ exponentiation clear pop Values are 32 bit signed integers. If we want to support real numbers (add a decimal point, use real division) then we will need a floating point representation. We may eventually want to support: ( open parenthesis ) close parenthesis In our implementation, negate is a real operator. After pressing it, the digits buffer is cleared and the negated value is put on the stack. One could imagine a negate operator which simply toggles the sign bit in the digits buffer. Equals currently just spits out the value on top, or error if it's not a number. One could imagine that the stack could be cleared, leaving this value as the only entry on the stack. In fact, let's do that. -} import RegFile import FIFOF sysRPN :: Module Calculator sysRPN = main ----------- -- TYPES -- ----------- data StackItem = Number Number -- | ArithOp ArithOp deriving (Eq,Bits) type Number = Int 32 --data ArithOp = Add | Sub | Mul | Div | Exp -- deriving (Eq,Bits) data Key = Digit0 | Digit1 | Digit2 | Digit3 | Digit4 | Digit5 | Digit6 | Digit7 | Digit8 | Digit9 | AddKey | SubKey | MulKey | DivKey | ExpKey | Negate | Equals | ClearAll | ClearLine deriving (Eq,Bits) --------------- -- FUNCTIONS -- --------------- isNothing :: Maybe a -> Bool isNothing Invalid = True isNothing _ = False getValue :: Maybe a -> a getValue (Valid x) = x getValue _ = error "attempting to get the value of Invalid" foreign integerMul :: Bit 32 -> Bit 32 -> Bit 32 foreign integerDiv :: Bit 32 -> Bit 32 -> Bit 32 foreign integerExp :: Bit 32 -> Bit 32 -> Bit 32 -- it would be great if we could use maxBound here somehow, -- but we want to do later constant arithmetic maxNumber :: (Bits Number n) => Integer maxNumber = 2 `exp` (valueOf n - 1) - 1 isDigitKey :: Key -> Bool -- isDigitKey k = (pack k) <= 10 -- or -- digitToNumber d = (d >= Digit0 && d <= Digit9) isDigitKey Digit0 = True isDigitKey Digit1 = True isDigitKey Digit2 = True isDigitKey Digit3 = True isDigitKey Digit4 = True isDigitKey Digit5 = True isDigitKey Digit6 = True isDigitKey Digit7 = True isDigitKey Digit8 = True isDigitKey Digit9 = True isDigitKey _ = False isNumber :: StackItem -> Bool isNumber (Number _) = True isNumber _ = False isBinaryArithKey :: Key -> Bool isBinaryArithKey AddKey = True isBinaryArithKey SubKey = True isBinaryArithKey MulKey = True isBinaryArithKey DivKey = True isBinaryArithKey ExpKey = True isBinaryArithKey _ = False -- the first number is the top of the stack applyBinaryOp :: Key -> StackItem -> StackItem -> Maybe StackItem applyBinaryOp AddKey (Number n2) (Number n1) = Valid (Number (n1 + n2)) applyBinaryOp SubKey (Number n2) (Number n1) = Valid (Number (n1 - n2)) applyBinaryOp MulKey (Number n2) (Number n1) = Valid (Number (unpack ((pack n1) `integerMul` (pack n2)))) applyBinaryOp DivKey (Number n2) (Number n1) = Valid (Number (unpack ((pack n1) `integerDiv` (pack n2)))) applyBinaryOp ExpKey (Number n2) (Number n1) = Valid (Number (unpack ((pack n1) `integerExp` (pack n2)))) applyBinaryOp _ _ _ = Invalid isUnaryArithKey :: Key -> Bool isUnaryArithKey Negate = True isUnaryArithKey _ = False applyUnaryOp :: Key -> StackItem -> Maybe StackItem applyUnaryOp Negate (Number n) = Valid (Number (negate n)) applyUnaryOp _ _ = Invalid digitToNumber :: Key -> Number -- digitToNumber d = zeroExtend (pack d) digitToNumber Digit0 = 0 digitToNumber Digit1 = 1 digitToNumber Digit2 = 2 digitToNumber Digit3 = 3 digitToNumber Digit4 = 4 digitToNumber Digit5 = 5 digitToNumber Digit6 = 6 digitToNumber Digit7 = 7 digitToNumber Digit8 = 8 digitToNumber Digit9 = 9 addDigit :: Number -> Key -> Number addDigit tot k = unpack ((pack tot) `integerMul` 10) + digitToNumber k emptyDigits :: Number emptyDigits = 0 ---------------- -- INTERFACES -- ---------------- -- first and second could be replaced by a function -- which takes a number of the element to be retrieved, -- and Invalid is a valid response for anything beyond the -- stack size. the applyOp functions could also somehow -- be generalized? interface Stack a = clear :: Action -- push Invalid to get an error push :: Maybe a -> Action pop :: Action first :: Maybe a second :: Maybe a applyUnaryOp :: (a -> Maybe a) -> Action applyBinaryOp :: (a -> a -> Maybe a) -> Action oneItem :: a -> Action error :: Bool -- full :: Bool -- empty :: Bool --------------- -- TEMPLATES -- --------------- mkStack :: (Bits a k) => Module (Stack a) mkStack = module arr :: RegFile (Bit 8) a arr <- mkRegFileFull size :: Reg (Bit 8) size <- mkReg 0 isError :: Reg Bool isError <- mkReg False interface clear = action { size := 0; isError := False; } push x = if (isError || size == maxBound || isNothing x) then action { isError := True; } else action { size := size + 1; arr.upd size (getValue x); } pop = if (isError || size == 0) then action { isError := True; } else action { size := size - 1; } first = if (isError || size == 0) then Invalid else Valid (arr.sub (size-1)) second = if (isError || size < 2) then Invalid else Valid (arr.sub (size-2)) applyUnaryOp op = let val = op (arr.sub (size-1)) in if (isError || size == 0 || isNothing val) then action { isError := True; } else action { arr.upd (size-1) (getValue val); } applyBinaryOp op = let val = op (arr.sub (size-1)) (arr.sub (size-2)) in if (isError || size < 2 || isNothing val) then action { isError := True; } else action { arr.upd (size-2) (getValue val); } oneItem x = action { isError := False; size := 1; arr.upd 0 x } error = isError ------------ -- SYSTEM -- ------------ interface Calculator = -- inputFifo :: InputFIFO Key -- outputFifo :: OutputFIFO (Maybe StackItem) inputFifo :: FIFOF Key outputFifo :: FIFOF (Maybe StackItem) main :: Module Calculator main = module -- input inputFifoS :: FIFOF Key inputFifoS <- mkFIFOF -- output outputFifoS :: FIFOF (Maybe StackItem) outputFifoS <- mkFIFOF -- storage currentDigits :: Reg Number currentDigits <- mkReg emptyDigits -- the stack stack :: Stack StackItem stack <- mkStack addRules $ mkCalcRules inputFifoS outputFifoS currentDigits stack interface inputFifo = inputFifoS outputFifo = outputFifoS mkCalcRules :: FIFOF Key -> FIFOF (Maybe StackItem) -> Reg Number -> Stack StackItem -> Rules mkCalcRules inputFifo outputFifo currentDigits stack = let keyPressed :: Bool keyPressed = inputFifo.notEmpty key :: Key key = inputFifo.first in rules "StackError": -- when the stack is in error, do nothing when keyPressed, stack.error, key /= ClearAll ==> action { inputFifo.deq } "ClearAll": when keyPressed, key == ClearAll ==> action { currentDigits := emptyDigits; stack.clear; inputFifo.deq; } "Digit": -- when a digit is pressed when keyPressed, isDigitKey key, currentDigits <= fromInteger ((maxNumber - 9) `div` 10) ==> action { currentDigits := addDigit currentDigits key; inputFifo.deq; }; -- if it's too many digits, ignore it when keyPressed, isDigitKey key, currentDigits > fromInteger ((maxNumber - 9) `div` 10) ==> action { inputFifo.deq } "ClearLine": -- when clearing a line when keyPressed, key == ClearLine, currentDigits > 0 ==> action { currentDigits := emptyDigits; inputFifo.deq; }; -- if the line is already empty, we pop the last value off the stack when keyPressed, key == ClearLine ==> action { stack.pop; inputFifo.deq; } "UnaryArithOp": -- when an arithmetic operator is pressed when keyPressed, isUnaryArithKey key, currentDigits == 0 ==> action { stack.applyUnaryOp (applyUnaryOp key); inputFifo.deq; }; -- if the buffer has digits in it when keyPressed, isUnaryArithKey key, currentDigits /= 0 ==> action { stack.push ((applyUnaryOp key) (Number currentDigits)); currentDigits := emptyDigits; inputFifo.deq; } "BinaryArithOp": -- when an arithmetic operator is pressed when keyPressed, isBinaryArithKey key, currentDigits == 0 ==> action { stack.applyBinaryOp (applyBinaryOp key); inputFifo.deq; }; -- if the buffer has digits in it when keyPressed, isBinaryArithKey key, currentDigits /= 0 ==> action { stack.applyUnaryOp ((applyBinaryOp key) (Number currentDigits)); currentDigits := emptyDigits; inputFifo.deq; } "Equals": -- the end of the computation when keyPressed, outputFifo.notFull, key == Equals, currentDigits == 0, Valid val@(Number v) <- stack.first ==> action { outputFifo.enq stack.first; stack.oneItem val; inputFifo.deq; }; -- the buffer has digits when keyPressed, outputFifo.notFull, key == Equals, currentDigits /= 0 ==> action { outputFifo.enq (Valid (Number currentDigits)); stack.oneItem (Number currentDigits); inputFifo.deq; }; -- the stack top is not a number when keyPressed, outputFifo.notFull, key == Equals, currentDigits == 0, (stack.first == Invalid) || (not (isNumber (getValue stack.first))) ==> action { outputFifo.enq Invalid; stack.push Invalid; -- signals an Error inputFifo.deq; }