{-# LANGUAGE FlexibleContexts #-}{-# LANGUAGE NoImplicitPrelude #-}{-# LANGUAGE UnicodeSyntax #-}{-| 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.
-}moduleText.RomanNumerals(-- * TypesNumeralConfig(..)-- * Sample configurations,modernRoman,simpleRoman-- * Pretty printing,convertTo,unsafeConvertTo,toRoman,unsafeToRoman-- * Parsing,convertFrom,fromRoman,unsafeConvertFrom,unsafeFromRoman)where--------------------------------------------------------------------------------- Imports--------------------------------------------------------------------------------- baseimportControl.Monad((>>=),(>>),fail,liftM2,return)importData.Bool(Bool(False,True),otherwise)importData.Char(String)importData.Either(either)importData.Eq((==){- needed for desugaring -})importData.Function(($),const,id)importData.List(stripPrefix,take)importData.Maybe(Maybe(Just),maybe)importData.Ord(Ord,(<),(>))importPrelude(Num,(-),(+),error,fromInteger)importText.Show(show)-- base-unicode-symbolsimportData.Eq.Unicode((≡))importData.Function.Unicode((∘))importData.Monoid.Unicode((⊕))importData.Ord.Unicode((≥))-- mtlimportControl.Monad.Error(MonadError,throwError)--------------------------------------------------------------------------------- Types--------------------------------------------------------------------------------- |A configuration with which the 'convertTo' and 'convertFrom'-- functions can be parameterized.data(Ordn,Numn)⇒NumeralConfign=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∷MaybeString-- |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∷(Ordn,Numn)⇒NumeralConfignmodernRoman=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∷(Ordn,Numn)⇒NumeralConfignsimpleRoman=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∷(Ordn,Numn,MonadErrorStringm)⇒NumeralConfign→n→mStringconvertToncn|n<0=throwError"Roman.convertTo: can't represent negative numbers"|n>maxN=throwError$"Roman.convertTo: too large (max = "⊕(showmaxN)⊕")"|n≡0=maybe(throwError"Roman.convertTo: no symbol for zero")return$ncZeronc|otherwise=gon$ncTablencwheremaxN=ncMaxncgo0_=return""go_[]=throwError"Roman.convertTo: out of symbols"gontab@(~(sym,val):ts)|n≥val=liftM2(⊕)(returnsym)$go(n-val)tab|otherwise=gonts-- |Like 'convertTo', but exceptions are promoted to errors.unsafeConvertTo∷(Ordn,Numn)⇒NumeralConfign→n→StringunsafeConvertTonc=eithererrorid∘convertTonc-- |Converts a number to a modern Roman numeral. See 'convertTo' for-- possible exceptions.toRoman∷(Ordn,Numn,MonadErrorStringm)⇒n→mStringtoRoman=convertTomodernRoman-- |Like 'toRoman', but exceptions are promoted to errors.unsafeToRoman∷(Ordn,Numn)⇒n→StringunsafeToRoman=unsafeConvertTomodernRoman--------------------------------------------------------------------------------- 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∷(Ordn,Numn,MonadErrorStringm)⇒NumeralConfign→String→mnconvertFromncxs|maybeFalse(≡xs)(ncZeronc)=return0|otherwise=don←(go0(ncTablenc)xs)xs'←convertToncnifxs≡xs'thenreturnnelsethrowError"Roman.convertFrom: invalid Roman numeral"wheregon_[]=returnngo_[]xs=throwError$"Roman.convertFrom: can't parse: '"⊕(take5xs)⊕"'"gontab@((sym,val):ts)xs=maybe(gontsxs)(go(n+val)tab)$stripPrefixsymxs-- |Like 'convertFrom', but exceptions are promoted to errors.unsafeConvertFrom∷(Ordn,Numn)⇒NumeralConfign→String→nunsafeConvertFromnc=eithererrorid∘convertFromnc-- |Parses a string as a modern Roman numeral. See 'convertFrom' for-- possible exceptions.fromRoman∷(Ordn,Numn,MonadErrorStringm)⇒String→mnfromRoman=convertFrommodernRoman-- |Like 'fromRoman', but exceptions are promoted to errors.unsafeFromRoman∷(Ordn,Numn)⇒String→nunsafeFromRoman=unsafeConvertFrommodernRoman--------------------------------------------------------------------------------- Properties-------------------------------------------------------------------------------printParseIsId∷(Ordn,Numn)⇒NumeralConfign→String→BoolprintParseIsIdncxs=either(constTrue)id$don←convertFromncxsxs'←convertToncnreturn$xs'≡xsparsePrintIsId∷(Ordn,Numn)⇒NumeralConfign→n→BoolparsePrintIsIdncn=either(constTrue)id$doxs←convertToncnn'←convertFromncxsreturn$n'≡n