module Parse where
import Control.Monad (when)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Char (ord)
import Data.List.NonEmpty (NonEmpty(..))
import Data.Text (Text)
import qualified Data.Text as T
import Text.Megaparsec
import Text.Megaparsec.Text
import Types
-- Okay, yeah, 'tokenize' would be better, sure
initialParserState :: Text -> State Text
initialParserState t = State
{ stateInput = t
, statePos = SourcePos "<file>" (unsafePos 1) (unsafePos 1) :| []
, stateTabWidth = unsafePos 8
}
pNextParsedObject :: State Text -> Either String (ParsedObject, State Text)
pNextParsedObject st = result
where result = case runParser' (pWhitespace *> pParsedObject) st of
(_, Left err) -> Left (show err)
(s, Right v) -> Right (v, s)
pWhitespace :: Parser ()
pWhitespace = skipMany spaceChar
specialChars :: String
specialChars = "()<>[]{}/% \t\r\n"
pParsedObject :: Parser ParsedObject
pParsedObject =
uncurry PORadix <$> try pRadix
<|> POReal <$> pReal
<|> POInteger <$> pInteger
<|> POString <$> pString
<|> POByteString <$> pByteString
<|> POSymbol <$> pSymbol
<|> POName <$> pName
pSign :: Num a => Parser (a -> a)
pSign = negate <$ char '-'
<|> id <$ char '+'
<|> pure id
pRadix :: Parser (Int, Integer)
pRadix = do
base <- read <$> some digitChar
_ <- char '#'
when (base < 2 || base > 36) $
fail ("Invalid base: " ++ show base)
num <- some (digitForBase base)
return (base, readAsBase base num)
readAsBase :: Num a => Int -> String -> a
readAsBase _ [] = error "[unreachable]"
readAsBase _ [c] = numValOf c
readAsBase b (c:cs) = (numValOf c * fromIntegral b) + readAsBase b cs
digitForBase :: Int -> Parser Char
digitForBase n = oneOf chars
where chars = take n ['0'..'9'] ++
take (n-1) ['A'..'Z'] ++
take (n-1) ['a'..'z']
pReal :: Parser Double
pReal = fail "unimplemented"
pInteger :: Parser Integer
pInteger = do
sign <- pSign
num <- some digitChar
return $ sign (read num)
pString :: Parser Text
pString = T.pack <$> (char '(' *> go)
where go =
"" <$ char ')'
<|> (++) <$> some (noneOf ['(',')','\\']) <*> go -- XXX: handle rest!
pByteString :: Parser ByteString
pByteString = char '<' *> (go <$> some hexDigitChar) <* char '>'
where go = BS.pack . toVals
toVals (x:y:xs) = (numValOf x * 16 + numValOf y) : toVals xs
toVals (x:[]) = [ numValOf x * 16 ]
toVals [] = []
numValOf :: Num b => Char -> b
numValOf n
| n >= 'a' && n <= 'z' = fromIntegral (ord n) - 0x57
| n >= 'A' && n <= 'Z' = fromIntegral (ord n) - 0x37
| otherwise = fromIntegral (ord n) - 0x30
pSymbol :: Parser Text
pSymbol = T.pack <$> (char '/' *> many (noneOf specialChars))
pName :: Parser Text
pName = T.pack <$> some (noneOf specialChars)