As a beginner exercise, I made small manual lexer that recognizes three types of inputs:
- integers:
/[-]?[0-9]+/
- strings, inside double quotes, with backslash escaping
- nil
using only Data.Char and Data.Maybe.
I would have wanted to do something that looks like this
parse s = tryParseInteger s
OR tryParseString s
OR tryParseNil
where each tryParse* would return a (Maybe MyToken)
and a failing case (= Nothing
) would continue to the next tryParse*.
But I didn't find a clean way to do it.
So here it goes:
import Data.Char;
import Data.Maybe;
data MyToken = MyNil
| MyNumber Int
| MyString String
deriving (Show)
tryParse :: String -> Maybe MyToken
tryParse "nil" = Just MyNil
tryParse (c : t)
-- Ignoring white space and parse the tail
| isSpace c = tryParse t
tryParse s = tryParseNumber s
tryParseNumber :: String -> Maybe MyToken
tryParseNumber s = case (parseNum s) of
Just v -> Just $ MyNumber v
Nothing -> tryParseString s
tryParseString :: String -> Maybe MyToken
tryParseString ('"':t) = fmap MyString (parseInsideString t)
tryParseString _ = Nothing
parseInsideString :: String -> Maybe String
parseInsideString ('\\':'"':cs) = fmap ('"':) (parseInsideString cs)
parseInsideString ('"':_) = Just ""
parseInsideString (c:cs) = fmap (c:) (parseInsideString cs)
parseInsideString _ = Nothing
parseNum :: String -> Maybe Int
parseNum ('-':xs) = fmap (\x -> -x) (parseNum xs)
parseNum cs@(c:_)
| isDigit c = foldl step Nothing cs
| otherwise = Nothing
where step acc c
| isDigit c = Just ((10 * fromMaybe 0 acc) + (digitToInt c))
| otherwise = acc
main = print $ tryParse "\"abcd\\\"efgh\""