koneko-0.0.2: src/Koneko/Misc.hs
-- -- ; {{{1
--
-- File : Koneko/Misc.hs
-- Maintainer : Felix C. Stegerman <flx@obfusk.net>
-- Date : 2020-11-11
--
-- Copyright : Copyright (C) 2020 Felix C. Stegerman
-- Version : v0.0.1
-- License : GPLv3+
--
-- -- ; }}}1
{-# LANGUAGE OverloadedStrings #-}
module Koneko.Misc (
Parser, isIdent, pIdent, pIdent_, pInt, pFloat, isSpaceOrComma,
lexeme, symbol, speof, sp, sp1, spaceOrComment, prompt, firstJust,
parseMaybe
) where
import Data.Char (isSpace)
import Data.Functor
import Data.List ((\\))
import Data.Maybe (isJust, maybeToList)
import Data.Text (Text)
import Data.Void (Void)
import System.IO (hFlush, stdout)
import System.IO.Error (catchIOError, isEOFError)
import Text.Megaparsec
import Text.Megaparsec.Char hiding (space, space1)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Text.Megaparsec.Char.Lexer as L
-- parser: common --
type Parser = Parsec Void Text
-- {{{1
-- | Is the string an identifier?
--
-- NB: only partially checks whether it is a *valid* identifier (i.e.
-- whether it is not e.g. "nil").
--
-- >>> :set -XOverloadedStrings
-- >>> isIdent "nil" -- OOPS
-- True
-- >>> isIdent ""
-- False
-- >>> isIdent "42"
-- False
-- >>> isIdent "foo-bar'"
-- True
-- >>> isIdent "[子猫]"
-- True
-- >>> isIdent "'foo"
-- False
-- >>> isIdent "@$%^&*!"
-- True
-- >>> isIdent "["
-- False
-- >>> isIdent "]"
-- False
-- >>> isIdent "x]"
-- True
-- >>> isIdent "x["
-- False
-- >>> isIdent "x:"
-- False
--
-- }}}1
isIdent :: Text -> Bool
isIdent s = parses pIdent s && not (parses pInt s || parses pFloat s)
-- | NB: also matches float and int
pIdent :: Parser Text
pIdent = pIdent_ Nothing
-- TODO
pIdent_ :: Maybe Char-> Parser Text
pIdent_ ok = T.pack <$> (try a <|> b)
where
a = (:[]) <$> okChar <* notFollowedBy miChar
b = (:) <$> hdChar <*> some (try b1 <|> tlChar)
b1 = miChar <* notFollowedBy (speof <|> (bad >> speof))
okChar = letterChar <|> numberChar <|> symbolChar <|> oneOf specialChar
hdChar = okChar <|> oneOf brackets
miChar = hdChar <|> oneOf badStart
tlChar = okChar <|> oneOf bracketsC <|> good
bad = oneOf $ badEnd \\ maybeToList ok
good = oneOf $ goodTail ++ maybeToList ok
brackets, bracketsO, bracketsC, specialChar, badStart, goodTail,
badEnd :: [Char]
brackets = bracketsO ++ bracketsC
bracketsO = "({["
bracketsC = ")}]"
specialChar = "@%&*-_/?"
badStart = goodTail ++ ":"
goodTail = "'!"
badEnd = bracketsO ++ ":"
pInt :: Parser Integer
pInt = hex <|> bin <|> dec
where
hex = string "0x" *> L.hexadecimal
bin = string "0b" *> L.binary
dec = signed L.decimal
pFloat :: Parser Double
pFloat = signed L.float
-- parser: helpers --
signed :: Num a => Parser a -> Parser a
signed = L.signed $ return ()
isSpaceOrComma :: Char -> Bool
isSpaceOrComma c = isSpace c || c == ','
parses :: Parser a -> Text -> Bool
parses p = isJust . parseMaybe p
-- parser: utilities --
lexeme :: Parser a -> Parser a
lexeme p = p <* speof
symbol :: Text -> Parser Text
symbol = lexeme . string
speof, sp, sp1, spaceOrComment, space1 :: Parser ()
speof = sp1 <|> eof
sp = skipMany spaceOrComment
sp1 = skipSome spaceOrComment
spaceOrComment = space1 <|> (L.skipLineComment ";")
space1 = void $ takeWhile1P (Just "white space") isSpaceOrComma
-- utilities --
prompt :: Maybe Text -> IO (Maybe Text)
prompt x = (Just <$> f) `catchIOError` g
where
f = maybe (return ()) h x >> T.getLine
g e = if isEOFError e then return Nothing else ioError e
h s = do T.putStr s; hFlush stdout
-- miscellaneous --
firstJust :: Monad m => [m (Maybe a)] -> m (Maybe a)
firstJust [] = return Nothing
firstJust (x:xt) = x >>= maybe (firstJust xt) (return . Just)
-- vim: set tw=70 sw=2 sts=2 et fdm=marker :