{-# 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