> module IOutilities > (capitalizeWord, > centerString, > displayStringsInColumns, > getCookedLine, > integralFromString, > interpretBackspaces, > isEmptyLine, > leftJustify, > realFloatFromString, > rightJustify, > scientificFormatRealFloat, > significanceFormatRealFloat, > spaces, > standardFormatRealFloat, > trim, > trimRight) > where > import SequenceUtilities > (blocks, centerInField, concatWithSpacer, > leftJustifyWith, rightJustifyWith, > splitFromRight, reps, transpose) > import Char(isSpace) convert string denoting Integral number to class Integral > integralFromString :: (Integral i, Read i) => String -> i > integralFromString intStringWithOptionalSign = x > where > [(x, _)] = reads intStringWithOptionalSign convert string denoting RealFloat number to class RealFloat > realFloatFromString :: (RealFloat r, Read r) => String -> r > realFloatFromString floatString = x > where > [(x, _)] = reads(fixedUpFloatString ++ exponentAndAfter) > fixedUpFloatString > | null beforeDecimalPt = sign ++ "0" ++ decimalPtAndAfter > | null decimalPtAndAfter = sign ++ afterSign ++ ".0" > | otherwise = floatString > (beforeExponent, exponentAndAfter) = break atE floatString > (beforeSign, signAndAfter) = break (== '-') beforeExponent > (sign, afterSign) > | null signAndAfter = ("", beforeSign) > | otherwise = splitAt 1 signAndAfter > (beforeDecimalPt, decimalPtAndAfter) = break (== '.') afterSign > (decimalPt, afterDecimalPt) > | null decimalPtAndAfter = ("", beforeDecimalPt) > | otherwise = splitAt 1 decimalPtAndAfter > atE c = 'e' == toLower c deliver string denoting a RealFloat number in standard format to a given number of decimal places > standardFormatRealFloat :: RealFloat r => Int -> r -> String > standardFormatRealFloat numberOfDecimalPlaces x = > signPart ++ integerPart ++ "." ++ fractionPart > where > (signPart, significand, exponent) = componentsOfRealFloat x > roundedSignificand = > shiftInteger (exponent + numberOfDecimalPlaces) significand > (integerPart, fractionPart) = > splitFromRight numberOfDecimalPlaces (show roundedSignificand) deliver string denoting a RealFloat number in standard format to a given number of signficant digits > significanceFormatRealFloat :: RealFloat r => Int -> r -> String > significanceFormatRealFloat numberOfSignificantDigits x = > signPart ++ integerPart ++ "." ++ fractionPart > where > (signPart, significand, exponent) = > componentsOfRealFloatRounded numberOfSignificantDigits x > significandAsString = show significand > lengthOfSignificand = length significandAsString > paddedSignificandString > | exponent >= 0 > = leftJustifyWith '0' (exponent + lengthOfSignificand) > significandAsString > | otherwise > = rightJustifyWith '0' (max (-exponent) lengthOfSignificand) > significandAsString > (integerPart, fractionPart) = > splitFromRight (max 0 (-exponent)) paddedSignificandString deliver string denoting a RealFloat number in scientific notation to a given number of significant digits > scientificFormatRealFloat :: RealFloat r => Int -> r -> String > scientificFormatRealFloat numberOfSignificantDigits x = > signPart ++ integerPart ++ "." ++ fractionPart ++ > "E" ++ exponentSign ++ exponentPart > where > (signPart, significand, exponent) = > componentsOfRealFloatRounded numberOfSignificantDigits x > exponentShifted = exponent + length fractionPart > exponentPart = > (rightJustifyWith '0' 2 . show . abs) exponentShifted > exponentSign > | exponentShifted >= 0 = "+" > | exponentShifted < 0 = "-" > (integerPart, fractionPart) = splitAt 1 (show significand) break RealFloat number into sign/rounded-significand/exponent, where rounded-significand contains a specified number of digits > cr :: RealFloat r => Int -> r -> (String, Integer, Int) > cr = componentsOfRealFloatRounded > componentsOfRealFloatRounded :: > RealFloat r => Int -> r -> (String, Integer, Int) > componentsOfRealFloatRounded numberOfSignificantDigits x = > (signPart, roundedSignificand, exponentOfRoundedSignificand) > where > (signPart, significand, exponent) = componentsOfRealFloat x > numberOfDigitsInSignificand = length(show significand) > shift = numberOfSignificantDigits - numberOfDigitsInSignificand > roundedSignificand = shiftInteger shift significand > exponentOfRoundedSignificand > | roundedSignificand == 0 = 0 > | otherwise = exponent - shift break RealFloat number into sign/significand/exponent > c :: RealFloat r => r -> (String, Integer, Int) > c = componentsOfRealFloat > componentsOfRealFloat :: RealFloat r => r -> (String, Integer, Int) > componentsOfRealFloat x = > (signAsString, significand, decimalPointPosition) > where > (signAsString, unsignedPart) = > (span(`elem` ['-', ' ']) . stripParentheses . show) x > (integerPartAsString, fractionPlusExponent) = > span isDigit unsignedPart > (fractionPartWithDecimalPointMaybe, exponentPartWithE) = > break (`elem` ['e', 'E']) fractionPlusExponent > (decimalPoint, fractionPartAsString) = > span (== '.') fractionPartWithDecimalPointMaybe > (ePart, exponentAsStringWithSignMaybe) = > span (`elem` ['e', 'E']) exponentPartWithE > exponentAsString = dropWhile (== '+') exponentAsStringWithSignMaybe > exponent > | null exponentAsString = 0 > | otherwise = integralFromString exponentAsString > significandAsString = integerPartAsString ++ fractionPartAsString > significand = toInteger(integralFromString significandAsString) > decimalPointPosition = exponent - length fractionPartAsString remove all parentheses from string > stripParentheses :: String -> String > stripParentheses = filter(/= '(') . filter (/= ')') nearest integer to n*(10^s) using exact arithmetic Constraint: ignore s if n is zero > shiftInteger :: Int -> Integer -> Integer > shiftInteger s n > | n == 0 = 0 > | s >= 0 = n * 10^s > | otherwise = (n `div` 10^(-s - 1) + roundUpOrDown) `div` 10 > where > roundUpOrDown = 5 justify string within field of given width (deliver original string if given field-width is too narrow) > leftJustify, centerString, rightJustify :: > Int -> String -> String > rightJustify = rightJustifyWith ' ' > leftJustify = leftJustifyWith ' ' > centerString = centerInField ' ' string comprising a given number of spaces > spaces :: Int -> String > spaces numberOfSpaces = reps numberOfSpaces ' ' capitalize first character in string, make rest lower case > capitalizeWord :: String -> String > capitalizeWord w > | null w = "" > | otherwise = [toUpper firstLetter] ++ map toLower others > where > ([firstLetter], others) = splitAt 1 w deliver string that will display a sequence of strings as a sequence of pages, with strings appearing in sequence down successive columns > displayStringsInColumns :: > Int -> Int -> Int -> Int -> Int -> [String] -> String > displayStringsInColumns pageWidth gapBetweenPages stringsPerColumn > columnWidth gapBetweenColumns = > concatWithSpacer(reps gapBetweenPages '\n') . > map displayPage . map transpose . map(blocks stringsPerColumn) . > blocks stringsPerPage . map(leftJustify columnWidth) > where > numberOfColumns = (pageWidth + gapBetweenColumns) `div` > (columnWidth + gapBetweenColumns) > stringsPerPage = stringsPerColumn * numberOfColumns > displayPage = > unlines . map(concatWithSpacer(spaces gapBetweenColumns)) remove leading and trailing whitespace from a string > trim :: String -> String > trim = trimLeft . trimRight remove leading whitespace from a string > trimLeft :: String -> String > trimLeft = dropWhile isSpace remove trailing whitespace from a string > trimRight :: String -> String > trimRight = reverse . dropWhile isSpace . reverse deliver True iff argument contains no characters other than blanks > isEmptyLine :: String -> Bool > isEmptyLine = (=="") . dropWhile(==' ') retrieve line from keyboard and interpret backspaces > getCookedLine :: IO(String) > getCookedLine = > do > rawLine <- getLine > return(interpretBackspaces rawLine) interpret backspaces in string, delivering string free of BS > interpretBackspaces :: String -> String > interpretBackspaces = > reverse . foldl interpretIfBS [ ] > interpretIfBS :: String -> Char -> String > interpretIfBS [ ] '\b' = [ ] > interpretIfBS (c : cs) '\b' = cs > interpretIfBS cs c = [c] ++ cs