Agda-2.3.2.2: src/prototyping/mixfix/old/MixFix2.hs
module MixFix2 (main) where
import Control.Monad
import Data.List
import Data.Char
import System.Environment
import Utils.ReadP
import Utils.TestHelpers
import Utils.IO
import Prelude hiding (putStrLn, print)
------------------------------------------------------------------------
-- Parser combinators
-- @'intersperseP' inside ts@ parses the tokens @ts@, separated by
-- @inside@. The values returned by the @inside@ parser are returned.
intersperseP :: Eq token => ReadP token a -> [token] -> ReadP token [a]
intersperseP inside ts =
liftM rights $
sequence $
intersperse (liftM Right inside)
(map (liftM Left . char) ts)
where
rights [] = []
rights (Right x : xs) = x : rights xs
rights (Left _ : xs) = rights xs
-- Associativity.
data Assoc
= Lft -- ^ Left associative.
| Rght -- ^ Right associative.
| Non -- ^ Nonassociative.
deriving (Show, Eq)
-- Operator descriptions. Note that the inner parts of mixfix
-- operators are not included in these descriptions.
data Op a
= Bin (a -> a -> a) Assoc
-- ^ Binary operator with a function constructing an application
-- from the left and right arguments, and the associativity of the
-- operator.
| Pre (a -> a)
-- ^ Prefix operator.
| Post (a -> a)
-- ^ Postfix operator.
-- | Checks that the input, a sequence of terms (left) or operators
-- (right), is valid. If it is valid the result of applying the
-- operators to the terms is computed.
--
-- The sequence has to satisfy these properties:
--
-- * First zero or more postfix applications.
--
-- * Then either
--
-- * zero or one nonassociative infix applications,
--
-- * zero or more left associative infix applications, or
--
-- * zero or more right associative infix applications.
--
-- * And finally zero or more prefix applications.
--
-- * With the exception that there may not be both prefix and postfix
-- applications if there are zero infix applications.
--
-- As an example, assume that @-@ is prefix, @+@, @*@ and @/@ infix,
-- and @!@ postfix, all of the same precedence, with @+@ left
-- associative, @*@ right associative, and @/@ nonassociative. All
-- nonambiguous parses are OK, so
-- @- - 3@, @4 ! !@, @5 * - 3@, @5 ! + 2 + 3@ and @2 / - - 3@
-- are OK, whereas
-- @- 3 !@, @- 3 * 5@, @5 + 2 + ! 3@ and @2 / 3 / 4@
-- are not.
checkOpApps :: ReadP (Either a (Op a)) a
checkOpApps = atLeastOneMid +++ pres many1 +++ posts many
where
elem = do
Left x <- get
return x
ass Non = mzero
ass a = do
Right (Bin o a') <- get
guard (a == a')
return o
pres c = do
ps <- c (do Right (Pre o) <- get; return o)
e <- elem
return $ foldr ($) e ps
posts c = do
e <- elem
ps <- c (do Right (Post o) <- get; return o)
return $ foldr ($) e ps
atLeastOneMid = do
e1 <- posts many
Right (Bin o a) <- get
eos <- many (liftM2 (,) elem (ass a))
e2 <- pres many
return $ case a of
Lft -> let (es, os) = unzip eos
eos' = zip (es ++ [e2]) (o : os)
in foldl (\l (r, o) -> l `o` r) e1 eos'
_ -> foldr (\(l, o) r -> l `o` r) e2 ((e1, o) : eos)
-- Parses zero or more operator applications.
op :: Eq token
=> [([token], [a] -> Op a)]
-- ^ Describes a bunch of operators with varying associativity
-- but equal precedence. The first component of every pair lists
-- the tokens building up the operator; if there are several
-- tokens, then the operator is mixfix. The second component
-- creates an operator description, given the contents for the
-- "middle" parts of the operator (an empty list in the case of
-- non-mixfix operators).
-> ReadP token a
-- ^ Parser for the terms between the operators.
-> ReadP token a
-- ^ Parser for the terms inside mixfix operators.
-> ReadP token a
op table rest inside = do
opApps <- many1 (liftM Left rest +++ liftM Right p)
case parse checkOpApps opApps of
[e] -> return e
_ -> mzero
where
p = choice $ map (\(ts, o) -> liftM o $ intersperseP inside ts)
table
------------------------------------------------------------------------
-- Example parser
-- expr is a parser for the following fixity declarations, plus
-- variables, parentheses and the nonfix operator ⟦_,_⟧:
--
-- infixl _+_ _-_
-- infix -_ binds tighter-than (_+_)
-- infixl _*_ _/_ binds tighter-than (_+_) looser-than (-_)
--
-- infixr _∧_
-- infix !_ binds tighter-than (_∧_)
-- infixr _∨_ _⊕_ binds looser-than (_∧_)
-- infixl _⇐_ binds looser-than (_∧_)
-- infixr _⇒_ binds as _⇐_
-- infix I_T_E_ binds as _⇒_
--
-- infix _≡_ binds looser-than (_+_ _⊕_ _⇒_)
-- infix ¬_ binds looser-than (_≡_)
-- infix _? binds tighter-than (_≡_)
--
-- infixr _,_ binds tighter-than (_-_ !_)
data BinOp = Plus | Minus | Mul | Div | And | Or | Xor | Eq
| Impl | RevImpl | Pair | Sem
deriving (Show, Eq)
data UnOp = Neg | BoolNot | Not | Quest
deriving (Show, Eq)
data Expr
= IfThenElse Expr Expr Expr
| BinOp BinOp Expr Expr
| UnOp UnOp Expr
| Var Char
deriving (Show, Eq)
pre op = \[] -> Pre (UnOp op)
post op = \[] -> Post (UnOp op)
bin op ass = \[] -> Bin (BinOp op) ass
op' table rest = op table rest expr
expr :: ReadP Char Expr
expr = nt
nt = op' [(['¬'], pre Not)] eq
eq = op' [(['≡'], bin Eq Non)] (quest +++ disj +++ impls +++ plus)
quest = op' [(['?'], post Quest)] base
impls =
op' [ (['⇒'], bin Impl Rght)
, (['⇐'], bin RevImpl Lft)
, (['I','T','E'], \[i, t] -> Pre (\e -> IfThenElse i t e))
]
conj
disj =
op' [ (['⊕'], bin Xor Rght)
, (['∨'], bin Or Rght)
]
conj
conj = op' [(['∧'], bin And Rght)] boolNot
boolNot = op' [(['!'], pre BoolNot)] pair
plus =
op' [ (['+'], bin Plus Lft)
, (['-'], bin Minus Lft)
]
mul
mul =
op' [ (['*'], bin Mul Lft)
, (['/'], bin Div Lft)
]
minus
minus = op' [(['-'], pre Neg)] pair
pair = op' [([','], bin Pair Rght)] base
base = var
+++
between (char '(') (char ')') expr
+++
liftM (\[x, y] -> BinOp Sem x y)
(intersperseP expr ['⟦', ',', '⟧'])
var = liftM Var $ satisfy (\c -> isAscii c && isLower c)
------------------------------------------------------------------------
-- Example of a very slow parser
-- testSlow (n + 1) takes about twice as long as testSlow n (for n
-- large enough).
slow n
| n <= 0 = char "0" >> return 0
| otherwise = op' "+" +++ op' "-"
-- ^^^ This choice causes the inefficiency.
-- Note that slow (n - 1) is used in both branches.
where op' c = op [([c ++ show n], \[] -> Pre succ)]
(slow (n - 1))
mzero
testSlow n = parse (slow n) (map show [- n .. 0]) == [n]
testSlower n = parse (slow n) ["0"] == replicate (2 ^ n) 0
------------------------------------------------------------------------
-- Tests
test s e = do
putStrLn $ pad 20 s ++ pad 10 (isOK ++ ":") ++ show e'
where
pad n s = s ++ replicate (n - length s) ' '
e' = nub $ parse expr s
isOK | e' == e = "OK"
| otherwise = "Not OK"
main = do
test "a" [Var 'a']
test "¬¬(¬a)" [UnOp Not (UnOp Not (UnOp Not (Var 'a')))]
test "¬x≡y" [UnOp Not (BinOp Eq (Var 'x') (Var 'y'))]
test "x≡y≡z" []
test "x≡y" [BinOp Eq (Var 'x') (Var 'y')]
test "x≡(y≡z)" [BinOp Eq (Var 'x') (BinOp Eq (Var 'y') (Var 'z'))]
test "x⊕y∨z" [BinOp Xor (Var 'x') (BinOp Or (Var 'y') (Var 'z'))]
test "x∨y?" []
test "y?" [UnOp Quest (Var 'y')]
test "x⇒y⇐z" []
test "IxTyEx⇒y" []
test "x⇒y⇒z" [BinOp Impl (Var 'x') (BinOp Impl (Var 'y') (Var 'z'))]
test "x⇐y⇐z" [BinOp RevImpl (BinOp RevImpl (Var 'x') (Var 'y'))
(Var 'z')]
test "-a+b*c≡d⊕!(c∧d)"
[BinOp Eq (BinOp Plus (UnOp Neg (Var 'a'))
(BinOp Mul (Var 'b') (Var 'c')))
(BinOp Xor (Var 'd')
(UnOp BoolNot (BinOp And (Var 'c')
(Var 'd'))))
]
test "⟦x+y,z⟧*b"
[BinOp Mul (BinOp Sem (BinOp Plus (Var 'x') (Var 'y'))
(Var 'z'))
(Var 'b')]
test "⟦x+y,z⟧(a*b)" []
test "⟦x+y,z⟧⟦x+y,z⟧" []
test "⟦⟦x,y⟧,z⟧" [BinOp Sem (BinOp Sem (Var 'x') (Var 'y')) (Var 'z')]
test "IxTyE⟦x⇒y,z⟧"
[IfThenElse (Var 'x') (Var 'y')
(BinOp Sem (BinOp Impl (Var 'x') (Var 'y'))
(Var 'z'))]
slowMain = do
[n] <- getArgs
print $ testSlow $ read n