disco-0.2: src/Disco/Syntax/Operators.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
-- SPDX-License-Identifier: BSD-3-Clause
-- |
-- Module : Disco.Syntax.Operators
-- Copyright : disco team and contributors
-- Maintainer : byorgey@gmail.com
--
-- Unary and binary operators along with information like precedence,
-- fixity, and concrete syntax.
module Disco.Syntax.Operators (
-- * Operators
UOp (..),
BOp (..),
TyOp (..),
-- * Operator info
UFixity (..),
BFixity (..),
OpFixity (..),
OpInfo (..),
-- * Operator tables and lookup
opTable,
uopMap,
bopMap,
opNames,
uPrec,
bPrec,
assoc,
funPrec,
) where
import Data.Data (Data)
import GHC.Generics (Generic)
import Unbound.Generics.LocallyNameless
import Data.Char (isAlpha)
import Data.Map (Map, (!))
import qualified Data.Map as M
------------------------------------------------------------
-- Operators
------------------------------------------------------------
-- | Unary operators.
data UOp
= -- | Arithmetic negation (@-@)
Neg
| -- | Logical negation (@not@)
Not
| -- | Factorial (@!@)
Fact
deriving (Show, Read, Eq, Ord, Generic, Data, Alpha, Subst t)
-- | Binary operators.
data BOp
= -- | Addition (@+@)
Add
| -- | Subtraction (@-@)
Sub
| -- | Saturating Subtraction (@.-@ / @∸@)
SSub
| -- | Multiplication (@*@)
Mul
| -- | Division (@/@)
Div
| -- | Exponentiation (@^@)
Exp
| -- | Integer division (@//@)
IDiv
| -- | Equality test (@==@)
Eq
| -- | Not-equal (@/=@)
Neq
| -- | Less than (@<@)
Lt
| -- | Greater than (@>@)
Gt
| -- | Less than or equal (@<=@)
Leq
| -- | Greater than or equal (@>=@)
Geq
| -- | Logical and (@&&@ / @and@)
And
| -- | Logical or (@||@ / @or@)
Or
| -- | Logical implies (@->@ / @implies@)
Impl
| -- | Logical biconditional (@<->@ / @iff@)
Iff
| -- | Modulo (@mod@)
Mod
| -- | Divisibility test (@|@)
Divides
| -- | Binomial and multinomial coefficients (@choose@)
Choose
| -- | List cons (@::@)
Cons
| -- | Cartesian product of sets (@**@ / @⨯@)
CartProd
| -- | Union of two sets (@union@ / @∪@)
Union
| -- | Intersection of two sets (@intersect@ / @∩@)
Inter
| -- | Difference between two sets (@\@)
Diff
| -- | Element test (@∈@)
Elem
| -- | Subset test (@⊆@)
Subset
| -- | Make a binary boolean operator into a testable proposition
Should BOp
deriving (Show, Read, Eq, Ord, Generic, Data, Alpha, Subst t)
-- | Type operators.
data TyOp
= -- | List all values of a type
Enumerate
| -- | Count how many values there are of a type
Count
deriving (Show, Eq, Ord, Generic, Data, Alpha, Subst t)
------------------------------------------------------------
-- Operator info
------------------------------------------------------------
-- | Fixities of unary operators (either pre- or postfix).
data UFixity
= -- | Unary prefix.
Pre
| -- | Unary postfix.
Post
deriving (Eq, Ord, Enum, Bounded, Show, Generic)
-- | Fixity/associativity of infix binary operators (either left,
-- right, or non-associative).
data BFixity
= -- | Left-associative infix.
InL
| -- | Right-associative infix.
InR
| -- | Infix.
In
deriving (Eq, Ord, Enum, Bounded, Show, Generic)
-- | Operators together with their fixity.
data OpFixity
= UOpF UFixity UOp
| BOpF BFixity BOp
deriving (Eq, Show, Generic)
-- | An @OpInfo@ record contains information about an operator, such
-- as the operator itself, its fixity, a list of concrete syntax
-- representations, and a numeric precedence level.
data OpInfo = OpInfo
{ opFixity :: OpFixity
, opSyns :: [String]
, opPrec :: Int
}
deriving (Show)
------------------------------------------------------------
-- Operator table
------------------------------------------------------------
-- | The @opTable@ lists all the operators in the language, in order
-- of precedence (highest precedence first). Operators in the same
-- list have the same precedence. This table is used by both the
-- parser and the pretty-printer.
opTable :: [[OpInfo]]
opTable =
assignPrecLevels
[
[ uopInfo Pre Not ["not", "¬"]
]
,
[ uopInfo Post Fact ["!"]
]
,
[ bopInfo InR Exp ["^"]
]
,
[ uopInfo Pre Neg ["-"]
]
,
[ bopInfo In Choose ["choose"]
]
,
[ bopInfo InR CartProd ["><", "⨯"]
]
,
[ bopInfo InL Union ["union", "∪"]
, bopInfo InL Inter ["intersect", "∩"]
, bopInfo InL Diff ["\\"]
]
,
[ bopInfo InL Mul ["*"]
, bopInfo InL Div ["/"]
, bopInfo InL Mod ["mod", "%"]
, bopInfo InL IDiv ["//"]
]
,
[ bopInfo InL Add ["+"]
, bopInfo InL Sub ["-"]
, bopInfo InL SSub [".-", "∸"]
]
,
[ bopInfo InR Cons ["::"]
]
,
[ bopInfo InR Eq ["=="]
, bopInfo InR Neq ["/=", "≠", "!="]
, bopInfo InR Lt ["<"]
, bopInfo InR Gt [">"]
, bopInfo InR Leq ["<=", "≤", "=<"]
, bopInfo InR Geq [">=", "≥", "=>"]
, bopInfo InR Divides ["divides"]
, bopInfo InL Subset ["subset", "⊆"]
, bopInfo InL Elem ["elem", "∈"]
]
,
[ bopInfo InR And ["/\\", "and", "∧", "&&"]
]
,
[ bopInfo InR Or ["\\/", "or", "∨", "||"]
]
,
[ bopInfo InR Impl ["->", "==>", "→", "implies"]
, bopInfo InR Iff ["<->", "<==>", "↔", "iff"]
]
]
where
uopInfo fx op syns = OpInfo (UOpF fx op) syns (-1)
bopInfo fx op syns = OpInfo (BOpF fx op) syns (-1)
-- Start at precedence level 2 so we can give level 1 to ascription, and level 0
-- to the ambient context + parentheses etc.
assignPrecLevels table = zipWith assignPrecs (reverse [2 .. length table + 1]) table
assignPrec p op = op {opPrec = p}
assignPrecs p = map (assignPrec p)
-- | A map from all unary operators to their associated 'OpInfo' records.
uopMap :: Map UOp OpInfo
uopMap =
M.fromList $
[(op, info) | opLevel <- opTable, info@(OpInfo (UOpF _ op) _ _) <- opLevel]
-- | A map from all binary operators to their associatied 'OpInfo' records.
bopMap :: Map BOp OpInfo
bopMap =
M.fromList $
[(op, info) | opLevel <- opTable, info@(OpInfo (BOpF _ op) _ _) <- opLevel]
opNames :: [String]
opNames = [syn | OpInfo _ syns _ <- concat opTable, syn <- filter (all isAlpha) syns]
-- | A convenient function for looking up the precedence of a unary operator.
uPrec :: UOp -> Int
uPrec = opPrec . (uopMap !)
-- | A convenient function for looking up the precedence of a binary operator.
bPrec :: BOp -> Int
bPrec (Should op) = bPrec op
bPrec op = case M.lookup op bopMap of
Just (OpInfo _ _ p) -> p
_ -> error $ "BOp " ++ show op ++ " not in bopMap!"
-- | Look up the \"fixity\" (/i.e./ associativity) of a binary operator.
assoc :: BOp -> BFixity
assoc (Should op) = assoc op
assoc op =
case M.lookup op bopMap of
Just (OpInfo (BOpF fx _) _ _) -> fx
_ -> error $ "BOp " ++ show op ++ " not in bopMap!"
-- | The precedence level of function application (higher than any
-- other precedence level).
funPrec :: Int
funPrec = length opTable + 1