roman-numerals (empty) → 0.1
raw patch · 4 files changed
+255/−0 lines, 4 filesdep +basedep +mtlsetup-changed
Dependencies added: base, mtl
Files
- LICENSE +31/−0
- Setup.hs +3/−0
- Text/RomanNumerals.hs +194/−0
- roman-numerals.cabal +27/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright (c) 2009 Roel van Dijk++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * The names of contributors may not be used to endorse or promote+ products derived from this software without specific prior+ written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ Text/RomanNumerals.hs view
@@ -0,0 +1,194 @@+{-# LANGUAGE FlexibleContexts #-}++{-| Parsing and pretty printing of Roman numerals.++ This module provides functions for parsing and pretty printing Roman+ numerals. Because the notation of Roman numerals has varied through+ the centuries this package allows for some customisation using a+ configuration that is passed to the conversion functions. Exceptions+ are dealt with by wrapping the results of conversions in the error+ monad.+-}+module Text.RomanNumerals+ ( -- * Types+ NumeralConfig(..)+ -- * Sample configurations+ , modernRoman+ , simpleRoman+ -- * Pretty printing+ , convertTo+ , unsafeConvertTo+ , toRoman+ , unsafeToRoman+ -- * Parsing+ , convertFrom+ , fromRoman+ , unsafeConvertFrom+ , unsafeFromRoman+ ) where++import Control.Monad.Error+import qualified Data.List as L++-------------------------------------------------------------------------------+-- Types+-------------------------------------------------------------------------------++-- |A configuration with which the 'convertTo' and 'convertFrom'+-- functions can be parameterized.+data (Ord n, Num n) =>+ NumeralConfig n = NC { -- |The largest value that can be+ -- represented using this configuration.+ ncMax :: n+ -- |Symbol to represent the value 0. The+ -- Romans did not have a symbol for+ -- zero. If set to Nothing a+ -- 'convertFrom' 0 will throw an error.+ , ncZero :: Maybe String+ -- |A table of symbols and their numerical+ -- values. The table must be ordered with+ -- the largest symbols appearing+ -- first. If any symbol is the empty+ -- string then 'convertFrom' will be+ -- undefined. If any symbol in this table+ -- is associated with the value 0 both+ -- the convertTo and 'convertFrom'+ -- function will be undefined.+ , ncTable :: [(String, n)]+ }++-------------------------------------------------------------------------------+-- Default tables+-------------------------------------------------------------------------------++-- |Configuration for Roman numerals as they are commenly used+-- today. The value 0 is represented by the empty string. It can be+-- interpreted as not writing down a number. This configuration is+-- limited to the range [1..3999]. Larger numbers can be represented+-- using Roman numerals but you will need notations that are hard or+-- impossible to express using strings.+modernRoman :: (Ord n, Num n) => NumeralConfig n+modernRoman = NC { ncMax = 3999+ , ncZero = Just ""+ , ncTable = [ ("M", 1000)+ , ("CM", 900)+ , ("D", 500)+ , ("CD", 400)+ , ("C", 100)+ , ("XC", 90)+ , ("L", 50)+ , ("XL", 40)+ , ("X", 10)+ , ("IX", 9)+ , ("V", 5)+ , ("IV", 4)+ , ("I", 1)+ ]+ }++-- |Configuration for Roman numerals that do not use the rule that a+-- lower rank symbol can be placed before a higher rank symbol to+-- denote the difference between them. Thus a numeral like "IV" will+-- not be accepted or generated by this configuration.+simpleRoman :: (Ord n, Num n) => NumeralConfig n+simpleRoman = NC { ncMax = 3999+ , ncZero = Just ""+ , ncTable = [ ("M", 1000)+ , ("D", 500)+ , ("C", 100)+ , ("L", 50)+ , ("X", 10)+ , ("V", 5)+ , ("I", 1)+ ]+ }+++-------------------------------------------------------------------------------+-- Pretty printing+-------------------------------------------------------------------------------+++-- |Converts a number to a Roman numeral according to the given+-- configuration. Numbers which are out of bounds will cause+-- exceptions to be thrown. An exception will also be raised if no+-- representation is possible with the given configuration. If the+-- value of any symbol in the configuration is equal to 0 or a symbol+-- is the empty string this function is undefined.+convertTo :: (Ord n, Num n, MonadError String m) => NumeralConfig n -> n -> m String+convertTo nc n | n < 0 = throwError "Roman.convertTo: can't represent negative numbers"+ | n > maxN = throwError $ "Roman.convertTo: too large (max = " ++ (show maxN) ++ ")"+ | n == 0 = maybe (throwError "Roman.convertTo: no symbol for zero")+ return+ $ ncZero nc+ | otherwise = go n $ ncTable nc+ where maxN = ncMax nc+ go 0 _ = return ""+ go _ [] = throwError "Roman.convertTo: out of symbols"+ go n tab@(~(sym, val) : ts) | n >= val = liftM2 (++) (return sym) $ go (n - val) tab+ | otherwise = go n ts++-- |Like 'convertTo', but exceptions are promoted to errors.+unsafeConvertTo :: (Ord n, Num n) => NumeralConfig n -> n -> String+unsafeConvertTo nc = either error id . convertTo nc++-- |Converts a number to a modern Roman numeral. See 'convertTo' for+-- possible exceptions.+toRoman :: (Ord n, Num n, MonadError String m) => n -> m String+toRoman = convertTo modernRoman++-- |Like 'toRoman', but exceptions are promoted to errors.+unsafeToRoman :: (Ord n, Num n) => n -> String+unsafeToRoman = unsafeConvertTo modernRoman++-------------------------------------------------------------------------------+-- Parsing+-------------------------------------------------------------------------------++-- convertFrom nc xs == undefined when any symbol in the NumeralConfig+-- nc is associated with the value 0.++-- |Parses a string as a Roman numeral according to the given+-- configuration. An exception will be raised if the input is not a+-- valid numeral.+convertFrom :: (Ord n, Num n, MonadError String m) => NumeralConfig n -> String -> m n+convertFrom nc xs | maybe False (== xs) (ncZero nc) = return 0+ | otherwise = do n <- (go 0 (ncTable nc) xs)+ xs' <- convertTo nc n+ if xs == xs'+ then return n+ else throwError "Roman.convertFrom: invalid Roman numeral"+ where go n _ [] = return n+ go _ [] xs = throwError $ "Roman.convertFrom: can't parse: '" ++ (take 5 xs) ++ "'"+ go n tab@((sym, val) : ts) xs = maybe (go n ts xs)+ (go (n + val) tab)+ $ L.stripPrefix sym xs++-- |Like 'convertFrom', but exceptions are promoted to errors.+unsafeConvertFrom :: (Ord n, Num n) => NumeralConfig n -> String -> n+unsafeConvertFrom nc = either error id . convertFrom nc++-- |Parses a string as a modern Roman numeral. See 'convertFrom' for+-- possible exceptions.+fromRoman :: (Ord n, Num n, MonadError String m) => String -> m n+fromRoman = convertFrom modernRoman++-- |Like 'fromRoman', but exceptions are promoted to errors.+unsafeFromRoman :: (Ord n, Num n) => String -> n+unsafeFromRoman = unsafeConvertFrom modernRoman++-------------------------------------------------------------------------------+-- Properties+-------------------------------------------------------------------------------++printParseIsId :: (Ord n, Num n) => NumeralConfig n -> String -> Bool+printParseIsId nc xs = either (const True) id+ $ do n <- convertFrom nc xs+ xs' <- convertTo nc n+ return $ xs' == xs++parsePrintIsId :: (Ord n, Num n) => NumeralConfig n -> n -> Bool+parsePrintIsId nc n = either (const True) id+ $ do xs <- convertTo nc n+ n' <- convertFrom nc xs+ return $ n' == n
+ roman-numerals.cabal view
@@ -0,0 +1,27 @@+name: roman-numerals+version: 0.1+cabal-version: >= 1.6+build-type: Simple+stability: provisional+author: Roel van Dijk+maintainer: vandijk.roel@gmail.com+copyright: (c) 2009 Roel van Dijk+license: BSD3+license-file: LICENSE+category: Numerical, Parsing+synopsis: Parsing and pretty printing of Roman numerals+description:+ This library provides functions for parsing and pretty printing+ Roman numerals. Because the notation of Roman numerals has varied+ through the centuries this package allows for some customisation+ using a configuration that is passed to the conversion+ functions. Exceptions are dealt with by wrapping the results of+ conversions in the error monad.++library+ build-depends: base, mtl ==1.1.*+ exposed-modules: Text.RomanNumerals+ extensions: FlexibleContexts+++