symbol-parser-0.1.0: src/Data/Type/Symbol/Natural.hs
{-# LANGUAGE UndecidableInstances #-}
{- | Parse 'Natural's from type-level 'Symbol's.
The type functions here may return errors. Use the provided and throw with
'TypeError', or wrap in your own error handling.
TODO
* Oh dear. Oh dear. Oh dear. If I want to get proper working composition with
meaningful errors, where the index pays attention to what we Drop... then I
need to write a type-level parser monad. That's it. These parsers need to
take in parser state (well, just character index is OK), and emit that state
on success. Oh dear. Oh no.
-}
module Data.Type.Symbol.Natural where
import Data.Type.Char.Digits
import GHC.TypeLits
import Data.Type.Bool ( type If )
import Data.Type.Equality ( type (==) )
import DeFun.Core ( type (~>), type App, type (@@) )
import Data.Type.Symbol ( type Length )
-- | Parse a 'Symbol' describing a binary (base 2) natural
-- to its 'Natural' value.
type ParseBinarySymbol sym = ParseSymbolDigits 2 ParseBinaryDigitSym sym
-- | Parse a 'Symbol' describing an octal (base 8) natural
-- to its 'Natural' value.
type ParseOctalSymbol sym = ParseSymbolDigits 8 ParseOctalDigitSym sym
-- | Parse a 'Symbol' describing a decimal (base 10) natural
-- to its 'Natural' value.
type ParseDecimalSymbol sym = ParseSymbolDigits 10 ParseDecimalDigitSym sym
-- | Parse a 'Symbol' describing a hexadecimal (base 16) natural
-- to its 'Natural' value.
type ParseHexSymbol sym = ParseSymbolDigits 16 ParseHexDigitSym sym
type family PrettyE e where
PrettyE 'EEmptySymbol = 'Text "empty symbol"
PrettyE ('EBadDigit base ch idx) = PrettyEBadDigit base ch idx
type family MapLeftPrettyE e where
MapLeftPrettyE ('Right a) = 'Right a
MapLeftPrettyE ('Left e) = 'Left (PrettyE e)
type family FromRightParseResult sym eab where
FromRightParseResult _ ('Right a) = a
FromRightParseResult sym ('Left e) = TypeError
( 'Text "error while parsing symbol: " :<>: 'Text sym
:$$: PrettyE e
)
data E = EBadDigit Natural Char Natural | EEmptySymbol
type PrettyEBadDigit base ch idx =
'Text "could not parse character as base "
:<>: 'ShowType base :<>: 'Text " digit"
:$$: 'ShowType ch :<>: 'Text " at index " :<>: 'ShowType idx
-- | Parse a symbol to a 'Natural' using the given base and digit parser.
type ParseSymbolDigits base tfDigitValue sym =
If (Length sym == 0) ('Left 'EEmptySymbol)
(WrapEBadDigit base
(ParseSymbolDigits' base tfDigitValue ('Just 0) '\0' 0 (Length sym - 1) (UnconsSymbol sym)))
type family WrapEBadDigit base eab where
WrapEBadDigit _ ('Right b) = 'Right b
WrapEBadDigit base ('Left '(ch, sym)) = 'Left ('EBadDigit base ch sym)
type ParseSymbolDigits'
:: Natural {- ^ base -}
-> (Char ~> Maybe Natural) {- ^ digit parser (defun symbol) -}
-> Maybe Natural {- ^ accumulator (Nothing means failure) -}
-> Char {- ^ previous parsed character -}
-> Natural {- ^ index in symbol -}
-> Natural {- ^ current exponent -}
-> Maybe (Char, Symbol) {- ^ remaining symbol -}
-> Either (Char, Natural) Natural
type family ParseSymbolDigits' base tfParseDigit mn prevCh idx expo mchsym where
ParseSymbolDigits' base tfParseDigit ('Just n) prevCh idx expo 'Nothing =
-- previous digit parsed, no more characters: all done
'Right n
-- note that the above will return 0 for empty symbols!
-- we could add an equation for that... but it would be inefficient, since
-- we only need to check once. instead, we handle that outside.
ParseSymbolDigits' base tfParseDigit 'Nothing prevCh idx expo mchsym =
-- digit parse error: emit problematic 'Char' and its index
-- the -1 is clumsy but the easiest way to achieve zero-indexing
-- safe: we always start with 'Just, so if we get here we're at least 1
'Left '(prevCh, idx-1)
ParseSymbolDigits' base tfParseDigit ('Just n) prevCh idx expo ('Just '(ch, sym)) =
-- previous digit parsed, characters remaining: parse next digit
ParseSymbolDigits' base tfParseDigit
(ParseSymbolDigits'Inc (base^expo) n (tfParseDigit @@ ch))
ch (idx+1) (expo-1) (UnconsSymbol sym)
-- little helper for incrementing accumulator, or failing to 'Nothing'
type family ParseSymbolDigits'Inc mult n mDigit where
ParseSymbolDigits'Inc mult n 'Nothing = 'Nothing
ParseSymbolDigits'Inc mult n ('Just digit) = 'Just (n + digit*mult)
type ParseBinaryDigitSym :: Char ~> Maybe Natural
data ParseBinaryDigitSym a
type instance App ParseBinaryDigitSym a = ParseBinaryDigit a
type ParseOctalDigitSym :: Char ~> Maybe Natural
data ParseOctalDigitSym a
type instance App ParseOctalDigitSym a = ParseOctalDigit a
type ParseDecimalDigitSym :: Char ~> Maybe Natural
data ParseDecimalDigitSym a
type instance App ParseDecimalDigitSym a = ParseDecimalDigit a
type ParseHexDigitSym :: Char ~> Maybe Natural
data ParseHexDigitSym a
type instance App ParseHexDigitSym a = ParseHexDigit a