2015-08-03 10:19:23 +03:00
|
|
|
|
--
|
2015-08-30 13:00:07 +03:00
|
|
|
|
-- QuickCheck tests for Megaparsec's lexer.
|
2015-08-03 10:19:23 +03:00
|
|
|
|
--
|
2016-01-09 15:56:33 +03:00
|
|
|
|
-- Copyright © 2015–2016 Megaparsec contributors
|
2015-08-03 10:19:23 +03:00
|
|
|
|
--
|
|
|
|
|
-- 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.
|
|
|
|
|
--
|
2015-10-30 16:41:21 +03:00
|
|
|
|
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS “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 HOLDERS 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.
|
2015-08-03 10:19:23 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
{-# LANGUAGE TupleSections #-}
|
|
|
|
|
|
2015-08-30 13:00:07 +03:00
|
|
|
|
module Lexer (tests) where
|
|
|
|
|
|
2015-09-13 18:00:39 +03:00
|
|
|
|
import Control.Applicative (empty)
|
|
|
|
|
import Control.Monad (void)
|
|
|
|
|
import Data.Char
|
|
|
|
|
( readLitChar
|
|
|
|
|
, showLitChar
|
|
|
|
|
, isDigit
|
|
|
|
|
, isAlphaNum
|
|
|
|
|
, isSpace
|
|
|
|
|
, toLower )
|
2015-09-22 12:09:40 +03:00
|
|
|
|
import Data.List (findIndices, isInfixOf, find)
|
2016-01-09 14:41:18 +03:00
|
|
|
|
import Data.Maybe
|
2015-09-11 14:15:46 +03:00
|
|
|
|
import Numeric (showInt, showHex, showOct, showSigned)
|
2015-08-03 10:19:23 +03:00
|
|
|
|
|
|
|
|
|
import Test.Framework
|
2015-08-30 13:00:07 +03:00
|
|
|
|
import Test.Framework.Providers.QuickCheck2 (testProperty)
|
|
|
|
|
import Test.QuickCheck
|
2015-08-03 10:19:23 +03:00
|
|
|
|
|
2015-09-11 14:15:46 +03:00
|
|
|
|
import Text.Megaparsec.Error
|
2015-08-30 13:00:07 +03:00
|
|
|
|
import Text.Megaparsec.Lexer
|
2015-09-13 18:00:39 +03:00
|
|
|
|
import Text.Megaparsec.Pos
|
2015-09-11 14:15:46 +03:00
|
|
|
|
import Text.Megaparsec.Prim
|
2015-09-13 18:00:39 +03:00
|
|
|
|
import Text.Megaparsec.String
|
2015-09-12 14:06:58 +03:00
|
|
|
|
import qualified Text.Megaparsec.Char as C
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
import Prim () -- 'Arbitrary' instance for 'SourcePos'
|
2015-09-11 14:15:46 +03:00
|
|
|
|
import Util
|
2015-08-03 10:19:23 +03:00
|
|
|
|
|
2015-09-30 20:30:50 +03:00
|
|
|
|
#if !MIN_VERSION_base(4,8,0)
|
2016-01-09 14:41:18 +03:00
|
|
|
|
import Control.Applicative ((<$>), (<*), (<*>), (<$))
|
2015-09-30 20:30:50 +03:00
|
|
|
|
#endif
|
|
|
|
|
|
2015-08-03 10:19:23 +03:00
|
|
|
|
tests :: Test
|
2015-08-30 13:00:07 +03:00
|
|
|
|
tests = testGroup "Lexer"
|
2015-10-28 14:51:35 +03:00
|
|
|
|
[ testProperty "space combinator" prop_space
|
|
|
|
|
, testProperty "symbol combinator" prop_symbol
|
|
|
|
|
, testProperty "symbol' combinator" prop_symbol'
|
2016-01-09 14:41:18 +03:00
|
|
|
|
, testProperty "indentLevel" prop_indentLevel
|
2015-10-28 14:51:35 +03:00
|
|
|
|
, testProperty "indentGuard combinator" prop_indentGuard
|
2016-01-09 14:41:18 +03:00
|
|
|
|
, testProperty "nonIndented combinator" prop_nonIndented
|
|
|
|
|
, testProperty "indentBlock combinator" prop_indentBlock
|
|
|
|
|
, testProperty "indentBlock (many)" prop_indentMany
|
2015-10-28 14:51:35 +03:00
|
|
|
|
, testProperty "charLiteral" prop_charLiteral
|
|
|
|
|
, testProperty "integer" prop_integer
|
|
|
|
|
, testProperty "decimal" prop_decimal
|
|
|
|
|
, testProperty "hexadecimal" prop_hexadecimal
|
|
|
|
|
, testProperty "octal" prop_octal
|
|
|
|
|
, testProperty "float 0" prop_float_0
|
|
|
|
|
, testProperty "float 1" prop_float_1
|
|
|
|
|
, testProperty "number 0" prop_number_0
|
|
|
|
|
, testProperty "number 1" prop_number_1
|
|
|
|
|
, testProperty "number 2 (signed)" prop_number_2
|
|
|
|
|
, testProperty "signed" prop_signed ]
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
-- White space
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
mkWhiteSpace :: Gen String
|
|
|
|
|
mkWhiteSpace = concat <$> listOf whiteUnit
|
|
|
|
|
where whiteUnit = oneof [whiteChars, whiteLine, whiteBlock]
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
mkSymbol :: Gen String
|
|
|
|
|
mkSymbol = (++) <$> symbolName <*> whiteChars
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
mkIndent :: String -> Int -> Gen String
|
2016-01-10 13:53:02 +03:00
|
|
|
|
mkIndent x n = (++) <$> mkIndent' x n <*> eol
|
|
|
|
|
where eol = frequency [(5, return "\n"), (1, listOf1 (return '\n'))]
|
|
|
|
|
|
|
|
|
|
mkIndent' :: String -> Int -> Gen String
|
|
|
|
|
mkIndent' x n = concat <$> sequence [spc, sym, tra]
|
2016-01-09 14:41:18 +03:00
|
|
|
|
where spc = frequency [(5, vectorOf n itm), (1, listOf itm)]
|
|
|
|
|
tra = listOf itm
|
|
|
|
|
itm = elements " \t"
|
|
|
|
|
sym = return x
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2015-09-13 18:00:39 +03:00
|
|
|
|
whiteChars :: Gen String
|
2016-01-09 14:41:18 +03:00
|
|
|
|
whiteChars = listOf (elements "\t\n ")
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2015-09-13 18:00:39 +03:00
|
|
|
|
whiteLine :: Gen String
|
|
|
|
|
whiteLine = commentOut <$> arbitrary `suchThat` goodEnough
|
|
|
|
|
where commentOut x = "//" ++ x ++ "\n"
|
|
|
|
|
goodEnough x = '\n' `notElem` x
|
|
|
|
|
|
|
|
|
|
whiteBlock :: Gen String
|
|
|
|
|
whiteBlock = commentOut <$> arbitrary `suchThat` goodEnough
|
|
|
|
|
where commentOut x = "/*" ++ x ++ "*/"
|
|
|
|
|
goodEnough x = not $ "*/" `isInfixOf` x
|
|
|
|
|
|
|
|
|
|
symbolName :: Gen String
|
|
|
|
|
symbolName = listOf $ arbitrary `suchThat` isAlphaNum
|
|
|
|
|
|
|
|
|
|
sc :: Parser ()
|
|
|
|
|
sc = space (void C.spaceChar) l b
|
|
|
|
|
where l = skipLineComment "//"
|
|
|
|
|
b = skipBlockComment "/*" "*/"
|
|
|
|
|
|
|
|
|
|
sc' :: Parser ()
|
|
|
|
|
sc' = space (void $ C.oneOf " \t") empty empty
|
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
prop_space :: Property
|
|
|
|
|
prop_space = forAll mkWhiteSpace (checkParser p r)
|
2015-09-13 18:00:39 +03:00
|
|
|
|
where p = sc
|
|
|
|
|
r = Right ()
|
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
prop_symbol :: Maybe Char -> Property
|
|
|
|
|
prop_symbol t = forAll mkSymbol $ \s ->
|
|
|
|
|
parseSymbol (symbol sc) id s t
|
2015-09-13 18:00:39 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
prop_symbol' :: Maybe Char -> Property
|
|
|
|
|
prop_symbol' t = forAll mkSymbol $ \s ->
|
|
|
|
|
parseSymbol (symbol' sc) (fmap toLower) s t
|
2015-09-13 18:00:39 +03:00
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
parseSymbol
|
|
|
|
|
:: (String -> Parser String)
|
|
|
|
|
-> (String -> String)
|
|
|
|
|
-> String
|
|
|
|
|
-> Maybe Char
|
|
|
|
|
-> Property
|
2015-09-13 18:00:39 +03:00
|
|
|
|
parseSymbol p' f s' t = checkParser p r s
|
|
|
|
|
where p = p' (f g)
|
2015-09-23 08:26:17 +03:00
|
|
|
|
r | g == s || isSpace (last s) = Right g
|
2015-09-13 18:00:39 +03:00
|
|
|
|
| otherwise = posErr (length s - 1) s [uneCh (last s), exEof]
|
|
|
|
|
g = takeWhile (not . isSpace) s
|
2016-01-09 14:41:18 +03:00
|
|
|
|
s = s' ++ maybeToList t
|
|
|
|
|
|
|
|
|
|
-- Indentation
|
|
|
|
|
|
|
|
|
|
prop_indentLevel :: SourcePos -> Property
|
|
|
|
|
prop_indentLevel pos = p /=\ sourceColumn pos
|
|
|
|
|
where p = setPosition pos >> indentLevel
|
|
|
|
|
|
|
|
|
|
prop_indentGuard :: NonNegative Int -> Property
|
|
|
|
|
prop_indentGuard n =
|
|
|
|
|
forAll ((,,) <$> mki <*> mki <*> mki) $ \(l0,l1,l2) ->
|
|
|
|
|
let r | getCol l0 <= 1 = posErr 0 s ii
|
|
|
|
|
| getCol l1 /= getCol l0 = posErr (getIndent l1 + g 1) s ii
|
|
|
|
|
| getCol l2 <= getCol l0 = posErr (getIndent l2 + g 2) s ii
|
2015-09-13 18:00:39 +03:00
|
|
|
|
| otherwise = Right ()
|
2016-01-09 14:41:18 +03:00
|
|
|
|
fragments = [l0,l1,l2]
|
|
|
|
|
g x = sum (length <$> take x fragments)
|
|
|
|
|
s = concat fragments
|
|
|
|
|
in checkParser p r s
|
|
|
|
|
where mki = mkIndent sbla (getNonNegative n)
|
|
|
|
|
p = ip (> 1) >>= \x -> sp >> ip (== x) >> sp >> ip (> x) >> sp >> sc
|
|
|
|
|
ip = indentGuard sc
|
|
|
|
|
sp = void (symbol sc' sbla <* C.eol)
|
|
|
|
|
|
|
|
|
|
prop_nonIndented :: Property
|
|
|
|
|
prop_nonIndented = forAll (mkIndent sbla 0) $ \s ->
|
|
|
|
|
let i = getIndent s
|
|
|
|
|
r | i == 0 = Right sbla
|
|
|
|
|
| otherwise = posErr i s ii
|
|
|
|
|
in checkParser p r s
|
|
|
|
|
where p = nonIndented sc (symbol sc sbla)
|
|
|
|
|
|
|
|
|
|
prop_indentBlock :: Maybe (Positive Int) -> Property
|
|
|
|
|
prop_indentBlock mn' = forAll mkBlock $ \(l0,l1,l2,l3,l4) ->
|
|
|
|
|
let r | getCol l1 <= getCol l0 =
|
|
|
|
|
posErr (getIndent l1 + g 1) s [uneCh (head sblb), exEof]
|
|
|
|
|
| isJust mn && getCol l1 /= ib =
|
|
|
|
|
posErr (getIndent l1 + g 1) s ii
|
|
|
|
|
| getCol l2 <= getCol l1 =
|
|
|
|
|
posErr (getIndent l2 + g 2) s ii
|
|
|
|
|
| getCol l3 == getCol l2 =
|
|
|
|
|
posErr (getIndent l3 + g 3) s [uneCh (head sblb), exStr sblc]
|
|
|
|
|
| getCol l3 <= getCol l0 =
|
|
|
|
|
posErr (getIndent l3 + g 3) s [uneCh (head sblb), exEof]
|
|
|
|
|
| getCol l3 /= getCol l1 =
|
|
|
|
|
posErr (getIndent l3 + g 3) s ii
|
|
|
|
|
| getCol l4 <= getCol l3 =
|
|
|
|
|
posErr (getIndent l4 + g 4) s ii
|
|
|
|
|
| otherwise = Right (sbla, [(sblb, [sblc]), (sblb, [sblc])])
|
|
|
|
|
fragments = [l0,l1,l2,l3,l4]
|
|
|
|
|
g x = sum (length <$> take x fragments)
|
|
|
|
|
s = concat fragments
|
|
|
|
|
in checkParser p r s
|
|
|
|
|
where mkBlock = do
|
|
|
|
|
l0 <- mkIndent sbla 0
|
|
|
|
|
l1 <- mkIndent sblb ib
|
|
|
|
|
l2 <- mkIndent sblc (ib + 2)
|
|
|
|
|
l3 <- mkIndent sblb ib
|
2016-01-10 13:53:02 +03:00
|
|
|
|
l4 <- mkIndent' sblc (ib + 2)
|
2016-01-09 14:41:18 +03:00
|
|
|
|
return (l0,l1,l2,l3,l4)
|
|
|
|
|
p = lvla
|
|
|
|
|
lvla = indentBlock sc $ IndentMany mn (l sbla) lvlb <$ b sbla
|
|
|
|
|
lvlb = indentBlock sc $ IndentSome Nothing (l sblb) lvlc <$ b sblb
|
|
|
|
|
lvlc = indentBlock sc $ IndentNone sblc <$ b sblc
|
|
|
|
|
b = symbol sc'
|
|
|
|
|
l x = return . (x,)
|
|
|
|
|
mn = getPositive <$> mn'
|
|
|
|
|
ib = fromMaybe 2 mn
|
|
|
|
|
|
|
|
|
|
prop_indentMany :: Property
|
2016-01-10 13:53:02 +03:00
|
|
|
|
prop_indentMany = forAll (mkIndent sbla 0) (checkParser p r)
|
2016-01-09 14:41:18 +03:00
|
|
|
|
where r = Right (sbla, [])
|
|
|
|
|
p = lvla
|
|
|
|
|
lvla = indentBlock sc $ IndentMany Nothing (l sbla) lvlb <$ b sbla
|
|
|
|
|
lvlb = b sblb
|
|
|
|
|
b = symbol sc'
|
|
|
|
|
l x = return . (x,)
|
|
|
|
|
|
|
|
|
|
getIndent :: String -> Int
|
|
|
|
|
getIndent = length . takeWhile isSpace
|
|
|
|
|
|
|
|
|
|
getCol :: String -> Int
|
|
|
|
|
getCol x = sourceColumn $
|
|
|
|
|
updatePosString defaultTabWidth (initialPos "") $ take (getIndent x) x
|
|
|
|
|
|
|
|
|
|
sbla, sblb, sblc :: String
|
|
|
|
|
sbla = "xxx"
|
|
|
|
|
sblb = "yyy"
|
|
|
|
|
sblc = "zzz"
|
|
|
|
|
|
|
|
|
|
ii :: [Message]
|
|
|
|
|
ii = [msg "incorrect indentation"]
|
|
|
|
|
|
|
|
|
|
-- Character and string literals
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
|
|
|
|
prop_charLiteral :: String -> Bool -> Property
|
|
|
|
|
prop_charLiteral t i = checkParser charLiteral r s
|
|
|
|
|
where b = listToMaybe $ readLitChar s
|
|
|
|
|
(h, g) = fromJust b
|
|
|
|
|
r | isNothing b = posErr 0 s $ exSpec "literal character" :
|
|
|
|
|
[ if null s then uneEof else uneCh (head s) ]
|
|
|
|
|
| null g = Right h
|
|
|
|
|
| otherwise = posErr l s [uneCh (head g), exEof]
|
|
|
|
|
l = length s - length g
|
|
|
|
|
s = if null t || i then t else showLitChar (head t) (tail t)
|
|
|
|
|
|
2016-01-09 14:41:18 +03:00
|
|
|
|
-- Numbers
|
|
|
|
|
|
2015-09-11 14:15:46 +03:00
|
|
|
|
prop_integer :: NonNegative Integer -> Int -> Property
|
|
|
|
|
prop_integer n' i = checkParser integer r s
|
|
|
|
|
where (r, s) = quasiCorrupted n' i showInt "integer"
|
|
|
|
|
|
|
|
|
|
prop_decimal :: NonNegative Integer -> Int -> Property
|
|
|
|
|
prop_decimal n' i = checkParser decimal r s
|
2015-09-22 12:09:40 +03:00
|
|
|
|
where (r, s) = quasiCorrupted n' i showInt "decimal integer"
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
|
|
|
|
prop_hexadecimal :: NonNegative Integer -> Int -> Property
|
|
|
|
|
prop_hexadecimal n' i = checkParser hexadecimal r s
|
2015-09-22 12:09:40 +03:00
|
|
|
|
where (r, s) = quasiCorrupted n' i showHex "hexadecimal integer"
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
|
|
|
|
prop_octal :: NonNegative Integer -> Int -> Property
|
|
|
|
|
prop_octal n' i = checkParser octal r s
|
2015-09-22 12:09:40 +03:00
|
|
|
|
where (r, s) = quasiCorrupted n' i showOct "octal integer"
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
2015-09-12 14:06:58 +03:00
|
|
|
|
prop_float_0 :: NonNegative Double -> Property
|
|
|
|
|
prop_float_0 n' = checkParser float r s
|
|
|
|
|
where n = getNonNegative n'
|
|
|
|
|
r = Right n
|
|
|
|
|
s = show n
|
|
|
|
|
|
|
|
|
|
prop_float_1 :: Maybe (NonNegative Integer) -> Property
|
|
|
|
|
prop_float_1 n' = checkParser float r s
|
|
|
|
|
where r | isNothing n' = posErr 0 s [uneEof, exSpec "float"]
|
|
|
|
|
| otherwise = posErr (length s) s [ uneEof, exCh '.', exCh 'E'
|
|
|
|
|
, exCh 'e', exSpec "digit" ]
|
|
|
|
|
s = maybe "" (show . getNonNegative) n'
|
|
|
|
|
|
2015-10-28 14:51:35 +03:00
|
|
|
|
prop_number_0 :: Either (NonNegative Integer) (NonNegative Double) -> Property
|
|
|
|
|
prop_number_0 n' = checkParser number r s
|
|
|
|
|
where r = Right $ case n' of
|
2015-09-12 14:06:58 +03:00
|
|
|
|
Left x -> Left $ getNonNegative x
|
|
|
|
|
Right x -> Right $ getNonNegative x
|
2015-10-28 14:51:35 +03:00
|
|
|
|
s = either (show . getNonNegative) (show . getNonNegative) n'
|
|
|
|
|
|
|
|
|
|
prop_number_1 :: Property
|
|
|
|
|
prop_number_1 = checkParser number r s
|
|
|
|
|
where r = posErr 0 s [uneEof, exSpec "number"]
|
|
|
|
|
s = ""
|
|
|
|
|
|
|
|
|
|
prop_number_2 :: Either Integer Double -> Property
|
|
|
|
|
prop_number_2 n = checkParser p (Right n) s
|
|
|
|
|
where p = signed (hidden C.space) number
|
|
|
|
|
s = either show show n
|
2015-09-12 14:06:58 +03:00
|
|
|
|
|
|
|
|
|
prop_signed :: Integer -> Int -> Bool -> Property
|
|
|
|
|
prop_signed n i plus = checkParser p r s
|
|
|
|
|
where p = signed (hidden C.space) integer
|
|
|
|
|
r | i > length z = Right n
|
2015-09-22 12:09:40 +03:00
|
|
|
|
| otherwise = posErr i s $ uneCh '?' :
|
2016-01-09 14:41:18 +03:00
|
|
|
|
(if i <= 0 then [exCh '+', exCh '-'] else []) ++
|
|
|
|
|
[exSpec $ if isNothing . find isDigit $ take i s
|
|
|
|
|
then "integer"
|
|
|
|
|
else "rest of integer"] ++
|
|
|
|
|
[exEof | i > head (findIndices isDigit s)]
|
2015-09-12 14:06:58 +03:00
|
|
|
|
z = let bar = showSigned showInt 0 n ""
|
|
|
|
|
in if n < 0 || plus then bar else '+' : bar
|
|
|
|
|
s = if i <= length z then take i z ++ "?" ++ drop i z else z
|
2015-09-11 14:15:46 +03:00
|
|
|
|
|
|
|
|
|
quasiCorrupted :: NonNegative Integer -> Int
|
|
|
|
|
-> (Integer -> String -> String) -> String
|
|
|
|
|
-> (Either ParseError Integer, String)
|
|
|
|
|
quasiCorrupted n' i shower l = (r, s)
|
|
|
|
|
where n = getNonNegative n'
|
|
|
|
|
r | i > length z = Right n
|
2015-09-22 12:09:40 +03:00
|
|
|
|
| otherwise = posErr i s $ uneCh '?' :
|
2016-01-09 14:41:18 +03:00
|
|
|
|
[ exEof | i > 0 ] ++
|
|
|
|
|
[if i <= 0 || null l
|
|
|
|
|
then exSpec l
|
|
|
|
|
else exSpec $ "rest of " ++ l]
|
2015-09-11 14:15:46 +03:00
|
|
|
|
z = shower n ""
|
|
|
|
|
s = if i <= length z then take i z ++ "?" ++ drop i z else z
|