In Haskell, I tried to implement a parser for expressions containing hyperoperations, and finally succeeded. Valid expressions shall contain:
Parentheses.
Nonnegative integers.
Addition represented by
+
. It has precedence of 6, and its associativity shall be exploited.Multiplication represented by
*
. It has precedence of 7, and its associativity shall be exploited.Exponentiation represented by
^
. It has precedence of 8.Tetration represented by
^^
. It has precedence of 9.Pentation represented by
^^^
. It has precedence of 10.ad infinitum.
I was struggling for months to implement this with ReadPrec
. It was truly a "Eureka" moment when I found that I have a workaround:
import Control.Monad
import Text.ParserCombinators.ReadPrec
import Text.Read
import Text.Read.Lex
hyperOp :: Int -> Integer -> Integer -> Integer
hyperOp 0 _ y = y + 1
hyperOp 1 x y = x + y
hyperOp 2 x y = x * y
hyperOp 3 x y = x ^ y
hyperOp n x 0 = 1
hyperOp n x y = hyperOp (n-1) x (hyperOp n x (y-1))
parseHyperOp :: Int -> ReadPrec Integer
parseHyperOp opn = parens . choice $
lift readDecP
: prec 6 (do
m <- step (parseHyperOp opn)
Symbol "+" <- lexP
n <- parseHyperOp opn
return (m + n))
: prec 7 (do
m <- step (parseHyperOp opn)
Symbol "*" <- lexP
n <- parseHyperOp opn
return (m * n))
: fmap (\c -> prec (7 + c) $ do
m <- step (parseHyperOp opn)
Symbol op <- lexP
guard (replicate c '^' == op)
n <- step (parseHyperOp opn)
return (hyperOp (2 + c) m n)
) [1 .. opn]
highestOp :: String -> Int
highestOp "" = 0
highestOp ('^':str) = let
(str1, str2) = span ('^'==) str
in max (1 + length str1) (highestOp str2)
highestOp (_ :str) = highestOp str
parseOp :: Prec -> ReadS Integer
parseOp c str = readPrec_to_S (parseHyperOp (highestOp str)) c str
The trick is to find the highest operation within the input string. That makes the choice
finite.
Still, I think it's quite ugly to use readPrec_to_S
. Can this be implemented without using readPrec_to_S
and co?
ReadS
and what is "and co"? And are you aware that a common way to parse arithmetic expressions is to use a stack? \$\endgroup\$parseHyperOp
is just an auxiliary function for implementingparseOp
, and is not to be used on its own. I tried implementingparseOp
without usingreadPrec_to_S
andreadS_to_Prec
because using them might cause potential runtime errors thereafter (say, by invokinggather
). And I don't know whether Haskell has stacks. \$\endgroup\$