hascal 2.0.0.1 → 3.0
raw patch · 4 files changed
+173/−217 lines, 4 filesdep +data-defaultdep +splitdep −HUnitdep −numbersdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: data-default, split
Dependencies removed: HUnit, numbers
Dependency ranges changed: base
API changes (from Hackage documentation)
- Hascal: eval :: (Read t, RealFloat t) => [(Char, Complex t -> Complex t -> Complex t)] -> String -> Either String (Complex t)
- Hascal: operators :: RealFloat t => [(Char, Complex t -> Complex t -> Complex t)]
- Hascal: prettyPrint :: (Show t, RealFloat t) => Complex t -> String
+ Hascal: Config :: [Operator t] -> [Constant t] -> Config t
+ Hascal: [constants] :: Config t -> [Constant t]
+ Hascal: [operators] :: Config t -> [Operator t]
+ Hascal: class Default a
+ Hascal: data Config t
+ Hascal: def :: Default a => a
+ Hascal: instance (GHC.Read.Read t, GHC.Float.RealFloat t) => Data.Default.Class.Default (Hascal.Config t)
+ Hascal: showHascal :: (Show t, Read t, RealFloat t) => Config t -> String -> String
+ Hascal: showNumber :: (Show t, RealFloat t) => Complex t -> String
- Hascal: hascal :: (Read t, RealFloat t) => String -> Either String (Complex t)
+ Hascal: hascal :: (Read t, RealFloat t) => Config t -> String -> Result t
Files
- Hascal.hs +158/−151
- Main.hs +3/−8
- Test.hs +0/−39
- hascal.cabal +12/−19
Hascal.hs view
@@ -1,175 +1,182 @@--- |Hascal is both a simple but extendable calculator library for Haskell--- and a command-line program using it.--- --- Also, its source code is a nice example for a minimalistic Haskell project.--- --- Some examples for the usage of the command-line program (using bash):--- --- >>> hascal 1+2--- 3.0--- --- >>> hascal 1+2*3-4/198^2--- 6.99989796959493929190898887868584838281807978777676--- --- Also, preceding exclamation marks mean that the following number is--- imaginary, that is, you have to multiply it with i. E.g.:--- --- >>> hascal _1 ^ 0.5--- !1.0--- --- And as you can see, negative numbers are preceded by an underscore.--- --- Although hascal itself doesn't understand brackets, you can use your shell--- to get that functionality, like this (using bash):--- --- >>> hascal e ^ $(hascal i*pi)--- -1.0--- --- Speaking of shells, you should consider that your shell might extend an--- asterisk (*) to the files at the current directory, like this:--- --- >>> echo *--- _darcs dist hascal.cabal Hascal.hs LICENSE Main.hs README.org Setup.hs--- --- That's why this might not work:--- --- >>> hascal 1 * 2--- Error. :(--- --- But you could do this instead:--- --- >>> hascal 1*2--- 2--- --- Or, you could do:--- --- >>> hascal '1*2'--- 2+-- |Hascal is a simple, minimalistic, tiny calculator library and program.+--+-- * Hascal only understands single-character operators.+-- * Hascal only understands infix operators.+-- * Hascal does not understand parantheses.+-- * @_@ is hard-coded as single prefix for negating numbers.+-- * By default, Hascal understands the operators @+@, @-@, @*@, @/@, @^@, and+-- @?@ (logarithm).+-- * By default, Hascal understands the constants @pi@, @e@, and @i@.+-- * Using Hascal as a library, you can add new operators and new constants+-- using a configuration data type.+--+-- The hascal executable program is easy to use. In a shell, type: -- --- Yeah, that's pretty much it. Hascal is really minimalistic.--- And I'm not planning to extend it much.-module Hascal (- -- * Operators- operators,- -- * Evaluators- eval,- hascal,- -- * Pretty Printers- prettyPrint+-- >>> hascal 1+2-3*4/5^6?7+-- -1.7263530417152033+--+-- Given a configuration, the 'hascal' function similarly evaluates an+-- expression of type 'String'. In a Haskell interpreter like GHCI, type:+--+-- >>> hascal def "1+2-3*4/5^6?7"+-- Right (0.2736469582847967 :+ 0.0)+--+-- >>> hascal def "1++2"+-- Left "Error at \"\"."++--------------------------------------------------------------------------------++module Hascal+ ( Config(..)+ , Data.Default.Default(..)+ , hascal+ , showHascal+ , showNumber ) where -import Control.Arrow (second)-import Data.Complex (Complex(..))-import Data.Functor ((<$>))-import Data.List (find)+-------------------------------------------------------------------------------- +import Data.Complex+import Data.Default+import Data.List+import Data.List.Split --- |'operators' is the default list of operators.--- --- An operator consists of one character and a function.--- --- 'operators' includes:--- --- * addition, represented by @\'+\'@,--- --- * subtraction, represented by @\'-\'@,--- --- * multiplication, represented by @\'*\'@,--- --- * division, represented by @\'\/\'@,--- --- * exponentiation, represented by @\'^\'@, and--- --- * logarithming (with flipped arguments, see below), represented by @\'?\'@,--- --- such that these laws are held:--- --- > (a - b == c) == (a == b + c)--- > (a / b == c) == (a == b * c)--- > (a ? b == c) == (a == b ^ c)-operators :: RealFloat t- => [(Char, Complex t -> Complex t -> Complex t)]-operators = [ ('+', (+))- , ('-', (-))- , ('/', (/))- , ('*', (*))- , ('^', (**))- , ('?', flip logBase)- ]+--------------------------------------------------------------------------------+-- | The result of an evaluation of a string is either a String containing an+-- error message or a complex number. +type Result t = Either String (Complex t) ++--------------------------------------------------------------------------------+-- | An operator is a pair of+-- * a 'Char' containing its identifier; and+-- * a binary numeral function.++type Operator t = (Char, Complex t -> Complex t -> Complex t)+++--------------------------------------------------------------------------------++-- | A constant is a pair of+-- * a 'String' containing its identifier; and+-- * a 'Complex' number containing its value.++type Constant t = (String, Complex t)+++--------------------------------------------------------------------------------++-- | A configuration. 'def' is the default configuration.++data Config t = Config+ { operators :: [Operator t] -- ^ A list of operators. The order in this list also determines the order of evaluation.+ , constants :: [Constant t] -- ^ A list of constants.+ }+++instance (Read t, RealFloat t) => Default (Config t) where+ def = Config+ [ ('+', (+))+ , ('-', (-))+ , ('*', (*))+ , ('/', (/))+ , ('^', (**))+ , ('?', flip logBase)+ ]+ [ ("pi", pi :+0)+ , ("e" , exp 1:+0)+ , ("i" , 0 :+1)+ ]+++--------------------------------------------------------------------------------++-- | Given a configuration and a 'String'-expression, returns a 'String'+-- containing the error message or the resulting complex number.++showHascal :: (Show t, Read t, RealFloat t)+ => Config t+ -> String+ -> String+showHascal conf s = either id showNumber (hascal conf s)+++--------------------------------------------------------------------------------++-- | Given a configuration and a 'String'-expression, returns 'Either' a+-- 'String' containing an error message; or a 'Complex' number.++hascal :: (Read t, RealFloat t) => Config t -> String -> Result t+hascal conf = calc (operators conf) (constants conf)+++--------------------------------------------------------------------------------++-- Given a list of operators, a list of constants, and a 'String'-expression,+-- returns 'Either' a 'String' containing an error message; or a 'Complex'+-- number.+ calc :: (Read t, RealFloat t)- => [(Char, Complex t -> Complex t -> Complex t)]+ => [Operator t]+ -> [Constant t] -> String- -> Either String (Complex t)-calc [] a- = readNumber a-calc l@((c,f):s) a- | z /= ""- = case (calc l y,calc l z) of- (Right n,Right m) -> Right (f m n)- (Left n,_ ) -> Left n- (_ ,Left m) -> Left m- | otherwise- = calc s a+ -> Result t+calc [] cs s = readNumber cs s+calc ((c, f) : ops) cs s =+ foldl1 apply (map (calc ops cs) (splitOn [c] s)) where- (y,z) = second (drop 1) $ break (==c) a+ apply x y = case (x, y) of+ (Right rx, Right ry) -> Right (f rx ry)+ (Left rx, _ ) -> Left rx+ (_ , Left ry) -> Left ry --- |'eval' gets a list of operators and a string containing a mathematical--- expression/term which only uses those operators listed in the first--- argument, and returns the result of that term.-eval :: (Read t, RealFloat t)- => [(Char, Complex t -> Complex t -> Complex t)] -- ^ list of operators- -> String -- ^ string containing term- -> Either String (Complex t) -- ^ just result, or nothing-eval = (. reverse) . calc+-------------------------------------------------------------------------------- +-- 'Either' returns a 'String' containing an error message or returns a+-- complex number represented in the given 'String' as a 'Constant' or as a+-- number. It’s a wrapper around 'findOrRead', adding the underscore prefix+-- operator for negation. --- Respects preceding exclamation marks and underscores before a number-readNumber :: (Read t, RealFloat t) => String -> Either String (Complex t)-readNumber x = case reverse x of- ('!':s) -> ((0:+1)*) <$> findOrRead s- ('_':s) -> negate <$> findOrRead s- ('-':s) -> Left ("Error at \"-" ++- s ++- "\".\n\nTo denote negative numbers, " ++- "use a preceding underscore instead.")- s -> findOrRead s+readNumber :: (Read t, RealFloat t)+ => [Constant t]+ -> String+ -> Result t+readNumber cs ('_':s) = fmap negate (findOrRead cs s)+readNumber cs s = findOrRead cs s --- Checks whether the string is readable as a mathematical constant before--- trying to read it as a number-findOrRead :: (Read t, Floating t) => String -> Either String (Complex t)-findOrRead s = maybe (maybeRead s) (Right . snd) $ find ((==s) . fst)- [("pi",pi :+0)- ,("e" ,exp 1:+0)- ,("i" ,0 :+1)- ]+-------------------------------------------------------------------------------- +-- 'Either' returns a 'String' containing an error message or returns a+-- complex number represented in the given 'String' as a 'Constant' or as a+-- number. --- Reads numbers-maybeRead :: (Read t, Num t) => String -> Either String (Complex t)-maybeRead s- | any (null . snd) (reads s :: [(Double,String)])- = Right (read s:+0)- | otherwise- = Left ("Error at \"" ++ s ++ "\".")+findOrRead :: (Read t, Floating t)+ => [Constant t]+ -> String+ -> Result t+findOrRead cs s = maybe (maybeRead s) (Right . snd) (find ((==s) . fst) cs)+ where+ maybeRead s+ | any (null . snd) (reads s :: [(Double,String)]) -- TODO: solve this better+ = Right (read s:+0)+ | otherwise+ = Left ("Error at \"" ++ s ++ "\".") --- |'hascal' is the default evaluator:--- --- @ hascal = 'eval' 'operators' @-hascal :: (Read t, RealFloat t) => String -> Either String (Complex t)-hascal = eval operators+-------------------------------------------------------------------------------- +-- | Show a 'Complex' number a little bit more human-readable by matching both+-- the real and the imaginery part against zero and one. --- |'prettyPrint' prints a number nicely.--- E.g., it doesn't show the real or imaginary part of the number if it's @0@.-prettyPrint :: (Show t, RealFloat t) => Complex t -> String-prettyPrint (r:+0) = show r-prettyPrint (r:+1) = prettyPrint (r:+0) ++ " + i"-prettyPrint (0:+i) = show i ++ "*i"-prettyPrint (r:+i) = show r ++ " + " ++ show i ++ "*i"+showNumber :: (Show t, RealFloat t)+ => Complex t+ -> String+showNumber (r:+0) = show r+showNumber (r:+1) = show r ++ " + i"+showNumber (0:+i) = show i ++ " * i"+showNumber (r:+i) = show r ++ " + " ++ show i ++ " * i"
Main.hs view
@@ -2,17 +2,12 @@ import Control.Monad import System.Environment-import Data.Number.CReal-import Data.Complex-+--import Data.Number.CReal+--import Data.Complex import Hascal main :: IO () main = do args <- getArgs- unless (null args) $- putStrLn $- either id prettyPrint $- (hascal :: String -> Either String (Complex CReal)) $- concat args+ unless (null args) $ putStrLn $ showHascal def $ concat args
− Test.hs
@@ -1,39 +0,0 @@-import Prelude hiding (succ)-import Test.HUnit hiding (test)-import Control.Monad (liftM)-import Data.Complex--import Hascal----main :: IO Int-main = liftM (\x -> errors x + failures x) (runTestTT tests)----tests :: Test-tests = TestList- [ succ "_5" ((-5):+0)- , succ "2+7" (9:+0)- , succ "3-4" ((-1):+0)- , succ "9*8" (72:+0)- , succ "9/3" (3:+0)- , succ "1+2-3-4+5" ((1+2-3-4+5):+0)- , succ "8/4+_2-96^2" ((8/4-2-96**2):+0)- ]---test :: (Read t, RealFloat t, Show t)- => String -> Maybe (Complex t) -> Test-test string value = TestLabel string $ TestCase $- assertEqual string value (hascal string)---succ :: (Read t, RealFloat t, Show t)- => String -> Complex t -> Test-succ string value = test string (Just value)---flop :: String -> t -> Test-flop string value = test string Nothing
hascal.cabal view
@@ -1,5 +1,5 @@ name: hascal-version: 2.0.0.1+version: 3.0 synopsis: A minimalistic but extensible and precise calculator description: Hascal is both a simple but extendable calculator library for Haskell as well as a command-line program using this library.@@ -11,38 +11,31 @@ Hascal also supports complex numbers. Hascal can work at an arbitrary precision. However, Hascal does not support parenthesis.-stability: provisional+stability: experimental category: Math, Console, Tools, Utility, Utils, Parsing-homepage: http://darcsden.com/mekeor/hascal-bug-reports: http://darcsden.com/mekeor/hascal/issues+homepage: https://github.com/mekeor/hascal+bug-reports: https://github.com/mekeor/hascal/issues license: GPL license-file: LICENSE-copyright: 2012 Mekeor Melire-author: Mekeor Melire <mekeor.melire@googlemail.com>-maintainer: Mekeor Melire <mekeor.melire@googlemail.com>-tested-with: GHC ==7.4.1+copyright: 2017 Mekeor Melire+author: Mekeor Melire <mekeor.melire@gmail.com>+maintainer: Mekeor Melire <mekeor.melire@gmail.com> cabal-version: >= 1.8 build-type: Simple-+ source-repository head- type: darcs- location: http://hub.darcs.net/mekeor/hascal+ type: git+ location: https://github.com/mekeor/hascal -test-suite test- type: exitcode-stdio-1.0- main-is: Test.hs- build-depends: HUnit ==1.2.*- - library- build-depends: base >2 && <5+ build-depends: base == 4.* , data-default == 0.7.*, split == 0.2.* exposed-modules: Hascal ghc-options: -Wall executable hascal- build-depends: base >2 && <5, numbers >= 3000+ build-depends: base == 4.* , data-default == 0.7.*, split == 0.2.* main-is: Main.hs ghc-options: -Wall